diff --git a/.gitignore b/.gitignore
index 70d0b71..c8bf649 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@ main.8xk
 main.bin
 
 ti83pv116.sav
+
+tool/rabbitsign
diff --git a/Makefile b/Makefile
index ae18967..4b54f14 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,19 @@
 
-CC=sdcc
-CFLAGS=-Ilib83 -c -mz80 --std-sdcc2x --no-std-crt0 --reserve-regs-iy --opt-code-size
+SDCC=sdcc
+SDCCFLAGS=-Ilib83 -c -mz80 --std-sdcc2x --no-std-crt0 --reserve-regs-iy --opt-code-size
 
 OBJS=_crt0.rel clrscr.rel putchar.rel puts.rel exit.rel gotoxy.rel __assert_fail.rel \
      getchar.rel put_int.rel ctype.rel memcpy.rel memset.rel memmove.rel memcmp.rel \
 	 strcpy.rel strlen.rel strncpy.rel \
 	 main.rel
 
-.PHONY: all clean try
+.PHONY: all clean try init
 all: main.8xk
 
+init:
+	@echo "Compiling rabbitsign..."
+	@$(CC) tool/rabbitsign-src/*.c -o tool/rabbitsign -O2 -g -w -DPROTOTYPES
+
 clean:
 	@rm -f obj/* main.ihx main.bin main.8xk
 
@@ -17,16 +21,16 @@ try: main.8xk
 	@tilem2 --rom=ti83pv116.bin
 
 %.rel: lib83/%.c
-	@echo "(lib) CC $<"
-	@$(CC) $(CFLAGS) $< -o obj/$@
+	@echo "(lib) SDCC $<"
+	@$(SDCC) $(SDCCFLAGS) $< -o obj/$@
 
 %.rel: %.c
-	@echo "CC $<"
-	@$(CC) $(CFLAGS) $< -o obj/$@
+	@echo "SDCC $<"
+	@$(SDCC) $(SDCCFLAGS) $< -o obj/$@
 
 obj/main.ihx: $(OBJS)
-	@echo "LD $@"
-	@cd obj && $(CC) -mz80 --no-std-crt0 --code-loc 0x4000 --code-size 0x4000 --xram-loc 0x9D95 --xram-size 0x6060 $^ -o ../$@ && cd ..
+	@echo "SDCCLD $@"
+	@cd obj && $(SDCC) -mz80 --no-std-crt0 --code-loc 0x4000 --code-size 0x4000 --xram-loc 0x9D95 --xram-size 0x6060 $^ -o ../$@ && cd ..
 
 main.bin: obj/main.ihx
 	@echo "IHX->BIN $@"
@@ -35,5 +39,5 @@ main.bin: obj/main.ihx
 
 main.8xk: main.bin
 	@echo "SIGN $@"
-	@rabbitsign -P -p -t 8xk -g $<
+	@tool/rabbitsign -P -p -t 8xk -g $<
 
diff --git a/README.md b/README.md
index 6a07a85..9a53d1e 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,25 @@ A C programming SDK for the TI-83 calculator. Includes pre-configured build syst
 
 ## Requirements
 
-- SDCC
-- GNU Make
-- TilEm
-- RabbitSign
+- A GNU/Linux system.
 
 ## Status
 
 WIP.
+
+## Usage
+
+Initialise the build environment:
+
+```bash
+git clone https://git.palaiologos.rocks/Palaiologos/ti83-sdk
+cd ti83-sdk
+make init
+make
+```
+
+Run the pre-supplied program:
+
+```bash
+make try
+```
diff --git a/tool/rabbitsign-src/app8x.c b/tool/rabbitsign-src/app8x.c
new file mode 100644
index 0000000..d114178
--- /dev/null
+++ b/tool/rabbitsign-src/app8x.c
@@ -0,0 +1,445 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "rabbitsign.h"
+#include "internal.h"
+#include "md5.h"
+
+/*
+ * Check/fix Flash app header and data.
+ *
+ * This function checks various parts of the application header which,
+ * if incorrect, are known to cause applications to be rejected by the
+ * calculator.  Depending on the flags, this function will also fix
+ * incorrect header fields.
+ *
+ * Note that this function can also add padding to the end of the app.
+ * The entire signature must be stored on one page, so if there is not
+ * enough room on the final page of the app, an extra page needs to be
+ * added to hold the signature.
+ *
+ * In addition, some versions of the boot code have a bug which
+ * results in incorrect MD5 hashes for applications that are 55 bytes
+ * long mod 64; this function will add an extra padding byte to avoid
+ * that case.
+ */
+int rs_repair_ti8x_app(RSProgram* app,	   /* app to repair */
+		       unsigned int flags) /* flags */
+{
+  unsigned long length, hdrstart, hdrsize, fieldstart, fieldsize, i;
+  unsigned char* hdr;
+  unsigned char dummy = 0;
+  int e, pagecount, addedpage = 0;
+
+  /* Various parts of the OS, as well as other software on the
+     calculator and PC, expect that every application begins with the
+     bytes 80 0F -- a "long" field.  Some things may work for apps
+     with an 80 0E (or even 80 0D) field, but not everything.  Please
+     stick with 80 0F. */
+
+  if (app->length < 6
+      || app->data[0] != 0x80
+      || app->data[1] != 0x0f) {
+    rs_error(NULL, app, "no app header found");
+    return RS_ERR_MISSING_HEADER;
+  }
+
+  /* Determine application length */
+
+  length = app->length;
+  rs_get_field_size(app->data, &hdrstart, &hdrsize);
+
+  /* If requested, remove the old signature (truncate the application
+     to its stated length.) */
+
+  if (flags & RS_REMOVE_OLD_SIGNATURE) {
+    if (length < hdrstart + hdrsize) {
+      rs_warning(NULL, app, "provided app data too short");
+    }
+    else {
+      if (length > hdrstart + hdrsize + 96)
+	rs_warning(NULL, app, "re-signing discards %lu bytes",
+		   length - hdrstart - hdrsize);
+      length = hdrstart + hdrsize;
+    }
+  }
+  else if (hdrsize && hdrstart + hdrsize != length) {
+    rs_warning(NULL, app, "application length incorrect");
+    rs_warning(NULL, app, "(perhaps you meant to use -r?)");
+  }
+
+  /* If necessary, add an extra page to ensure that the signature
+     doesn't span a page boundary. */
+
+  if (((length + 69 + 0x3fff) >> 14) != ((length + 0x3fff) >> 14)) {
+    if (flags & (RS_ZEALOUSLY_PAD_APP | RS_IGNORE_ALL_WARNINGS)) {
+      rs_warning(NULL, app, "adding an extra page to hold app signature");
+      length = ((length + 0x4000) & ~0x3fff) + 1;
+      addedpage = 1;
+    }
+    else {
+      rs_error(NULL, app, "application ends too close to a page boundary");
+      return RS_ERR_FINAL_PAGE_TOO_LONG;
+    }
+  }
+
+  if ((e = rs_program_set_length(app, length)))
+    return e;
+
+  /* If the length is 55 mod 64, add an extra byte.  (Note that, with
+     512-bit keys, this can never cause a page overflow.)  We use zero
+     for the padding value, rather than FF, so that our output matches
+     that of other tools. */
+
+  if ((length % 64) == 55) {
+    length++;
+    rs_message(2, NULL, app, "adding an extra byte due to boot code bugs");
+    if ((e = rs_program_append_data(app, &dummy, 1)))
+      return e;
+  }
+
+  /* Set app size header to the correct value */
+
+  hdrsize = length - hdrstart;
+  if (rs_set_field_size(app->data, hdrsize)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application length header too small");
+    else {
+      rs_error(NULL, app, "application length header too small");
+      return RS_ERR_FIELD_TOO_SMALL;
+    }
+  }
+
+  /* Check/fix page count.  This field is required to be present and
+     contain the correct number of pages.  It must be one byte long
+     (some parts of the OS don't even check the length and assume it
+     is one byte long.) */
+
+  hdr = app->data + hdrstart;
+  if (hdrsize > 128)
+    hdrsize = 128;
+
+  if (rs_find_app_field(0x8080, hdr, hdrsize,
+			NULL, &fieldstart, &fieldsize)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no page count field");
+    else {
+      rs_error(NULL, app, "application has no page count field");
+      return RS_ERR_MISSING_PAGE_COUNT;
+    }
+  }
+  else if (fieldsize != 1) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has an invalid page count field");
+    else {   
+      rs_error(NULL, app, "application has an invalid page count field");
+      return RS_ERR_INCORRECT_PAGE_COUNT;
+    }
+  }
+  else {
+    pagecount = ((length + 0x3fff) >> 14);
+
+    if (flags & RS_FIX_PAGE_COUNT) {
+      hdr[fieldstart] = pagecount;
+    }
+    else if (addedpage && hdr[fieldstart] == pagecount - 1) {
+      hdr[fieldstart] = pagecount;
+    }
+    else if (hdr[fieldstart] != pagecount) {
+      if (flags & RS_IGNORE_ALL_WARNINGS) {
+	rs_warning(NULL, app,
+		   "application has an incorrect page count (actual: %lu)",
+		   ((length + 0x3fff) >> 14));
+	hdr[fieldstart] = pagecount;
+      }
+      else {      
+	rs_error(NULL, app,
+		 "application has an incorrect page count (actual: %lu)",
+		 ((length + 0x3fff) >> 14));
+	return RS_ERR_INCORRECT_PAGE_COUNT;
+      }
+    }
+  }
+
+  /* Check for key ID.  This field is required to be present; it
+     determines which public key is used for validation.  (The
+     contents of this field are usually thought of as a big-endian
+     integer, but to be more precise, they're really treated as a
+     binary string.) */
+
+  if (rs_find_app_field(0x8010, hdr, hdrsize, NULL, NULL, NULL)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no key ID");
+    else {
+      rs_error(NULL, app, "application has no key ID");
+      return RS_ERR_MISSING_KEY_ID;
+    }
+  }
+
+  /* Check for date stamp.  This seems to be required -- the OS will
+     use it to update its last-known date stamp if necessary -- and
+     should consist of an 032x field containing an 090x field,
+     followed by an 020x field containing the date stamp signature.
+     (The contents of the latter only matter if the date stamp is
+     "new.") */
+
+  if (rs_find_app_field(0x0320, hdr, hdrsize,
+			NULL, &fieldstart, &fieldsize)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no date stamp");
+    else {
+      rs_error(NULL, app, "application has no date stamp");
+      return RS_ERR_MISSING_DATE_STAMP;
+    }
+  }
+  else if (rs_find_app_field(0x0900, hdr + fieldstart, fieldsize,
+			     NULL, NULL, NULL)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no date stamp");
+    else {
+      rs_error(NULL, app, "application has no date stamp");
+      return RS_ERR_MISSING_DATE_STAMP;
+    }
+  }
+  else if (hdr[fieldstart + fieldsize] != 0x02
+	   || (hdr[fieldstart + fieldsize + 1] & 0xf0) != 0) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no date stamp signature");
+    else {
+      rs_error(NULL, app, "application has no date stamp signature");
+      return RS_ERR_MISSING_DATE_STAMP;
+    }
+  }
+
+  /* Check for program image field.  This field indicates the end of
+     the header and the start of application code.  Note, however,
+     that the OS handles this field in an exceedingly broken way.  To
+     be safe, this must always be the last field of the header, and
+     should always be written as 80 7F followed by four length bytes.
+     The length bytes may be anything you like -- they're ignored. */
+
+  if (rs_find_app_field(0x8070, hdr, hdrsize,
+			NULL, NULL, NULL)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no program image field");
+    else {
+      rs_error(NULL, app, "application has no program image field");
+      return RS_ERR_MISSING_PROGRAM_IMAGE;
+    }
+  }
+
+  /* Check for invalid pages (those beginning with FF.)  An OS bug
+     means that such pages will end up being erased completely if
+     defragmenting requires the application to be moved in Flash. */
+
+  e = RS_SUCCESS;
+
+  for (i = 0; i < app->length; i += 0x4000) {
+    if (app->data[i] == 0xff) {
+      if (flags & RS_IGNORE_ALL_WARNINGS)
+	rs_warning(NULL, app, "page %ld begins with FFh", (i >> 14));
+      else {
+	rs_error(NULL, app, "page %ld begins with FFh", (i >> 14));
+	e = RS_ERR_INVALID_PROGRAM_DATA;
+      }
+    }
+  }
+
+  return e;
+}
+
+/*
+ * Compute signature for a Flash app.
+ *
+ * The app header should be checked and/or repaired by
+ * rs_repair_ti8x_app() prior to calling this function.
+ *
+ * There are four equally valid Rabin signatures for any application;
+ * rootnum determines which of the four should be used.
+ */
+int rs_sign_ti8x_app(RSProgram* app, /* app to sign */
+		     RSKey* key,     /* signing key */
+		     int rootnum)    /* signature number */
+{
+  md5_uint32 hash[4];
+  mpz_t hashv, sigv;
+  int f;
+  unsigned int lastpagelength;
+  unsigned char sigdata[512];
+  size_t siglength;
+  int e;
+
+  /* Check if app length is risky */
+
+  if ((app->length % 64) == 55) {
+    rs_warning(NULL, app, "application has length 55 mod 64");
+    rs_warning(NULL, app, "(this will fail to validate on TI-83+ BE)");
+  }
+
+  /* Compute signature */
+
+  md5_buffer((char*) app->data, app->length, hash);
+
+  mpz_init(hashv);
+  mpz_init(sigv);
+
+  mpz_import(hashv, 16, -1, 1, 0, 0, hash);
+  rs_message(2, NULL, app, "hash = %ZX", hashv);
+
+  if ((e = rs_sign_rabin(sigv, &f, hashv, rootnum, key))) {
+    mpz_clear(hashv);
+    mpz_clear(sigv);
+    return e;
+  }
+
+  rs_message(2, NULL, app, "sig = %ZX", sigv);
+  rs_message(2, NULL, app, "f = %d", f);
+
+  /* Write the square root value as an 022D field... */
+
+  sigdata[0] = 0x02;
+  sigdata[1] = 0x2d;
+  mpz_export(sigdata + 3, &siglength, -1, 1, 0, 0, sigv);
+  sigdata[2] = siglength & 0xff;
+  siglength += 3;
+
+  mpz_clear(hashv);
+  mpz_clear(sigv);
+
+  /* ...and append the f value as a big integer */
+
+  if (f == 0) {
+    sigdata[siglength++] = 0;
+  }
+  else {
+    sigdata[siglength++] = 1;
+    sigdata[siglength++] = f;
+  }
+
+  /* Add padding, but not too much (it seems to make some link
+     programs happier) */
+
+  lastpagelength = app->length & 0x3fff;
+
+  while (siglength < 96 && (lastpagelength + siglength) < 0x3fff)
+    sigdata[siglength++] = 0xff;
+
+  return rs_program_append_data(app, sigdata, siglength);
+}
+
+/*
+ * Validate a Flash app signature.
+ */
+int rs_validate_ti8x_app(const RSProgram* app, /* app to validate */
+			 const RSKey* key)     /* signing key */
+{
+  unsigned long length, hdrstart, hdrsize, fieldstart, fieldsize, i;
+  const unsigned char *hdr, *sig;
+  md5_uint32 hash[4];
+  mpz_t hashv, sigv;
+  int f, e, e2 = RS_SUCCESS;
+
+  if (app->length < 6) {
+    rs_error(NULL, app, "no app header found");
+    return RS_ERR_MISSING_HEADER;
+  }
+
+  rs_get_field_size(app->data, &hdrstart, &hdrsize);
+  length = hdrstart + hdrsize;
+  hdr = app->data + hdrstart;
+  if (hdrsize > 128)
+    hdrsize = 128;
+
+  if (((length + 0x3fff) >> 14) != ((app->length + 0x3fff) >> 14)
+      || length + 4 > app->length || length + 96 < app->length) {
+    rs_error(NULL, app, "incorrect application length");
+    return RS_ERR_INCORRECT_PROGRAM_SIZE;
+  }
+
+  if (rs_find_app_field(0x8070, hdr, hdrsize,
+			NULL, NULL, NULL)) {
+    rs_warning(NULL, app, "application has no program image field");
+    e2 = RS_ERR_MISSING_PROGRAM_IMAGE;
+  }
+
+  if (rs_find_app_field(0x8080, hdr, hdrsize,
+			NULL, &fieldstart, &fieldsize)) {
+    rs_warning(NULL, app, "application has no no page count field");
+    e2 = RS_ERR_MISSING_PAGE_COUNT;
+  }
+  else if (fieldsize != 1) {
+    rs_warning(NULL, app, "application has an invalid page count field");
+    e2 = RS_ERR_INCORRECT_PAGE_COUNT;
+  }
+  else if (hdr[fieldstart] != ((length + 0x3fff) >> 14)) {
+    rs_warning(NULL, app, "application has an incorrect page count field");
+    e2 = RS_ERR_INCORRECT_PAGE_COUNT;
+  }
+
+  if ((length % 64) == 55) {
+    rs_warning(NULL, app, "application has length 55 mod 64");
+    rs_warning(NULL, app, "(this will fail to validate on TI-83+ BE)");
+    e2 = RS_ERR_INVALID_PROGRAM_SIZE;
+  }
+
+  for (i = 0; i < app->length; i += 0x4000) {
+    if (app->data[i] == 0xff) {
+      rs_warning(NULL, app, "page %ld begins with FFh", (i >> 14));
+      e2 = RS_ERR_INVALID_PROGRAM_DATA;
+    }
+  }
+
+  md5_buffer((char*) app->data, length, &hash);
+
+  sig = app->data + length;
+  if (sig[0] != 0x02 || sig[1] != 0x2d) {
+    rs_error(NULL, app, "application does not have a Rabin signature");
+    return RS_ERR_MISSING_RABIN_SIGNATURE;
+  }
+  rs_get_field_size(sig, &fieldstart, &fieldsize);
+
+  mpz_init(sigv);
+  mpz_init(hashv);
+
+  mpz_import(hashv, 16, -1, 1, 0, 0, hash);
+  rs_message(2, NULL, app, "hash = %ZX", hashv);
+
+  mpz_import(sigv, fieldsize, -1, 1, 0, 0, sig + fieldstart);
+  rs_message(2, NULL, app, "sig = %ZX", sigv);
+
+  if (sig[fieldstart + fieldsize] == 0)
+    f = 0;
+  else
+    f = sig[fieldstart + fieldsize + 1];
+  rs_message(2, NULL, app, "f = %d", f);
+
+  e = rs_validate_rabin(sigv, f, hashv, key);
+  if (e == RS_SIGNATURE_INCORRECT)
+    rs_message(0, NULL, app, "application signature incorrect");
+
+  mpz_clear(sigv);
+  mpz_clear(hashv);
+  return (e ? e : e2);
+}
+
diff --git a/tool/rabbitsign-src/app9x.c b/tool/rabbitsign-src/app9x.c
new file mode 100644
index 0000000..9d0dda9
--- /dev/null
+++ b/tool/rabbitsign-src/app9x.c
@@ -0,0 +1,297 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "rabbitsign.h"
+#include "internal.h"
+#include "md5.h"
+
+/*
+ * Check/fix app/OS header and data.
+ *
+ * (This is something of a work in progress; a lot more
+ * experimentation would be useful to determine what exactly is
+ * required of app and OS headers on the 68k calculators.)
+ */
+static int repair_app(RSProgram* app,     /* app to repair */
+		      unsigned int flags, /* flags */
+		      unsigned int type)  /* field type */
+{
+  unsigned long length, hdrstart, hdrsize, fieldhead,
+    fieldstart, fieldsize;
+  unsigned char *hdr;
+  int e;
+
+  if (app->length < 6
+      || app->data[0] != type
+      || (app->data[1] & 0xf0) != 0) {
+    rs_error(NULL, app, "no app header found");
+    return RS_ERR_MISSING_HEADER;
+  }
+
+  /* Determine application length */
+
+  length = app->length;
+  rs_get_field_size(app->data, &hdrstart, &hdrsize);
+
+  /* If requested, remove the old signature (truncate the application
+     to its stated length.) */
+
+  if (flags & RS_REMOVE_OLD_SIGNATURE) {
+    if (length < hdrstart + hdrsize) {
+      rs_warning(NULL, app, "provided app data too short");
+    }
+    else {
+      if (length > hdrstart + hdrsize + 67)
+	rs_warning(NULL, app, "re-signing discards %lu bytes",
+		   length - hdrstart - hdrsize);
+      length = hdrstart + hdrsize;
+    }
+  }
+  else if (hdrsize && hdrstart + hdrsize != length) {
+    rs_warning(NULL, app, "application length incorrect");
+    rs_warning(NULL, app, "(perhaps you meant to use -r?)");
+  }
+
+  if ((e = rs_program_set_length(app, length)))
+    return e;
+
+  /* Set app size header to the correct value */
+
+  hdrsize = length - hdrstart;
+  if (rs_set_field_size(app->data, hdrsize)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "cannot set application length");
+    else {
+      rs_error(NULL, app, "cannot set application length");
+      return RS_ERR_FIELD_TOO_SMALL;
+    }
+  }
+
+  /* Check for key ID */
+
+  hdr = app->data + hdrstart;
+  if (hdrsize > 128)
+    hdrsize = 128;
+
+  if (rs_find_app_field((type << 8) | 0x10, hdr, hdrsize,
+			NULL, NULL, NULL)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no key ID");
+    else {
+      rs_error(NULL, app, "application has no key ID");
+      return RS_ERR_MISSING_KEY_ID;
+    }
+  }
+
+  /* Check for date stamp (note: I haven't actually tested whether
+     this is required, but it always seems to be present in both 68k
+     apps and OSes, and it is required for TI-83+ apps) */
+
+  if (rs_find_app_field(0x0320, hdr, hdrsize,
+			NULL, &fieldstart, &fieldsize)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no date stamp");
+    else {
+      rs_error(NULL, app, "application has no date stamp");
+      return RS_ERR_MISSING_DATE_STAMP;
+    }
+  }
+  else if (rs_find_app_field(0x0900, hdr + fieldstart, fieldsize,
+			NULL, NULL, NULL)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no date stamp");
+    else {
+      rs_error(NULL, app, "application has no date stamp");
+      return RS_ERR_MISSING_DATE_STAMP;
+    }
+  }
+  else if (hdr[fieldstart + fieldsize] != 0x02
+      || (hdr[fieldstart + fieldsize + 1] & 0xf0) != 0) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no date stamp signature");
+    else {
+      rs_error(NULL, app, "application has no date stamp signature");
+      return RS_ERR_MISSING_DATE_STAMP;
+    }
+  }
+
+  /* Check for program image field and fix length */
+
+  if (rs_find_app_field((type << 8) | 0x70, hdr, hdrsize,
+			&fieldhead, &fieldstart, &fieldsize)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, app, "application has no program image field");
+    else {
+      rs_error(NULL, app, "application has no program image field");
+      return RS_ERR_MISSING_PROGRAM_IMAGE;
+    }
+  }
+  else {
+    if ((fieldstart + hdrstart) % 2) {
+      /* The OS appears to align apps so the start of the app header
+	 is at an even address; if the application code itself is at
+	 an odd address, bad stuff will happen. */
+      if (flags & RS_IGNORE_ALL_WARNINGS)
+	rs_warning(NULL, app, "application header is not a multiple of 2 bytes");
+      else {
+	rs_error(NULL, app, "application header is not a multiple of 2 bytes");
+	return RS_ERR_MISALIGNED_PROGRAM_IMAGE;
+      }
+    }
+
+    if (fieldsize && fieldstart + fieldsize != length - hdrstart)
+      rs_warning(NULL, app, "program image length incorrect");
+
+    if (rs_set_field_size(hdr + fieldhead, length - hdrstart - fieldstart)) {
+      rs_error(NULL, app, "cannot set program image length");
+      return RS_ERR_FIELD_TOO_SMALL;
+    }
+  }
+
+  return RS_SUCCESS;
+}
+
+/*
+ * Check/fix Flash app header and data.
+ */
+int rs_repair_ti9x_app(RSProgram* app,     /* app to repair */
+		       unsigned int flags) /* flags */
+{
+  return repair_app(app, flags, 0x81);
+}
+
+/*
+ * Check/fix OS header and data.
+ */
+int rs_repair_ti9x_os(RSProgram* app,     /* app to repair */
+		      unsigned int flags) /* flags */
+{
+  return repair_app(app, flags, 0x80);
+}
+
+/*
+ * Compute signature for a 68k app/OS.
+ *
+ * The app header should be checked and/or repaired by
+ * rs_repair_ti9x_app() prior to calling this function.
+ */
+int rs_sign_ti9x_app(RSProgram* app, /* app to sign */
+		     RSKey* key)     /* signing key */
+{
+  md5_uint32 hash[4];
+  mpz_t hashv, sigv;
+  unsigned char sigdata[512];
+  size_t siglength;
+  int e;
+
+  md5_buffer((char*) app->data, app->length, &hash);
+
+  mpz_init(hashv);
+  mpz_init(sigv);
+
+  mpz_import(hashv, 16, -1, 1, 0, 0, hash);
+  rs_message(2, NULL, app, "hash = %ZX", hashv);
+
+  if ((e = rs_sign_rsa(sigv, hashv, key))) {
+    mpz_clear(hashv);
+    mpz_clear(sigv);
+    return e;
+  }
+
+  rs_message(2, NULL, app, "sig = %ZX", sigv);
+
+  sigdata[0] = 0x02;
+  sigdata[1] = 0x0d;
+  mpz_export(sigdata + 3, &siglength, -1, 1, 0, 0, sigv);
+  sigdata[2] = siglength & 0xff;
+  siglength += 3;
+
+  return rs_program_append_data(app, sigdata, siglength);
+}
+
+/*
+ * Validate app/OS signature.
+ */
+int rs_validate_ti9x_app(const RSProgram* app, /* app to validate */
+			 const RSKey* key)     /* signing key */
+{
+  unsigned long length, hdrstart, hdrsize, fieldstart, fieldsize;
+  const unsigned char *hdr, *sig;
+  md5_uint32 hash[4];
+  mpz_t hashv, sigv;
+  int e, e2 = RS_SUCCESS;
+
+  if (app->length < 6) {
+    rs_error(NULL, app, "no app header found");
+    return RS_ERR_MISSING_HEADER;
+  }
+
+  rs_get_field_size(app->data, &hdrstart, &hdrsize);
+  length = hdrstart + hdrsize;
+  hdr = app->data + hdrstart;
+  if (hdrsize > 128)
+    hdrsize = 128;
+
+  if (length + 4 > app->length || length + 67 < app->length) {
+    rs_error(NULL, app, "incorrect application length");
+    return RS_ERR_INCORRECT_PROGRAM_SIZE;
+  }
+
+  if (rs_find_app_field((app->data[0] << 8) | 0x70, hdr, hdrsize,
+			NULL, &fieldstart, &fieldsize)) {
+    rs_warning(NULL, app, "application has no program image field");
+    e2 = RS_ERR_MISSING_PROGRAM_IMAGE;
+  }
+  else if ((fieldstart + hdrstart) % 2) {
+    rs_warning(NULL, app, "application header is not a multiple of 2 bytes");
+    e2 = RS_ERR_MISALIGNED_PROGRAM_IMAGE;
+  }
+
+  md5_buffer((char*) app->data, length, &hash);
+
+  sig = app->data + length;
+  if (sig[0] != 0x02 || (sig[1] & 0xf0) != 0x00) {
+    rs_error(NULL, app, "application does not have an RSA signature");
+    return RS_ERR_MISSING_RSA_SIGNATURE;
+  }
+  rs_get_field_size(sig, &fieldstart, &fieldsize);
+
+  mpz_init(sigv);
+  mpz_init(hashv);
+
+  mpz_import(hashv, 16, -1, 1, 0, 0, hash);
+  rs_message(2, NULL, app, "hash = %ZX", hashv);
+
+  mpz_import(sigv, fieldsize, -1, 1, 0, 0, sig + fieldstart);
+  rs_message(2, NULL, app, "sig = %ZX", sigv);
+
+  e = rs_validate_rsa(sigv, hashv, key);
+  if (e == RS_SIGNATURE_INCORRECT)
+    rs_message(0, NULL, app, "application signature incorrect");
+
+  mpz_clear(sigv);
+  mpz_clear(hashv);
+  return (e ? e : e2);
+}
+
diff --git a/tool/rabbitsign-src/apps.c b/tool/rabbitsign-src/apps.c
new file mode 100644
index 0000000..896674d
--- /dev/null
+++ b/tool/rabbitsign-src/apps.c
@@ -0,0 +1,85 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Check/fix program header and data.
+ */
+int rs_repair_program(RSProgram* prgm,	  /* app to repair */
+		      unsigned int flags) /* flags */
+{
+  if (rs_calc_is_ti8x(prgm->calctype)) {
+    if (prgm->datatype == RS_DATA_OS)
+      return rs_repair_ti8x_os(prgm, flags);
+    else if (prgm->datatype == RS_DATA_APP)
+      return rs_repair_ti8x_app(prgm, flags);
+  }
+
+  if (rs_calc_is_ti9x(prgm->calctype)) {
+    if (prgm->datatype == RS_DATA_OS)
+      return rs_repair_ti9x_os(prgm, flags);
+    else if (prgm->datatype == RS_DATA_APP)
+      return rs_repair_ti9x_app(prgm, flags);
+  }
+
+  rs_error(NULL, prgm, "calc/data type (%X/%X) unrecognized",
+	   prgm->calctype, prgm->datatype);
+  return RS_ERR_UNKNOWN_PROGRAM_TYPE;
+}
+
+/*
+ * Add a signature to the program.
+ */
+int rs_sign_program(RSProgram* prgm, /* app to sign */
+		    RSKey* key,      /* signing key */
+		    int rootnum)     /* signature number */
+{
+  if (rs_calc_is_ti8x(prgm->calctype)) {
+    if (prgm->datatype == RS_DATA_OS)
+      return rs_sign_ti8x_os(prgm, key);
+    else if (prgm->datatype == RS_DATA_APP)
+      return rs_sign_ti8x_app(prgm, key, rootnum);
+  }
+
+  return rs_sign_ti9x_app(prgm, key);
+}
+
+/*
+ * Validate program signature.
+ */
+int rs_validate_program(const RSProgram* prgm, /* app to validate */
+			const RSKey* key)      /* signing key */
+{
+  if (rs_calc_is_ti8x(prgm->calctype)) {
+    if (prgm->datatype == RS_DATA_OS)
+      return rs_validate_ti8x_os(prgm, key);
+    else if (prgm->datatype == RS_DATA_APP)
+      return rs_validate_ti8x_app(prgm, key);
+  }
+
+  return rs_validate_ti9x_app(prgm, key);
+}
+
diff --git a/tool/rabbitsign-src/autokey.c b/tool/rabbitsign-src/autokey.c
new file mode 100644
index 0000000..23b4a5f
--- /dev/null
+++ b/tool/rabbitsign-src/autokey.c
@@ -0,0 +1,241 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+#include "autokeys.h"
+
+/*
+ * Get key ID for the given program.
+ */
+unsigned long rs_program_get_key_id(const RSProgram* prgm)
+{
+  const unsigned char* hdr;
+  unsigned long hdrstart, hdrsize;
+
+  if (prgm->header_length > 0) {
+    hdr = prgm->header;
+    hdrsize = prgm->header_length;
+  }
+  else if (prgm->length > 0) {
+    hdr = prgm->data;
+    hdrsize = prgm->length;
+    if (hdrsize > 128)
+      hdrsize = 128;
+  }
+  else
+    return 0;
+
+  rs_get_field_size(hdr, &hdrstart, NULL);
+  hdrsize -= hdrstart;
+
+  if (hdr[0] == 0x81)
+    return rs_get_numeric_field(0x8110, hdr + hdrstart, hdrsize);
+  else
+    return rs_get_numeric_field(0x8010, hdr + hdrstart, hdrsize);
+}
+
+/*
+ * Try to load key from a file.
+ */
+static int try_key_file(RSKey* key, /* key structure to store result */
+			const char* a, /* first path element */
+			const char* b, /* second path element */
+			const char* c) /* third path element */
+{
+  char* s;
+  FILE* f;
+  int e;
+
+  s = rs_malloc(strlen(a) + strlen(b) + strlen(c) + 1);
+  if (!s)
+    return RS_ERR_OUT_OF_MEMORY;
+  strcpy(s, a);
+  strcat(s, b);
+  strcat(s, c);
+
+  f = fopen(s, "rt");
+  if (!f) {
+    rs_free(s);
+    return RS_ERR_KEY_NOT_FOUND;
+  }
+
+  if ((e = rs_read_key_file(key, f, s, 1))) {
+    fclose(f);
+    rs_free(s);
+    return e;
+  }
+
+  fclose(f);
+  rs_free(s);
+  return RS_SUCCESS;
+}
+
+/*
+ * Try to locate a given key file.
+ */
+static int find_key_file(RSKey* key,           /* key structure to
+						  store result */
+			 const char* filename) /* file name to search
+						  for */
+{
+  const char* p;
+  int e;
+
+  e = try_key_file(key, "", "", filename);
+  if (e != RS_ERR_KEY_NOT_FOUND)
+    return e;
+
+  if ((p = getenv("RABBITSIGN_KEY_DIR"))) {
+#if defined(__MSDOS__) || defined(__WIN32__)
+    e = try_key_file(key, p, "\\", filename);
+#else
+    e = try_key_file(key, p, "/", filename);
+#endif
+    if (e != RS_ERR_KEY_NOT_FOUND)
+      return e;
+  }
+
+#if defined(__MSDOS__) || defined(__WIN32__)
+  if ((p = getenv("TI83PLUSDIR"))) {
+    e = try_key_file(key, p, "\\Utils\\", filename);
+    if (e != RS_ERR_KEY_NOT_FOUND)
+      return e;
+  }
+#endif
+
+#ifdef SHARE_DIR
+  e = try_key_file(key, SHARE_DIR, "", filename);
+  if (e != RS_ERR_KEY_NOT_FOUND)
+    return e;
+#endif
+
+  return RS_ERR_KEY_NOT_FOUND;
+}
+
+/*
+ * Find key file for the given ID.
+ */
+int rs_key_find_for_id(RSKey* key,	    /* key structure to store
+					       result */
+		       unsigned long keyid, /* key ID to search for */
+		       int publiconly)	    /* 1 = search for public
+					       key only */
+{
+  static const char* fmts[] = { "%02lx.%s", "%02lX.%s",
+				"%04lx.%s", "%04lX.%s", NULL };
+  char buf[16];
+  int i, e;
+
+  mpz_set_ui(key->p, 0);
+  mpz_set_ui(key->q, 0);
+  mpz_set_ui(key->qinv, 0);
+  mpz_set_ui(key->d, 0);
+
+  if (keyid > 0xFF)
+    sprintf(buf, "%04lX", keyid);
+  else
+    sprintf(buf, "%02lX", keyid);
+
+  for (i = 0; known_priv_keys[i].n; i++) {
+    if (keyid == known_priv_keys[i].id) {
+      if ((e = rs_parse_key_value(key->n, known_priv_keys[i].n)))
+	return e;
+
+      if (known_priv_keys[i].p
+	  && (e = rs_parse_key_value(key->p, known_priv_keys[i].p)))
+	return e;
+      if (known_priv_keys[i].q
+	  && (e = rs_parse_key_value(key->q, known_priv_keys[i].q)))
+	return e;
+      if (known_priv_keys[i].d
+	  && (e = rs_parse_key_value(key->d, known_priv_keys[i].d)))
+	return e;
+
+      rs_message(2, key, NULL, "Loaded builtin private key %s:", buf);
+      rs_message(2, key, NULL, " n = %ZX", key->n);
+      if (mpz_sgn(key->p))
+	rs_message(2, key, NULL, " p = %ZX", key->p);
+      if (mpz_sgn(key->q))
+	rs_message(2, key, NULL, " q = %ZX", key->q);
+      if (mpz_sgn(key->d))
+	rs_message(2, key, NULL, " d = %ZX", key->d);
+
+      key->id = keyid;
+      return 0;
+    }
+  }
+
+  if (publiconly) {
+    for (i = 0; known_pub_keys[i].n; i++) {
+      if (keyid == known_pub_keys[i].id) {
+	if ((e = rs_parse_key_value(key->n, known_pub_keys[i].n)))
+	  return e;
+
+	rs_message(2, key, NULL, "Loaded builtin public key %s:", buf);
+	rs_message(2, key, NULL, " n = %ZX", key->n);
+
+	key->id = keyid;
+	return 0;
+      }
+    }
+  }
+
+  for (i = 0; fmts[i]; i++) {
+    sprintf(buf, fmts[i], keyid, "key");
+    e = find_key_file(key, buf);
+    if (e != RS_ERR_KEY_NOT_FOUND) {
+      if (e == 0 && !key->id)
+	key->id = keyid;
+      return e;
+    }
+  }
+
+  if (publiconly) {
+    for (i = 0; fmts[i]; i++) {
+      sprintf(buf, fmts[i], keyid, "pub");
+      e = find_key_file(key, buf);
+      if (e != RS_ERR_KEY_NOT_FOUND) {
+	if (e == 0 && !key->id)
+	  key->id = keyid;
+	return e;
+      }
+    }
+  }
+
+  rs_error(NULL, NULL, "cannot find key file %s", buf);
+  return RS_ERR_KEY_NOT_FOUND;
+}
diff --git a/tool/rabbitsign-src/autokeys.h b/tool/rabbitsign-src/autokeys.h
new file mode 100644
index 0000000..9880eab
--- /dev/null
+++ b/tool/rabbitsign-src/autokeys.h
@@ -0,0 +1,47 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+struct pubkeyinfo {
+  unsigned long id;
+  const char* n;
+} known_pub_keys[] =
+  {{ 0x0101, "406104CDFAD955D41F1ECCB9B622007FE8BC75E8B28DA178334755FEF27C564D47B04FD82498C163B762991C68CF64E29236BC41A4C1BCB9793B6EE965407C74BC" },
+   { 0x0102, "4085F11FF810591B84875FDE4C92A5961CDD233A9B7ED76E8CFF65128C71420FCCC80E375DC8D2A8551AE2BEB9FD41654CE7B0A95E32BE9997750407904560BEFC" },
+   { 0x0103, "40ED0236FD3D8B0CBA88C1CECFA549F8A838CDBAC487F9253F6C67F20C9627984FEA0D0A0BA035424C7B9F5E702286CEDCC66D2DA93320F7071BF2C93C59DE6B91" },
+   { 0x010A, "4005D1EB8485AA14C983FFA04031B27C89950C3D7F4181FE603A353F48DE0933DFE1173BDD2E14FEB7325BAA35A12F21804DCFD30E56119C1305D348A77BCF448F" },
+   { 0x01, "40F78D55E9A40A92D11D5C1446BFCEA74C8368BE1A81B6BA1596970E6D5932D933B86FF3CEA6B381CE65F5E383BDB02C82F33B8190375D0B40DEF2F1FE3CCA49AD" },
+   { 0x02, "4081396D55C0989BC949FA30821FFE61C9441EDC3827D0E89EEE16DDEF697634B8E10B8B7F42FE7CC1A7478606D6D09F6FE96365E71E3D2AAA7C8D91068F1DFAF3" },
+   { 0x03, "40E7C21F66BD1116F2F4F691121F3330060E24C8C7A1858D49636E24E80015F3AA25C2F6033AB39067D453945ABD8A5F4CFAFADABAF8BA2BFB88895A04B5D47689" },
+   { 0x04, "408FE528B340EB1C88B505B2354BAADF47F3616D92CB532E7E5A2A0DFF1C4E4283CEEA2B2F7AD5F28B7E4BE4F3F4C99CABA0D98A8E5F2BE15E2AAC7CED0940EF82" },
+   { 0x08, "40110510EE17B0A300E2BB27441F266843EDB541BAC1077AC203CF18ABB7800F8F0E259495F80D863C49C4EE7E9FB1FE03488A140C7CD5A54CE148C8CE22B00783" },
+   { 0x0A, "40B11C71D4EA2C13C9AB2E501C6085FEC87FF3B88BFD783EAC43351E1B10F65AD31C79C1268F75051DC8FC008EBF593AE5912E8B653975C13127E2B60A0BEF5FEF" },
+   { 0, 0 }};
+
+struct privkeyinfo {
+  unsigned long id;
+  const char* n;
+  const char* p;
+  const char* q;
+  const char* d;
+} known_priv_keys[] =
+  {{ 0x0104, "40AD2431DA2297E4175EAC61A3154FA3D847115794DD330AB7FF36BA59FEDA195FEA7C16743BD7BCED8A0DA885E5E5C34D5BF20D0AB3EF9181ED39BA2C4D898E87",
+     "205B2E54E9B5C1FE26CE93261478D3873F3FC41BFFF1F5F934D7A5793A43C1C21C", "2197F7707B94079B73858720BF6D4909AB3BEDA1BA9B93112B041340A16ED597B604", 0 },
+   { 0x05, "406BABF27E9BF1826FD46CBF934E3360EF1F1D3D09D6C74E9DF78049D01A42F584BD383A10E64330C2EE6F1B1C5162789E91E94677900F85D98E7D99F49B30A2BF",
+     /* "20F59BA0274F1CA6231A882B053AAD9A2B80EBE9D2B6E9FD1CDCFCE1AD9D9414D3", "20DFED657A28DE2BFF75DE4F1AEBB7555859779DA38A671B7C76F81B50F02A6AE8", */
+     0, 0, "40E131D6636091E0F0EB3F6444FA2DABB7744FD4DDCF54018AD906C38A0789180D05C7A9275A9149819B05F279F357CEF3A0C53855AF90992572E0F09E3DC2B970" },
+   { 0, 0, 0, 0, 0 }};
diff --git a/tool/rabbitsign-src/cmdline.c b/tool/rabbitsign-src/cmdline.c
new file mode 100644
index 0000000..af4cb45
--- /dev/null
+++ b/tool/rabbitsign-src/cmdline.c
@@ -0,0 +1,112 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+#if !defined(strchr) && !defined(HAVE_STRCHR) && defined(HAVE_INDEX)
+# define strchr index
+#endif
+
+/*
+ * Parse and return next command line option.
+ */
+int rs_parse_cmdline(int argc, char** argv, const char* optstring,
+		     int* i, int* j, const char** arg)
+{
+  char c;
+  char* p;
+
+  if (*i >= argc)
+    return RS_CMDLINE_FINISHED;
+
+  if (argv[*i][0] != '-' || argv[*i][1] == 0) {
+    *arg = argv[*i];
+    (*i)++;
+    *j = 1;
+    return RS_CMDLINE_FILENAME;
+  }
+
+  if (argv[*i][1] == '-') {
+    if (!strcasecmp(argv[*i], "--help")) {
+      (*i)++;
+      *j = 1;
+      return RS_CMDLINE_HELP;
+    }
+    else if (!strcasecmp(argv[*i], "--version")) {
+      (*i)++;
+      *j = 1;
+      return RS_CMDLINE_VERSION;
+    }
+    else {
+      rs_error(NULL, NULL, "unrecognized option %s (try --help)", argv[*i]);
+      return RS_CMDLINE_ERROR;
+    }
+  }
+
+  c = argv[*i][*j];
+
+  if (c == ':' || !(p = strchr(optstring, c))) {
+    rs_error(NULL, NULL, "unrecognized option -%c (try --help)", c);
+    return RS_CMDLINE_ERROR;
+  }
+
+  if (p[1] == ':') {
+    if (argv[*i][*j + 1]) {
+      *arg = &argv[*i][*j + 1];
+      (*i)++;
+      *j = 1;
+      return c;
+    }
+    else {
+      (*i) += 2;
+      *j = 1;
+      if (*i > argc) {
+	rs_error(NULL, NULL, "-%c: requires an argument", c);
+	return RS_CMDLINE_ERROR;
+      }
+      *arg = argv[*i - 1];
+      return c;
+    }
+  }
+  else {
+    if (argv[*i][*j + 1]) {
+      (*j)++;
+    }
+    else {
+      (*i)++;
+      *j = 1;
+    }
+    *arg = NULL;
+    return c;
+  }
+}
diff --git a/tool/rabbitsign-src/error.c b/tool/rabbitsign-src/error.c
new file mode 100644
index 0000000..031f2a0
--- /dev/null
+++ b/tool/rabbitsign-src/error.c
@@ -0,0 +1,131 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+static const char* progname;
+static int verbose;
+static RSMessageFunc errorfunc, messagefunc;
+static void *errorfuncdata, *messagefuncdata;
+
+void rs_set_progname(s)
+     const char* s;
+{
+  progname = s;
+}
+
+void rs_set_verbose(v)
+     int v;
+{
+  verbose = v;
+}
+
+void rs_set_error_func(RSMessageFunc func, void* data)
+{
+  errorfunc = func;
+  errorfuncdata = data;
+}
+
+void rs_set_message_func(RSMessageFunc func, void* data)
+{
+  messagefunc = func;
+  messagefuncdata = data;
+}
+
+static void print_message(const RSKey* key, const RSProgram* prgm,
+			  const char* msg)
+{
+  if (prgm && prgm->filename)
+    fprintf(stderr, "%s: ", prgm->filename);
+  else if (key && key->filename)
+    fprintf(stderr, "%s: ", key->filename);
+  else if (progname)
+    fprintf(stderr, "%s: ", progname);
+  fputs(msg, stderr);
+  fputc('\n', stderr);
+}
+
+/* Display a critical error */
+void rs_error(const RSKey* key, const RSProgram* prgm, const char* fmt, ...)
+{
+  char msg[512];
+  va_list ap;
+
+  va_start(ap, fmt);
+  strcpy(msg, "error: ");
+  rs_vsnprintf(msg + 7, sizeof(msg) - 7, fmt, ap);
+  va_end(ap);
+
+  if (errorfunc)
+    (*errorfunc)(key, prgm, msg, errorfuncdata);
+  else
+    print_message(key, prgm, msg);
+}
+
+/* Display a warning message */
+void rs_warning(const RSKey* key, const RSProgram* prgm, const char* fmt, ...)
+{
+  char msg[512];
+  va_list ap;
+
+  va_start(ap, fmt);
+  strcpy(msg, "warning: ");
+  rs_vsnprintf(msg + 9, sizeof(msg) - 9, fmt, ap);
+  va_end(ap);
+
+  if (errorfunc)
+    (*errorfunc)(key, prgm, msg, errorfuncdata);
+  else
+    print_message(key, prgm, msg);
+}
+
+/* Display an informative message */
+void rs_message(int level, const RSKey* key, const RSProgram* prgm,
+		const char* fmt, ...)
+{
+  char msg[512];
+  va_list ap;
+
+  if (level > verbose)
+    return;
+
+  va_start(ap, fmt);
+  rs_vsnprintf(msg, sizeof(msg), fmt, ap);
+  va_end(ap);
+
+  if (messagefunc)
+    (*messagefunc)(key, prgm, msg, messagefuncdata);
+  else
+    print_message(key, prgm, msg);
+}
diff --git a/tool/rabbitsign-src/graphlink.c b/tool/rabbitsign-src/graphlink.c
new file mode 100644
index 0000000..06e6e3f
--- /dev/null
+++ b/tool/rabbitsign-src/graphlink.c
@@ -0,0 +1,118 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef TM_IN_SYS_TIME
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+#define BCD(x) ((x) + 6 * ((x)/10))
+
+/*
+ * Write a TIFL header to a file.
+ */
+int rs_write_tifl_header(FILE* outfile,    /* file to write to */
+			 int is_hex,	    /* is file in hex format? */
+			 int major,	    /* major version # */
+			 int minor,	    /* minor version # */
+			 int month,	    /* current month */
+			 int day,	    /* current day */
+			 int year,	    /* current year */
+			 const char* name, /* name of program */
+			 int calctype,	    /* calculator type */
+			 int datatype,	    /* data type */
+			 unsigned long filesize) /* size of data */
+{
+  unsigned char buf[78];
+  time_t t;
+  struct tm* tm;
+
+  memset(buf, 0, 78);
+
+  strcpy((char*) buf, "**TIFL**");
+
+  buf[8] = major;
+  buf[9] = minor;
+
+  if (is_hex) {
+    buf[10] = 0x01;
+    buf[11] = 0x88;
+  }
+  else {
+    buf[10] = 0;
+    buf[11] = 0;
+  }
+
+  if (!month && !day && !year) {
+    time(&t);
+    tm = localtime(&t);
+    month = tm->tm_mon + 1;
+    day = tm->tm_mday;
+    year = tm->tm_year + 1900;
+  }
+
+  buf[12] = BCD(month);
+  buf[13] = BCD(day);
+  buf[14] = BCD(year / 100);
+  buf[15] = BCD(year % 100);
+
+  buf[16] = strlen(name);
+  if (buf[16] > 8)
+    buf[16] = 8;
+
+  strncpy((char*) buf + 17, name, 8);
+
+  buf[48] = calctype;
+  buf[49] = datatype;
+
+  buf[74] = filesize & 0xff;
+  buf[75] = (filesize >> 8) & 0xff;
+  buf[76] = (filesize >> 16) & 0xff;
+  buf[77] = (filesize >> 24) & 0xff;
+
+  if (fwrite(buf, 1, 78, outfile) != 78) {
+    rs_error(NULL, NULL, "file I/O error");
+    return RS_ERR_FILE_IO;
+  }
+
+  return RS_SUCCESS;
+}
+
diff --git a/tool/rabbitsign-src/header.c b/tool/rabbitsign-src/header.c
new file mode 100644
index 0000000..36bbe5b
--- /dev/null
+++ b/tool/rabbitsign-src/header.c
@@ -0,0 +1,169 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Get length of a header field.
+ */
+void rs_get_field_size (const unsigned char* data, /* Data */
+			unsigned long* fieldstart, /* Offset to start
+						      of field
+						      contents */
+			unsigned long* fieldsize)  /* Length of field
+						      contents */
+{
+  switch (data[1] & 0x0f) {
+  case 0x0D:
+    if (fieldstart) *fieldstart = 3;
+    if (fieldsize) *fieldsize = data[2];
+    break;
+
+  case 0x0E:
+    if (fieldstart) *fieldstart = 4;
+    if (fieldsize) *fieldsize = ((data[2] << 8) | data[3]);
+    break;
+
+  case 0x0F:
+    if (fieldstart) *fieldstart = 6;
+    if (fieldsize) {
+      *fieldsize = (((unsigned long) data[2] << 24)
+		    | ((unsigned long) data[3] << 16)
+		    | ((unsigned long) data[4] << 8)
+		    | (unsigned long) data[5]);
+    }
+    break;
+
+  default:
+    if (fieldstart) *fieldstart = 2;
+    if (fieldsize) *fieldsize = (data[1] & 0x0f);
+    break;
+  }
+}
+
+/* Set length of a header field. */
+int rs_set_field_size (unsigned char* data,
+		       unsigned long fieldsize)
+{
+  switch (data[1] & 0x0f) {
+  case 0x0D:
+    if (fieldsize > 0xff)
+      return -1;
+    data[2] = fieldsize;
+    return 0;
+
+  case 0x0E:
+    if (fieldsize > 0xfffful)
+      return -1;
+    data[2] = (fieldsize >> 8) & 0xff;
+    data[3] = fieldsize & 0xff;
+    return 0;
+
+  case 0x0F:
+    if (fieldsize > 0xfffffffful)
+      return -1;
+    data[2] = (fieldsize >> 24) & 0xff;
+    data[3] = (fieldsize >> 16) & 0xff;
+    data[4] = (fieldsize >> 8) & 0xff;
+    data[5] = fieldsize & 0xff;
+    return 0;
+
+  default:
+    if (fieldsize > 0x0C)
+      return -1;
+    data[1] = (data[1] & 0xf0) | fieldsize;
+    return 0;
+  }
+}
+
+/*
+ * Find a given header field in the data.
+ */
+int rs_find_app_field(unsigned int type,         /* Type of field to
+						    search for (e.g.,
+						    0x8040 to search
+						    for the name) */
+		      const unsigned char* data, /* Data to search */
+		      unsigned long length,      /* Maximum length of
+						    data to search */
+		      unsigned long* fieldhead,	 /* Offset to field
+						    type bytes, if
+						    found */
+		      unsigned long* fieldstart, /* Offset to start of
+						    field contents, if
+						    found */
+		      unsigned long* fieldsize)  /* Length of field
+						    contents, if
+						    found */
+{
+  unsigned char b1, b2;
+  unsigned long pos = 0;
+  unsigned long fstart, fsize;
+
+  b1 = ((type >> 8) & 0xff);
+  b2 = (type & 0xf0);
+
+  while (pos < length) {
+    if (data[pos] == b1 && (data[pos + 1] & 0xf0) == b2) {
+      rs_get_field_size(data + pos, &fstart, fieldsize);
+      if (fieldhead) *fieldhead = pos;
+      if (fieldstart) *fieldstart = pos + fstart;
+      return 0;
+    }
+
+    rs_get_field_size(data + pos, &fstart, &fsize);
+    pos += fstart + fsize;
+  }
+
+  return -1;
+}
+
+/*
+ * Get value of a numeric header field.
+ *
+ * Return 0 if field is not found, or if its contents are longer than
+ * 4 bytes.
+ */
+unsigned long rs_get_numeric_field (unsigned int type,
+				    const unsigned char* data,
+				    unsigned long length)
+{
+  unsigned long fstart, fsize, value;
+
+  if (rs_find_app_field(type, data, length, NULL, &fstart, &fsize))
+    return 0;
+
+  if (fsize > 4)
+    return 0;
+
+  value = 0;
+  while (fsize > 0) {
+    value <<= 8;
+    value |= data[fstart];
+    fstart++;
+    fsize--;
+  }
+  return value;
+}
diff --git a/tool/rabbitsign-src/input.c b/tool/rabbitsign-src/input.c
new file mode 100644
index 0000000..2f0ce37
--- /dev/null
+++ b/tool/rabbitsign-src/input.c
@@ -0,0 +1,520 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Determine the type of an unknown program, if possible.
+ */
+static void guess_type(RSProgram* prgm, int is_hex)
+{
+  const unsigned char* hdr;
+  unsigned long hdrstart, hdrsize, keyid, fieldstart, fieldsize;
+
+  /* Z80 OSes have a detached program header */
+
+  if (prgm->header_length > 2 && prgm->header[0] == 0x80) {
+    rs_get_field_size(prgm->header, &hdrstart, NULL);
+    hdr = prgm->header + hdrstart;
+    hdrsize = prgm->header_length - hdrstart;
+    keyid = rs_get_numeric_field(0x8010, hdr, hdrsize);
+
+    prgm->datatype = RS_DATA_OS;
+
+    if ((keyid & 0xff) == 0x02) {
+      prgm->calctype = RS_CALC_TI73;
+    }
+    else {
+      prgm->calctype = RS_CALC_TI83P;
+    }
+  }
+  else if (prgm->length > 2) {
+    rs_get_field_size(prgm->data, &hdrstart, NULL);
+    hdr = prgm->data + hdrstart;
+    hdrsize = prgm->length - hdrstart;
+    if (hdrsize > 128)
+      hdrsize = 128;
+
+    /* Z80 apps and 68k OSes have field type 0x8000 */
+
+    if (prgm->data[0] == 0x80 && (prgm->data[1] & 0xf0) == 0x00) {
+      keyid = rs_get_numeric_field(0x8010, hdr, hdrsize);
+
+      switch (keyid & 0xff) {
+      case 0x02:
+	prgm->calctype = RS_CALC_TI73;
+	prgm->datatype = RS_DATA_APP;
+	break;
+
+      case 0x04:
+      case 0x0A:
+	prgm->calctype = RS_CALC_TI83P;
+	prgm->datatype = RS_DATA_APP;
+	break;
+
+      case 0x03:
+      case 0x09:
+	prgm->calctype = RS_CALC_TI89;
+	prgm->datatype = RS_DATA_OS;
+	break;
+
+      case 0x01:
+      case 0x08:
+	prgm->calctype = RS_CALC_TI92P;
+	prgm->datatype = RS_DATA_OS;
+	break;
+
+      default:
+	if (is_hex) {
+	  prgm->calctype = RS_CALC_TI83P;
+	  prgm->datatype = RS_DATA_APP;
+	}
+	break;
+      }
+    }
+
+    /* 68k apps have field type 0x8100 */
+
+    else if (prgm->data[0] == 0x81 && (prgm->data[1] & 0xf0) == 0x00) {
+      keyid = rs_get_numeric_field(0x8110, hdr, hdrsize);
+      prgm->datatype = RS_DATA_APP;
+
+      switch (keyid & 0xff) {
+      case 0x03:
+      case 0x09:
+	prgm->calctype = RS_CALC_TI89;
+	break;
+
+      case 0x01:
+      case 0x08:
+	prgm->calctype = RS_CALC_TI92P;
+	break;
+      }
+    }
+
+    /* Certificates have field type 0x0300 */
+
+    else if (prgm->data[0] == 0x03 && (prgm->data[1] & 0xf0) == 0x00) {
+      prgm->datatype = RS_DATA_CERT;
+
+      if (!rs_find_app_field(0x0400, hdr, hdrsize,
+			     NULL, &fieldstart, &fieldsize)
+	  && fieldsize >= 1) {
+	switch (hdr[fieldstart]) {
+	case 0x02:
+	  prgm->calctype = RS_CALC_TI73;
+	  break;
+
+	case 0x04:
+	case 0x0A:
+	  prgm->calctype = RS_CALC_TI83P;
+	  break;
+
+	case 0x03:
+	case 0x09:
+	  prgm->calctype = RS_CALC_TI89;
+	  break;
+
+	case 0x01:
+	case 0x08:
+	  prgm->calctype = RS_CALC_TI92P;
+	  break;
+	}
+      }
+    }
+  }
+}
+
+/*
+ * Read the contents of a binary file into an RSProgram.
+ */
+static int read_file_binary(RSProgram* prgm,
+			    FILE* f,
+			    unsigned long filesize)
+{
+  unsigned char buf[1024];
+  size_t count;
+
+  if (filesize) {
+    while (filesize > 0) {
+      if (filesize > 1024)
+	count = fread(buf, 1, 1024, f);
+      else
+	count = fread(buf, 1, filesize, f);
+
+      if (count > 0)
+	rs_program_append_data(prgm, buf, count);
+      else
+	break;
+
+      filesize -= count;
+    }
+  }
+  else {
+    do {
+      count = fread(buf, 1, 1024, f);
+      if (count > 0) {
+	rs_program_append_data(prgm, buf, count);
+      }
+    } while (count > 0);
+  }
+
+  if (!prgm->calctype || !prgm->datatype)
+    guess_type(prgm, 0);
+  return RS_SUCCESS;
+}
+
+/*
+ * Find a given page in the list of page numbers (or add it to the
+ * end.)
+ */
+static int getpageidx(RSProgram* prgm,	    /* program */
+		      unsigned int pagenum) /* page number */
+{
+  int i;
+  unsigned int* array;
+
+  for (i = 0; i < prgm->npagenums; i++)
+    if (prgm->pagenums[i] == pagenum)
+      return i;
+
+  if (!(array = rs_realloc(prgm->pagenums, (i + 1) * sizeof(unsigned int))))
+    return 0;
+  prgm->pagenums = array;
+  prgm->npagenums = i + 1;
+  prgm->pagenums[i] = pagenum;
+  return i;
+}
+
+/*
+ * Read an Intel/TI hex file into an RSProgram.
+ *
+ * Note that the first ':' is assumed to have been read already.
+ */
+static int read_file_hex(RSProgram* prgm,
+			 FILE* f,
+			 unsigned int flags)
+{
+  int c;
+  unsigned int nbytes, addr, rectype, sum, i, b, value;
+  unsigned int pagenum = 0, pageidx = 0, lastaddr = 0;
+  unsigned long offset;
+  unsigned char data[256];
+  unsigned char* sigp;
+  int nparts = 0;
+  int possibly_os_header = 1;
+
+  rs_free(prgm->pagenums);
+  if (!(prgm->pagenums = rs_malloc(sizeof(unsigned int))))
+    return RS_ERR_OUT_OF_MEMORY;
+  prgm->pagenums[0] = 0;
+  prgm->npagenums = 1;
+
+  while (!feof(f) && !ferror(f)) {
+    if (3 > fscanf(f, "%2X%4X%2X", &nbytes, &addr, &rectype)) {
+      rs_error(NULL, prgm, "invalid hex data (following %X:%X)",
+	       pagenum, lastaddr);
+      return RS_ERR_HEX_SYNTAX;
+    }
+
+    /* Read data bytes */
+
+    sum = nbytes + addr + (addr >> 8) + rectype;
+    value = 0;
+    for (i = 0; i < nbytes; i++) {
+      if (1 > fscanf(f, "%2X", &b)) {
+	rs_error(NULL, prgm, "invalid hex data (at %X:%X)",
+		 pagenum, addr);
+	return RS_ERR_HEX_SYNTAX;
+      }
+      data[i] = b;
+      sum += b;
+      value = (value << 8) + b;
+    }
+
+    /* Read checksum */
+
+    c = fgetc(f);
+    if (c == 'X') {
+      c = fgetc(f);
+      if (c != 'X') {
+	rs_error(NULL, prgm, "invalid hex data (at %X:%X)",
+		 pagenum, addr);
+	return RS_ERR_HEX_SYNTAX;
+      }
+    }
+    else {
+      ungetc(c, f);
+      if (1 > fscanf(f, "%2X", &b)) {
+	rs_error(NULL, prgm, "invalid hex data (at %X:%X)",
+		 pagenum, addr);
+	return RS_ERR_HEX_SYNTAX;
+      }
+      sum += b;
+      if (sum & 0xff)
+	rs_warning(NULL, prgm, "incorrect checksum (at %X:%X)",
+		   pagenum, addr);
+    }
+
+    if (rectype == 0 && nbytes > 0) {
+      /* Record type 0: program data */
+
+      if (addr & 0xff00)
+	possibly_os_header = 0;
+
+      addr &= 0x3fff;
+
+      /* if program does not start at addr 0000 (or 4000), assume
+	 unsorted */
+      if (addr && prgm->length == 0)
+	flags &= ~RS_INPUT_SORTED;
+
+      if ((flags & RS_INPUT_SORTED) && !addr && lastaddr) {
+	/* automatically switch to next page */
+	pagenum++;
+	pageidx = getpageidx(prgm, pagenum);
+	if (!pageidx)
+	  return RS_ERR_OUT_OF_MEMORY;
+      }
+      else if (addr < lastaddr)
+	flags &= ~RS_INPUT_SORTED;
+
+      if (nparts == 2 && prgm->header_length) {
+	/* Reading an OS signature */
+	if (addr + nbytes > prgm->signature_length) {
+	  if (!(sigp = rs_realloc(prgm->signature, addr + nbytes)))
+	    return RS_ERR_OUT_OF_MEMORY;
+
+	  prgm->signature = sigp;
+	  if (addr > prgm->signature_length) {
+	    memset(prgm->signature + prgm->signature_length, 0xff,
+		   addr - prgm->signature_length);
+	  }
+	  prgm->signature_length = addr + nbytes;
+	}
+	memcpy(prgm->signature + addr, data, nbytes);
+      }
+      else {
+	/* Reading normal program data */
+	offset = ((unsigned long) pageidx << 14) | addr;
+	if (offset + nbytes <= prgm->length) {
+	  memcpy(prgm->data + offset, data, nbytes);
+	}
+	else {
+	  rs_program_set_length(prgm, offset);
+	  rs_program_append_data(prgm, data, nbytes);
+	}
+      }
+
+      lastaddr = addr;
+    }
+    else if (rectype == 1) {
+      /* Record type 1: "end of file" */
+      nparts++;
+      if (nparts == 3 && prgm->header_length)
+	break;
+    }
+    else if (rectype == 2 || rectype == 4) {
+      /* Record type 2 or 4: extended address */
+      possibly_os_header = 0;
+      flags &= ~RS_INPUT_SORTED;
+      if (nparts < 2) {
+	pagenum = value;
+	pageidx = getpageidx(prgm, pagenum);
+	if (pagenum && !pageidx)
+	  return RS_ERR_OUT_OF_MEMORY;
+      }
+    }
+
+    do {
+      c = fgetc(f);
+    } while (c == '\n' || c == '\r' || c == ' ');
+
+    if (c == EOF)
+      break;
+    else if (c != ':') {
+      if (rectype == 1)
+	break;
+      else {
+	rs_error(NULL, prgm, "invalid hex data (following %X:%X)",
+		 pagenum, lastaddr);
+	return RS_ERR_HEX_SYNTAX;
+      }
+    }
+
+    if (rectype == 1 && nparts == 1 && prgm->length > 0
+	&& possibly_os_header) {
+      /* Just finished reading OS header */
+      flags &= ~RS_INPUT_SORTED;
+      pagenum = pageidx = 0;
+
+      rs_free(prgm->header);
+      if (!(prgm->header = rs_malloc(prgm->length)))
+	return RS_ERR_OUT_OF_MEMORY;
+
+      memcpy(prgm->header, prgm->data, prgm->length);
+      prgm->header_length = prgm->length;
+      prgm->length = 0;
+      possibly_os_header = 0;
+    }
+  }
+
+  if (!prgm->calctype || !prgm->datatype)
+    guess_type(prgm, 1);
+  return RS_SUCCESS;
+}
+
+/*
+ * Check if calc/data type matches expected type (or any recognized
+ * type, if none was specified.)
+ */
+static int check_tifl_type(int calctype,
+			   int datatype,
+			   int calctype_expected,
+			   int datatype_expected)
+{
+  if (calctype_expected) {
+    if (calctype_expected != calctype)
+      return 0;
+  }
+  else {
+    if (calctype != RS_CALC_TI73 && calctype != RS_CALC_TI83P
+	&& calctype != RS_CALC_TI89 && calctype != RS_CALC_TI92P)
+      return 0;
+  }
+
+  if (datatype_expected) {
+    if (datatype_expected != datatype)
+      return 0;
+  }
+  else {
+    if (datatype != RS_DATA_APP && datatype != RS_DATA_OS)
+      return 0;
+  }
+
+  return 1;
+}
+
+/*
+ * Read program contents from a file.
+ *
+ * Various file formats are supported:
+ *
+ * - Raw binary (must begin with the value 0x80 or 0x81)
+ * - Plain Intel/TI hex
+ * - Binary TIFL (89k, 89u, ...)
+ * - Hex TIFL (8xk, 8xu, ...)
+ *
+ * Note: on platforms where it matters, all input files must be opened
+ * in "binary" mode.
+ */
+int rs_read_program_file(RSProgram* prgm,    /* program */
+			 FILE* f,	     /* file */
+			 const char* fname,  /* file name */
+			 unsigned int flags) /* option flags */
+{
+  int c;
+  unsigned char tiflbuf[78];
+  unsigned long tiflsize, i;
+  int e;
+
+  rs_program_set_length(prgm, 0);
+  prgm->header_length = 0;
+  prgm->signature_length = 0;
+  prgm->npagenums = 0;
+
+  rs_free(prgm->filename);
+  prgm->filename = rs_strdup(fname);
+  if (fname && !prgm->filename)
+    return RS_ERR_OUT_OF_MEMORY;
+
+  if (flags & RS_INPUT_BINARY)
+    return read_file_binary(prgm, f, 0);
+
+  c = fgetc(f);
+  if (c == 0x80 || c == 0x81) {
+    tiflbuf[0] = c;
+    if ((e = rs_program_append_data(prgm, tiflbuf, 1)))
+      return e;
+    return read_file_binary(prgm, f, 0);
+  }
+
+  while (!feof(f) && !ferror(f)) {
+    if (c == ':') {
+      return read_file_hex(prgm, f, flags);
+    }
+    else if (c == '*') {
+      if (fread(tiflbuf, 1, 78, f) < 78
+	  || strncmp((char*) tiflbuf, "*TIFL**", 7)) {
+	rs_error(NULL, prgm, "unknown input file format");
+	return RS_ERR_UNKNOWN_FILE_FORMAT;
+      }
+
+      tiflsize = ((unsigned long) tiflbuf[73]
+		  | ((unsigned long) tiflbuf[74] << 8)
+		  | ((unsigned long) tiflbuf[75] << 16)
+		  | ((unsigned long) tiflbuf[76] << 24));
+
+      if (check_tifl_type(tiflbuf[47], tiflbuf[48],
+			  prgm->calctype, prgm->datatype)) {
+	prgm->calctype = tiflbuf[47];
+	prgm->datatype = tiflbuf[48];
+
+	if (tiflbuf[77] == ':')
+	  return read_file_hex(prgm, f, 0);
+	else {
+	  if ((e = rs_program_append_data(prgm, tiflbuf + 77, 1)))
+	    return e;
+	  return read_file_binary(prgm, f, tiflsize ? tiflsize - 1 : 0);
+	}
+      }
+      else {
+	/* extra data (license, certificate, etc.) -- ignore */
+	if (fseek(f, tiflsize - 1, SEEK_CUR)) {
+	  for (i = 0; i < tiflsize - 1; i++) {
+	    if (fgetc(f) == EOF) {
+	      rs_error(NULL, prgm, "unexpected EOF");
+	      return RS_ERR_UNKNOWN_FILE_FORMAT;
+	    }
+	  }
+	}
+      }
+    }
+
+    c = fgetc(f);
+  }
+
+  rs_error(NULL, prgm, "unknown input file format");
+  return RS_ERR_UNKNOWN_FILE_FORMAT;
+}
+
diff --git a/tool/rabbitsign-src/internal.h b/tool/rabbitsign-src/internal.h
new file mode 100644
index 0000000..d3fc1b5
--- /dev/null
+++ b/tool/rabbitsign-src/internal.h
@@ -0,0 +1,111 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __RABBITSIGN_INTERNAL_H__
+#define __RABBITSIGN_INTERNAL_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**** Memory management (mem.c) ****/
+
+#define rs_malloc(nnn) rs_realloc(0, (nnn))
+#define rs_free(ppp) rs_realloc((ppp), 0)
+void* rs_realloc (void* ptr, unsigned long count) RS_ATTR_MALLOC;
+char* rs_strdup (const char* str) RS_ATTR_MALLOC;
+
+
+/**** Rabin signature functions (rabin.c) ****/
+
+/* Compute a Rabin signature and the useful value of f. */
+RSStatus rs_sign_rabin (mpz_t res, int* f, const mpz_t hash,
+			int rootnum, RSKey* key);
+
+/* Check that the given Rabin signature is valid. */
+RSStatus rs_validate_rabin (const mpz_t sig, int f, const mpz_t hash,
+			    const RSKey* key);
+
+
+/**** RSA signature functions (rsa.c) ****/
+
+/* Compute an RSA signature. */
+RSStatus rs_sign_rsa (mpz_t res, const mpz_t hash, RSKey* key);
+
+/* Check that the given RSA signature is valid. */
+RSStatus rs_validate_rsa (const mpz_t sig, const mpz_t hash,
+			  const RSKey* key);
+
+
+/**** TIFL file output (graphlink.c) ****/
+
+/* Write TIFL header to a file. */
+RSStatus rs_write_tifl_header (FILE* f, int is_hex, int major, int minor,
+			       int month, int day, int year,
+			       const char* name, int calctype, int datatype,
+			       unsigned long filesize);
+
+
+/**** Type <-> string conversions (typestr.c) ****/
+
+/* Get default file suffix for a given calc/data type. */
+const char* rs_type_to_suffix (RSCalcType calctype, RSDataType datatype,
+			       int hexonly);
+
+/* Get implied calc/data type for a given file suffix. */
+int rs_suffix_to_type (const char* suff, RSCalcType* calctype,
+		       RSDataType* datatype);
+
+/* Get a human-readable description of a calculator type. */
+const char* rs_calc_type_to_string (RSCalcType calctype);
+
+/* Get a human-readable description of a data type. */
+const char* rs_data_type_to_string (RSDataType datatype);
+
+
+/**** Command line option parsing (cmdline.c) ****/
+
+#define RS_CMDLINE_FINISHED 0
+#define RS_CMDLINE_FILENAME '#'
+#define RS_CMDLINE_HELP '!'
+#define RS_CMDLINE_VERSION '@'
+#define RS_CMDLINE_ERROR '?'
+
+int rs_parse_cmdline(int argc, char** argv, const char* optstring,
+		     int* i, int* j, const char** arg);
+
+
+/**** Error/message logging (error.c) ****/
+
+/* Display an error message */
+void rs_error (const RSKey* key, const RSProgram* prgm,
+	       const char* fmt, ...) RS_ATTR_PRINTF(3,4);
+
+/* Display a warning message */
+void rs_warning (const RSKey* key, const RSProgram* prgm,
+		 const char* fmt, ...) RS_ATTR_PRINTF(3,4);
+
+/* Display an informational message */
+void rs_message (int level, const RSKey* key, const RSProgram* prgm,
+		 const char* fmt, ...) /*RS_ATTR_PRINTF(4,5)*/;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __RABBITSIGN_INTERNAL_H__ */
diff --git a/tool/rabbitsign-src/keys.c b/tool/rabbitsign-src/keys.c
new file mode 100644
index 0000000..d92f36b
--- /dev/null
+++ b/tool/rabbitsign-src/keys.c
@@ -0,0 +1,234 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Create a new key.
+ */
+RSKey* rs_key_new()
+{
+  RSKey* key = rs_malloc(sizeof(RSKey));
+
+  if (!key)
+    return NULL;
+
+  key->filename = NULL;
+  key->id = 0;
+  mpz_init(key->n);
+  mpz_init(key->p);
+  mpz_init(key->q);
+  mpz_init(key->qinv);
+  mpz_init(key->d);
+
+  return key;
+}
+
+/*
+ * Free a key.
+ */
+void rs_key_free(RSKey* key)
+{
+  if (!key)
+    return;
+
+  rs_free(key->filename);
+  mpz_clear(key->n);
+  mpz_clear(key->p);
+  mpz_clear(key->q);
+  mpz_clear(key->qinv);
+  mpz_clear(key->d);
+  rs_free(key);
+}
+
+/*
+ * Parse a number written in TI's hexadecimal key format.
+ */
+static int parse_value(mpz_t dest,	/* mpz to store result */
+		       const char* str) /* string to parse */
+{
+  unsigned int count, b, i;
+  int n;
+  unsigned char buf[256];
+
+  if (1 > sscanf(str, "%2X%n", &count, &n) || n != 2
+      || (count * 2 + 2) > strlen(str))
+    return 1;
+
+  for (i = 0; i < count; i++) {
+    if (1 > sscanf(str + 2 + 2 * i, "%2X%n", &b, &n) || n != 2)
+      return 1;
+    buf[i] = b;
+  }
+
+  mpz_import(dest, i, -1, 1, 0, 0, buf);
+  return 0;
+}
+
+/*
+ * Read key from a file.
+ *
+ * Two formats of key file are supported:
+ *
+ * "Rabin" style (the type used by the TI-83 Plus SDK) consists of
+ * three lines: the public key (n) followed by its two factors (p and
+ * q.)
+ *
+ * "RSA" style (the type used by the TI-89/92 Plus SDK) also consists
+ * of three lines: the key ID, the public key (n), and the signing
+ * exponent (d).
+ *
+ * In either case, if we are only interested in validating signatures,
+ * the private key may be omitted.
+ *
+ * Note that "Rabin" style key files can be used to generate RSA
+ * signatures, but not vice versa.
+ */
+int rs_read_key_file(RSKey* key,        /* key structure */
+		     FILE* f,	        /* file to read */
+		     const char* fname, /* file name */
+		     int verify)	/* 1 = check key validity */
+{
+  char buf[1024];
+  mpz_t tmp;
+
+  rs_free(key->filename);
+  key->filename = rs_strdup(fname);
+  if (fname && !key->filename)
+    return RS_ERR_OUT_OF_MEMORY;
+
+  if (!fgets(buf, sizeof(buf), f)) {
+    rs_error(key, NULL, "invalid key file syntax");
+    return RS_ERR_KEY_SYNTAX;
+  }
+
+  if (strlen(buf) < 11) {
+    if (1 > sscanf(buf, "%lX", &key->id)) {
+      rs_error(key, NULL, "invalid key file syntax");
+      return RS_ERR_KEY_SYNTAX;
+    }
+
+    if (!fgets(buf, sizeof(buf), f)
+	|| parse_value(key->n, buf)) {
+      rs_error(key, NULL, "invalid key file syntax");
+      return RS_ERR_KEY_SYNTAX;
+    }
+
+    if (!fgets(buf, sizeof(buf), f)
+	|| parse_value(key->d, buf))
+      mpz_set_ui(key->d, 0);
+    else if (verify) {
+      /* We can't truly verify the key without factoring n (which is
+	 possible, given d, but would take a bit of work.)  Instead,
+	 test the key by performing a single RSA encryption and
+	 decryption. */
+      mpz_init(tmp);
+      mpz_set_ui(tmp, 17);
+      mpz_powm(tmp, tmp, tmp, key->n);
+      mpz_powm(tmp, tmp, key->d, key->n);
+      if (mpz_cmp_ui(tmp, 17)) {
+	mpz_clear(tmp);
+	rs_error(key, NULL, "private key incorrect (de != 1 mod phi(n))");
+	return RS_ERR_INVALID_KEY;
+      }
+      mpz_clear(tmp);
+    }
+
+    mpz_set_ui(key->p, 0);
+    mpz_set_ui(key->q, 0);
+    mpz_set_ui(key->qinv, 0);
+  }
+  else {
+    if (parse_value(key->n, buf)) {
+      rs_error(key, NULL, "invalid key file");
+      return RS_ERR_KEY_SYNTAX;
+    }
+
+    if (!fgets(buf, sizeof(buf), f)
+	|| parse_value(key->p, buf)
+	|| !fgets(buf, sizeof(buf), f)
+	|| parse_value(key->q, buf)) {
+      mpz_set_ui(key->p, 0);
+      mpz_set_ui(key->q, 0);
+    }
+    else if (verify) {
+      /* Verify that p * q = n (of course, that doesn't guarantee that
+	 these are the only factors of n.) */
+      mpz_init(tmp);
+      mpz_mul(tmp, key->p, key->q);
+      if (mpz_cmp(tmp, key->n)) {
+	mpz_clear(tmp);
+	rs_error(key, NULL, "private key incorrect (pq != n)");
+	return RS_ERR_INVALID_KEY;
+      }
+      mpz_clear(tmp);
+    }
+
+    mpz_set_ui(key->qinv, 0);
+    mpz_set_ui(key->d, 0);
+    key->id = 0;
+  }
+
+  if (mpz_sgn(key->p) && mpz_sgn(key->q)) {
+    rs_message(2, key, NULL, "Loaded Rabin/RSA private key:");
+    rs_message(2, key, NULL, " n = %ZX", key->n);
+    rs_message(2, key, NULL, " p = %ZX", key->p);
+    rs_message(2, key, NULL, " q = %ZX", key->q);
+  }
+  else if (mpz_sgn(key->d)) {
+    rs_message(2, key, NULL, "Loaded RSA private key:");
+    rs_message(2, key, NULL, " n = %ZX", key->n);
+    rs_message(2, key, NULL, " d = %ZX", key->d);
+  }
+  else {
+    rs_message(2, key, NULL, "Loaded public key:");
+    rs_message(2, key, NULL, " n = %ZX", key->n);
+  }
+
+  return RS_SUCCESS;
+}
+
+/*
+ * Parse a number written in TI's hexadecimal key format.
+ */
+int rs_parse_key_value(mpz_t dest,	/* mpz to store result */
+		       const char* str) /* string to parse */
+{
+  if (parse_value(dest, str)) {
+    rs_error(NULL, NULL, "invalid key value syntax");
+    return RS_ERR_KEY_SYNTAX;
+  }
+  else {
+    return RS_SUCCESS;
+  }
+}
diff --git a/tool/rabbitsign-src/md5.c b/tool/rabbitsign-src/md5.c
new file mode 100644
index 0000000..d742c54
--- /dev/null
+++ b/tool/rabbitsign-src/md5.c
@@ -0,0 +1,419 @@
+/* md5.c - Functions to compute MD5 message digest of files or memory blocks
+   according to the definition of MD5 in RFC 1321 from April 1992.
+   Copyright (C) 1995, 1996 Free Software Foundation, Inc.
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to bug-glibc@prep.ai.mit.edu.
+
+   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, or (at your option) any
+   later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#if STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+# include <string.h>
+#else
+# ifndef HAVE_MEMCPY
+#  define memcpy(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#include "md5.h"
+
+#ifdef _LIBC
+# include <endian.h>
+# if __BYTE_ORDER == __BIG_ENDIAN
+#  define WORDS_BIGENDIAN 1
+# endif
+#endif
+
+#ifdef WORDS_BIGENDIAN
+# define SWAP(n)							\
+    (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+#else
+# define SWAP(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+   64-byte boundary.  (RFC 1321, 3.1: Step 1)  */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ...  */ };
+
+
+/* Initialize structure containing state of computation.
+   (RFC 1321, 3.3: Step 3)  */
+void
+md5_init_ctx (ctx)
+     struct md5_ctx *ctx;
+{
+  ctx->A = 0x67452301;
+  ctx->B = 0xefcdab89;
+  ctx->C = 0x98badcfe;
+  ctx->D = 0x10325476;
+
+  ctx->total[0] = ctx->total[1] = 0;
+  ctx->buflen = 0;
+}
+
+/* Put result from CTX in first 16 bytes following RESBUF.  The result
+   must be in little endian byte order.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+void *
+md5_read_ctx (ctx, resbuf)
+     const struct md5_ctx *ctx;
+     void *resbuf;
+{
+  ((md5_uint32 *) resbuf)[0] = SWAP (ctx->A);
+  ((md5_uint32 *) resbuf)[1] = SWAP (ctx->B);
+  ((md5_uint32 *) resbuf)[2] = SWAP (ctx->C);
+  ((md5_uint32 *) resbuf)[3] = SWAP (ctx->D);
+
+  return resbuf;
+}
+
+/* Process the remaining bytes in the internal buffer and the usual
+   prolog according to the standard and write the result to RESBUF.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+void *
+md5_finish_ctx (ctx, resbuf)
+     struct md5_ctx *ctx;
+     void *resbuf;
+{
+  /* Take yet unprocessed bytes into account.  */
+  md5_uint32 bytes = ctx->buflen;
+  size_t pad;
+
+  /* Now count remaining bytes.  */
+  ctx->total[0] += bytes;
+  if (ctx->total[0] < bytes)
+    ++ctx->total[1];
+
+  pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+  memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+  /* Put the 64-bit file length in *bits* at the end of the buffer.  */
+  *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3);
+  *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) |
+							(ctx->total[0] >> 29));
+
+  /* Process last bytes.  */
+  md5_process_block (ctx->buffer, bytes + pad + 8, ctx);
+
+  return md5_read_ctx (ctx, resbuf);
+}
+
+/* Compute MD5 message digest for bytes read from STREAM.  The
+   resulting message digest number will be written into the 16 bytes
+   beginning at RESBLOCK.  */
+int
+md5_stream (stream, resblock)
+     FILE *stream;
+     void *resblock;
+{
+  /* Important: BLOCKSIZE must be a multiple of 64.  */
+#define BLOCKSIZE 4096
+  struct md5_ctx ctx;
+  char buffer[BLOCKSIZE + 72];
+  size_t sum;
+
+  /* Initialize the computation context.  */
+  md5_init_ctx (&ctx);
+
+  /* Iterate over full file contents.  */
+  while (1)
+    {
+      /* We read the file in blocks of BLOCKSIZE bytes.  One call of the
+	 computation function processes the whole buffer so that with the
+	 next round of the loop another block can be read.  */
+      size_t n;
+      sum = 0;
+
+      /* Read block.  Take care for partial reads.  */
+      do
+	{
+	  n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream);
+
+	  sum += n;
+	}
+      while (sum < BLOCKSIZE && n != 0);
+      if (n == 0 && ferror (stream))
+        return 1;
+
+      /* If end of file is reached, end the loop.  */
+      if (n == 0)
+	break;
+
+      /* Process buffer with BLOCKSIZE bytes.  Note that
+			BLOCKSIZE % 64 == 0
+       */
+      md5_process_block (buffer, BLOCKSIZE, &ctx);
+    }
+
+  /* Add the last bytes if necessary.  */
+  if (sum > 0)
+    md5_process_bytes (buffer, sum, &ctx);
+
+  /* Construct result in desired memory.  */
+  md5_finish_ctx (&ctx, resblock);
+  return 0;
+}
+
+/* Compute MD5 message digest for LEN bytes beginning at BUFFER.  The
+   result is always in little endian byte order, so that a byte-wise
+   output yields to the wanted ASCII representation of the message
+   digest.  */
+void *
+md5_buffer (buffer, len, resblock)
+     const char *buffer;
+     size_t len;
+     void *resblock;
+{
+  struct md5_ctx ctx;
+
+  /* Initialize the computation context.  */
+  md5_init_ctx (&ctx);
+
+  /* Process whole buffer but last len % 64 bytes.  */
+  md5_process_bytes (buffer, len, &ctx);
+
+  /* Put result in desired memory area.  */
+  return md5_finish_ctx (&ctx, resblock);
+}
+
+
+void
+md5_process_bytes (buffer, len, ctx)
+     const void *buffer;
+     size_t len;
+     struct md5_ctx *ctx;
+{
+  /* When we already have some bits in our internal buffer concatenate
+     both inputs first.  */
+  if (ctx->buflen != 0)
+    {
+      size_t left_over = ctx->buflen;
+      size_t add = 128 - left_over > len ? len : 128 - left_over;
+
+      memcpy (&ctx->buffer[left_over], buffer, add);
+      ctx->buflen += add;
+
+      if (left_over + add > 64)
+	{
+	  md5_process_block (ctx->buffer, (left_over + add) & ~63, ctx);
+	  /* The regions in the following copy operation cannot overlap.  */
+	  memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+		  (left_over + add) & 63);
+	  ctx->buflen = (left_over + add) & 63;
+	}
+
+      buffer = (const char *) buffer + add;
+      len -= add;
+    }
+
+  /* Process available complete blocks.  */
+  if (len > 64)
+    {
+      md5_process_block (buffer, len & ~63, ctx);
+      buffer = (const char *) buffer + (len & ~63);
+      len &= 63;
+    }
+
+  /* Move remaining bytes in internal buffer.  */
+  if (len > 0)
+    {
+      memcpy (ctx->buffer, buffer, len);
+      ctx->buflen = len;
+    }
+}
+
+
+/* These are the four functions used in the four steps of the MD5 algorithm
+   and defined in the RFC 1321.  The first function is a little bit optimized
+   (as found in Colin Plumbs public domain implementation).  */
+/* #define FF(b, c, d) ((b & c) | (~b & d)) */
+#define FF(b, c, d) (d ^ (b & (c ^ d)))
+#define FG(b, c, d) FF (d, b, c)
+#define FH(b, c, d) (b ^ c ^ d)
+#define FI(b, c, d) (c ^ (b | ~d))
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+   It is assumed that LEN % 64 == 0.  */
+
+void
+md5_process_block (buffer, len, ctx)
+     const void *buffer;
+     size_t len;
+     struct md5_ctx *ctx;
+{
+  md5_uint32 correct_words[16];
+  const md5_uint32 *words = buffer;
+  size_t nwords = len / sizeof (md5_uint32);
+  const md5_uint32 *endp = words + nwords;
+  md5_uint32 A = ctx->A;
+  md5_uint32 B = ctx->B;
+  md5_uint32 C = ctx->C;
+  md5_uint32 D = ctx->D;
+
+  /* First increment the byte count.  RFC 1321 specifies the possible
+     length of the file up to 2^64 bits.  Here we only compute the
+     number of bytes.  Do a double word increment.  */
+  ctx->total[0] += len;
+  if (ctx->total[0] < len)
+    ++ctx->total[1];
+
+  /* Process all bytes in the buffer with 64 bytes in each round of
+     the loop.  */
+  while (words < endp)
+    {
+      md5_uint32 *cwp = correct_words;
+      md5_uint32 A_save = A;
+      md5_uint32 B_save = B;
+      md5_uint32 C_save = C;
+      md5_uint32 D_save = D;
+
+      /* First round: using the given function, the context and a constant
+	 the next context is computed.  Because the algorithms processing
+	 unit is a 32-bit word and it is determined to work on words in
+	 little endian byte order we perhaps have to change the byte order
+	 before the computation.  To reduce the work for the next steps
+	 we store the swapped words in the array CORRECT_WORDS.  */
+
+#define OP(a, b, c, d, s, T)						\
+      do								\
+        {								\
+	  a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T;		\
+	  ++words;							\
+	  CYCLIC (a, s);						\
+	  a += b;							\
+        }								\
+      while (0)
+
+      /* It is unfortunate that C does not provide an operator for
+	 cyclic rotation.  Hope the C compiler is smart enough.  */
+#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
+
+      /* Before we start, one word to the strange constants.
+	 They are defined in RFC 1321 as
+
+	 T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
+       */
+
+      /* Round 1.  */
+      OP (A, B, C, D,  7, 0xd76aa478);
+      OP (D, A, B, C, 12, 0xe8c7b756);
+      OP (C, D, A, B, 17, 0x242070db);
+      OP (B, C, D, A, 22, 0xc1bdceee);
+      OP (A, B, C, D,  7, 0xf57c0faf);
+      OP (D, A, B, C, 12, 0x4787c62a);
+      OP (C, D, A, B, 17, 0xa8304613);
+      OP (B, C, D, A, 22, 0xfd469501);
+      OP (A, B, C, D,  7, 0x698098d8);
+      OP (D, A, B, C, 12, 0x8b44f7af);
+      OP (C, D, A, B, 17, 0xffff5bb1);
+      OP (B, C, D, A, 22, 0x895cd7be);
+      OP (A, B, C, D,  7, 0x6b901122);
+      OP (D, A, B, C, 12, 0xfd987193);
+      OP (C, D, A, B, 17, 0xa679438e);
+      OP (B, C, D, A, 22, 0x49b40821);
+
+      /* For the second to fourth round we have the possibly swapped words
+	 in CORRECT_WORDS.  Redefine the macro to take an additional first
+	 argument specifying the function to use.  */
+#undef OP
+#define OP(f, a, b, c, d, k, s, T)					\
+      do 								\
+	{								\
+	  a += f (b, c, d) + correct_words[k] + T;			\
+	  CYCLIC (a, s);						\
+	  a += b;							\
+	}								\
+      while (0)
+
+      /* Round 2.  */
+      OP (FG, A, B, C, D,  1,  5, 0xf61e2562);
+      OP (FG, D, A, B, C,  6,  9, 0xc040b340);
+      OP (FG, C, D, A, B, 11, 14, 0x265e5a51);
+      OP (FG, B, C, D, A,  0, 20, 0xe9b6c7aa);
+      OP (FG, A, B, C, D,  5,  5, 0xd62f105d);
+      OP (FG, D, A, B, C, 10,  9, 0x02441453);
+      OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);
+      OP (FG, B, C, D, A,  4, 20, 0xe7d3fbc8);
+      OP (FG, A, B, C, D,  9,  5, 0x21e1cde6);
+      OP (FG, D, A, B, C, 14,  9, 0xc33707d6);
+      OP (FG, C, D, A, B,  3, 14, 0xf4d50d87);
+      OP (FG, B, C, D, A,  8, 20, 0x455a14ed);
+      OP (FG, A, B, C, D, 13,  5, 0xa9e3e905);
+      OP (FG, D, A, B, C,  2,  9, 0xfcefa3f8);
+      OP (FG, C, D, A, B,  7, 14, 0x676f02d9);
+      OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
+
+      /* Round 3.  */
+      OP (FH, A, B, C, D,  5,  4, 0xfffa3942);
+      OP (FH, D, A, B, C,  8, 11, 0x8771f681);
+      OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);
+      OP (FH, B, C, D, A, 14, 23, 0xfde5380c);
+      OP (FH, A, B, C, D,  1,  4, 0xa4beea44);
+      OP (FH, D, A, B, C,  4, 11, 0x4bdecfa9);
+      OP (FH, C, D, A, B,  7, 16, 0xf6bb4b60);
+      OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);
+      OP (FH, A, B, C, D, 13,  4, 0x289b7ec6);
+      OP (FH, D, A, B, C,  0, 11, 0xeaa127fa);
+      OP (FH, C, D, A, B,  3, 16, 0xd4ef3085);
+      OP (FH, B, C, D, A,  6, 23, 0x04881d05);
+      OP (FH, A, B, C, D,  9,  4, 0xd9d4d039);
+      OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);
+      OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);
+      OP (FH, B, C, D, A,  2, 23, 0xc4ac5665);
+
+      /* Round 4.  */
+      OP (FI, A, B, C, D,  0,  6, 0xf4292244);
+      OP (FI, D, A, B, C,  7, 10, 0x432aff97);
+      OP (FI, C, D, A, B, 14, 15, 0xab9423a7);
+      OP (FI, B, C, D, A,  5, 21, 0xfc93a039);
+      OP (FI, A, B, C, D, 12,  6, 0x655b59c3);
+      OP (FI, D, A, B, C,  3, 10, 0x8f0ccc92);
+      OP (FI, C, D, A, B, 10, 15, 0xffeff47d);
+      OP (FI, B, C, D, A,  1, 21, 0x85845dd1);
+      OP (FI, A, B, C, D,  8,  6, 0x6fa87e4f);
+      OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
+      OP (FI, C, D, A, B,  6, 15, 0xa3014314);
+      OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);
+      OP (FI, A, B, C, D,  4,  6, 0xf7537e82);
+      OP (FI, D, A, B, C, 11, 10, 0xbd3af235);
+      OP (FI, C, D, A, B,  2, 15, 0x2ad7d2bb);
+      OP (FI, B, C, D, A,  9, 21, 0xeb86d391);
+
+      /* Add the starting values of the context.  */
+      A += A_save;
+      B += B_save;
+      C += C_save;
+      D += D_save;
+    }
+
+  /* Put checksum in context given as argument.  */
+  ctx->A = A;
+  ctx->B = B;
+  ctx->C = C;
+  ctx->D = D;
+}
diff --git a/tool/rabbitsign-src/md5.h b/tool/rabbitsign-src/md5.h
new file mode 100644
index 0000000..ad97efc
--- /dev/null
+++ b/tool/rabbitsign-src/md5.h
@@ -0,0 +1,146 @@
+/* md5.h - Declaration of functions and data types used for MD5 sum
+   computing library functions.
+   Copyright (C) 1995, 1996 Free Software Foundation, Inc.
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to bug-glibc@prep.ai.mit.edu.
+
+   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, or (at your option) any
+   later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifndef _MD5_H
+#define _MD5_H 1
+
+#include <stdio.h>
+
+#if defined HAVE_LIMITS_H || _LIBC
+# include <limits.h>
+#endif
+
+/* The following contortions are an attempt to use the C preprocessor
+   to determine an unsigned integral type that is 32 bits wide.  An
+   alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
+   doing that would require that the configure script compile and *run*
+   the resulting executable.  Locally running cross-compiled executables
+   is usually not possible.  */
+
+#ifdef _LIBC
+# include <sys/types.h>
+typedef u_int32_t md5_uint32;
+#else
+# if defined __STDC__ && __STDC__
+#  define UINT_MAX_32_BITS 4294967295U
+# else
+#  define UINT_MAX_32_BITS 0xFFFFFFFF
+# endif
+
+/* If UINT_MAX isn't defined, assume it's a 32-bit type.
+   This should be valid for all systems GNU cares about because
+   that doesn't include 16-bit systems, and only modern systems
+   (that certainly have <limits.h>) have 64+-bit integral types.  */
+
+# ifndef UINT_MAX
+#  define UINT_MAX UINT_MAX_32_BITS
+# endif
+
+# if UINT_MAX == UINT_MAX_32_BITS
+   typedef unsigned int md5_uint32;
+# else
+#  if USHRT_MAX == UINT_MAX_32_BITS
+    typedef unsigned short md5_uint32;
+#  else
+#   if ULONG_MAX == UINT_MAX_32_BITS
+     typedef unsigned long md5_uint32;
+#   else
+     /* The following line is intended to evoke an error.
+        Using #error is not portable enough.  */
+     "Cannot determine unsigned 32-bit data type."
+#   endif
+#  endif
+# endif
+#endif
+
+#undef __P
+#if defined (__STDC__) && __STDC__
+#define	__P(x) x
+#else
+#define	__P(x) ()
+#endif
+
+/* Structure to save state of computation between the single steps.  */
+struct md5_ctx
+{
+  md5_uint32 A;
+  md5_uint32 B;
+  md5_uint32 C;
+  md5_uint32 D;
+
+  md5_uint32 total[2];
+  md5_uint32 buflen;
+  char buffer[128];
+};
+
+/*
+ * The following three functions are build up the low level used in
+ * the functions `md5_stream' and `md5_buffer'.
+ */
+
+/* Initialize structure containing state of computation.
+   (RFC 1321, 3.3: Step 3)  */
+extern void md5_init_ctx __P ((struct md5_ctx *ctx));
+
+/* Starting with the result of former calls of this function (or the
+   initialization function update the context for the next LEN bytes
+   starting at BUFFER.
+   It is necessary that LEN is a multiple of 64!!! */
+extern void md5_process_block __P ((const void *buffer, size_t len,
+				    struct md5_ctx *ctx));
+
+/* Starting with the result of former calls of this function (or the
+   initialization function update the context for the next LEN bytes
+   starting at BUFFER.
+   It is NOT required that LEN is a multiple of 64.  */
+extern void md5_process_bytes __P ((const void *buffer, size_t len,
+				    struct md5_ctx *ctx));
+
+/* Process the remaining bytes in the buffer and put result from CTX
+   in first 16 bytes following RESBUF.  The result is always in little
+   endian byte order, so that a byte-wise output yields to the wanted
+   ASCII representation of the message digest.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+extern void *md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf));
+
+
+/* Put result from CTX in first 16 bytes following RESBUF.  The result is
+   always in little endian byte order, so that a byte-wise output yields
+   to the wanted ASCII representation of the message digest.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+extern void *md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf));
+
+
+/* Compute MD5 message digest for bytes read from STREAM.  The
+   resulting message digest number will be written into the 16 bytes
+   beginning at RESBLOCK.  */
+extern int md5_stream __P ((FILE *stream, void *resblock));
+
+/* Compute MD5 message digest for LEN bytes beginning at BUFFER.  The
+   result is always in little endian byte order, so that a byte-wise
+   output yields to the wanted ASCII representation of the message
+   digest.  */
+extern void *md5_buffer __P ((const char *buffer, size_t len, void *resblock));
+
+#endif
diff --git a/tool/rabbitsign-src/mem.c b/tool/rabbitsign-src/mem.c
new file mode 100644
index 0000000..d65064b
--- /dev/null
+++ b/tool/rabbitsign-src/mem.c
@@ -0,0 +1,73 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+void* rs_realloc(void* ptr, unsigned long count)
+{
+  void* p;
+
+  if (!count) {
+    if (ptr) {
+      free(ptr);
+    }
+    return NULL;
+  }
+
+  if (ptr)
+    p = realloc(ptr, count);
+  else
+    p = malloc(count);
+  if (!p)
+    rs_error(NULL, NULL, "out of memory (need %lu bytes)", count);
+  return p;
+}
+
+char* rs_strdup(const char* str)
+{
+  int n;
+  char* p;
+
+  if (!str)
+    return NULL;
+
+  n = strlen(str);
+  p = rs_malloc(n + 1);
+  if (p)
+    memcpy(p, str, n + 1);
+  return p;  
+}
diff --git a/tool/rabbitsign-src/mpz.c b/tool/rabbitsign-src/mpz.c
new file mode 100644
index 0000000..2a38ef5
--- /dev/null
+++ b/tool/rabbitsign-src/mpz.c
@@ -0,0 +1,1026 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_ASSERT_H
+# include <assert.h>
+#else
+# define assert(xxx) if (!(xxx)) {				\
+    fprintf(stderr, "mpz: assertion \"%s\" failed\n", #xxx);	\
+    abort();							\
+  }
+#endif
+
+#include "mpz.h"
+
+/*
+ * This file contains multiple-precision arithmetic functions.  These
+ * are equivalent to the corresponding GMP functions in the ways they
+ * are used by RabbitSign.
+ *
+ * HOWEVER, they are not by any means complete.  They do not meet the
+ * specifications of the GMP functions; for the sake of portability
+ * and compactness they are vastly less efficient; and they may even
+ * have bugs.
+ */
+
+/*
+#define DBG(args...) if (1) \
+  do { \
+    gmp_fprintf(stderr, "mpz: " args); \
+    fputc('\n', stderr); \
+  } while (0)
+*/
+
+#define IDX(nnn, iii) (nnn)->m[iii]
+
+/* \
+  (*(__extension__ ({ \
+      int _ii = (iii); \
+      const struct _mpz* _nn = (nnn); \
+      assert(_nn->size <= _nn->size_alloc); \
+      assert(_ii >= 0); \
+      assert(((unsigned) _ii) < (_nn->size)); \
+      &(_nn->m[_ii]); })))
+*/
+
+static void* xrealloc(p, n)
+     void* p;
+     size_t n;
+{
+  void* res;
+
+  if (n <= 0)
+    n = 1;
+
+  if (p)
+    res = realloc(p, n);
+  else
+    res = malloc(n);
+
+  if (!res) {
+    fprintf(stderr,"mpz: out of memory (need %lu bytes)\n",
+	    (unsigned long) n);
+    abort();
+  }
+  return res;
+}
+
+static inline void allocate_mpz(x)
+     mpz_t x;
+{
+  if (x->size_alloc < x->size) {
+    x->size_alloc = x->size;
+    x->m = (limb_t*) xrealloc(x->m, x->size_alloc * sizeof(limb_t));
+  }
+}
+
+static inline void zero_mpz(x)
+     mpz_t x;
+{
+  size_t i;
+  for (i = 0; i < x->size; i++)
+    IDX(x, i) = 0;
+}
+
+static inline void copyref_mpz(dest, src)
+     mpz_t dest;
+     const mpz_t src;
+{
+  dest->size = src->size;
+  dest->size_alloc = src->size_alloc;
+  dest->m = src->m;
+  dest->sign = src->sign;
+}
+
+static inline void reduce_mpz(x)
+     mpz_t x;
+{
+  while (x->size > 0 && IDX(x, (x->size) - 1) == 0)
+    x->size--;
+}
+
+/**************** Init / Clear ****************/
+
+void mpz_init(x)
+     mpz_t x;
+{
+  x->size = 0;
+  x->size_alloc = 0;
+  x->m = (limb_t*)0;
+  x->sign = 1;
+}
+
+void mpz_clear(x)
+     mpz_t x;
+{
+  if (x->m)
+    free(x->m);
+  mpz_init(x);
+}
+
+/**************** Setting ****************/
+
+void mpz_set(dest, src)
+     mpz_t dest;
+     const mpz_t src;
+{
+  size_t i;
+
+  dest->size = src->size;
+  allocate_mpz(dest);
+  dest->sign = src->sign;
+
+  for (i = 0; i < src->size; i++)
+    IDX(dest, i) = IDX(src, i);
+}
+
+void mpz_set_ui(dest, a)
+     mpz_t dest;
+     unsigned int a;
+{
+  if (a) {
+    dest->size = 1;
+    allocate_mpz(dest);
+    IDX(dest, 0) = a;
+  }
+  else
+    dest->size = 0;
+  dest->sign = 1;
+}
+
+unsigned int mpz_get_ui(a)
+     const mpz_t a;
+{
+  return IDX(a, 0);
+}
+
+static void mpz_swap(a, b)
+     mpz_t a;
+     mpz_t b;
+{
+  mpz_t temp;
+  copyref_mpz(temp, a);
+  copyref_mpz(a, b);
+  copyref_mpz(b, temp);
+}
+
+/**************** Import / Export ****************/
+
+void mpz_import(dest, count, order, size, endian, nails, op)
+     mpz_t dest;
+     size_t count;
+     int order; /* must be -1 (little endian structure) */
+     int size; /* must be 1 (bytes) */
+     int endian; /* must be 0 (native endian words, doesn't matter for bytes) */
+     size_t nails; /* must be 0 (no nails) */
+     const void* op;
+{
+  size_t i, j;
+
+  assert(order == -1);
+  assert(size == 1);
+  assert(endian == 0);
+  assert(nails == 0);
+
+  dest->size = (count + LIMB_BYTES - 1) / LIMB_BYTES;
+  allocate_mpz(dest);
+  dest->sign = 1;
+
+  for (i = 0; i < dest->size; i++) {
+    IDX(dest, i) = 0;
+    for (j = 0; j < LIMB_BYTES && ((i * LIMB_BYTES) + j) < count; j++) {
+      IDX(dest, i) |= ((unsigned char*)op)[(i * LIMB_BYTES) + j] << 8 * j;
+    }
+  }
+}
+
+void mpz_export(dest, count, order, size, endian, nails, op)
+     void* dest;
+     size_t* count;
+     int order; /* must be -1 (little endian structure) */
+     int size; /* must be 1 (bytes) */
+     int endian; /* must be 0 (native endian words, doesn't matter for bytes) */
+     size_t nails; /* must be 0 (no nails) */
+     const mpz_t op;
+{
+  size_t i, j;
+
+  assert(order == -1);
+  assert(size == 1);
+  assert(endian == 0);
+  assert(nails == 0);
+
+  for (i = 0; i < op->size; i++) {
+    for (j = 0; j < LIMB_BYTES; j++) {
+      ((unsigned char*)dest)[(i * LIMB_BYTES) + j] = IDX(op, i) >> 8 * j;
+    }
+  }
+  *count = op->size * LIMB_BYTES;
+}
+
+/**************** Comparison ****************/
+
+int mpz_sgn(a)
+     const mpz_t a;
+{
+  size_t i = a->size;
+
+  while (i > 0 && IDX(a, i - 1) == 0)
+    i--;
+
+  if (i == 0)
+    return 0;
+  else
+    return a->sign;
+}
+
+static int mpz_cmpabs(a, b)
+     const mpz_t a;
+     const mpz_t b;
+{
+  size_t sa = a->size;
+  size_t sb = b->size;
+
+  while (sa > 0 && IDX(a, sa - 1) == 0)
+    sa--;
+  while (sb > 0 && IDX(b, sb - 1) == 0)
+    sb--;
+
+  if (sa > sb)
+    return 1;
+  else if (sb > sa)
+    return -1;
+
+  while (sa > 0) {
+    if (IDX(a, sa - 1) > IDX(b, sa - 1))
+      return 1;
+    else if (IDX(a, sa - 1) < IDX(b, sa - 1))
+      return -1;
+    sa--;
+  }
+
+  return 0;
+}
+
+int mpz_cmp(a, b)
+     const mpz_t a;
+     const mpz_t b;
+{
+  size_t sa = a->size;
+  size_t sb = b->size;
+
+  while (sa > 0 && IDX(a, sa - 1) == 0)
+    sa--;
+  while (sb > 0 && IDX(b, sb - 1) == 0)
+    sb--;
+
+  if (sa == 0 && sb == 0)
+    return 0;
+  else if (sa == 0)
+    return -(b->sign);
+  else if (sb == 0)
+    return a->sign;
+  else if (a->sign != b->sign)
+    return a->sign;
+
+  return a->sign * mpz_cmpabs(a, b);
+}
+
+int mpz_cmp_ui(a, b)
+     const mpz_t a;
+     unsigned int b;
+{
+  size_t sa = a->size;
+
+  while (sa > 0 && IDX(a, sa - 1) == 0)
+    sa--;
+
+  if (sa == 0 && b == 0)
+    return 0;
+
+  if (sa == 1 && a->sign == 1) {
+    if (IDX(a, 0) > b)
+      return 1;
+    else if (IDX(a, 0) < b)
+      return -1;
+    else
+      return 0;
+  }
+
+  return (a->sign);
+}
+
+/**************** Addition / Subtraction ****************/
+
+static void mpz_addabs(dest, a, b)
+     mpz_t dest;		/* != a, b */
+     const mpz_t a;
+     const mpz_t b;
+{
+  size_t i;
+  double_limb_t carry = 0;
+
+  if (a->size > b->size)
+    dest->size = a->size + 1;
+  else
+    dest->size = b->size + 1;
+  allocate_mpz(dest);
+
+  assert(dest != a);
+  assert(dest != b);
+
+  for (i = 0; i < a->size && i < b->size; i++) {
+    carry += IDX(a, i);
+    carry += IDX(b, i);
+    IDX(dest, i) = carry & LIMB_MASK;
+    carry >>= LIMB_BITS;
+  }
+
+  for (; i < a->size; i++) {
+    carry += IDX(a, i);
+    IDX(dest, i) = carry & LIMB_MASK;
+    carry >>= LIMB_BITS;
+  }
+
+  for (; i < b->size; i++) {
+    carry += IDX(b, i);
+    IDX(dest, i) = carry & LIMB_MASK;
+    carry >>= LIMB_BITS;
+  }
+  IDX(dest, i) = carry;
+}
+
+static void mpz_subabs(dest, a, b)
+     mpz_t dest;		/* != b */
+     const mpz_t a;
+     const mpz_t b;		/* must be <= a */
+{
+  size_t i;
+  signed_double_limb_t carry = 0;
+  dest->size = a->size;
+  allocate_mpz(dest);
+
+  assert(dest != b);
+
+  for (i = 0; i < a->size && i < b->size; i++) {
+    carry += IDX(a, i);
+    carry -= IDX(b, i);
+    IDX(dest, i) = carry & LIMB_MASK;
+    carry >>= LIMB_BITS;
+  }
+
+  for (; i < a->size; i++) {
+    carry += IDX(a, i);
+    IDX(dest, i) = carry & LIMB_MASK;
+    carry >>= LIMB_BITS;
+  }
+
+  assert(carry == 0);
+}
+
+void mpz_add(dest, a, b)
+     mpz_t dest;
+     const mpz_t a;
+     const mpz_t b;
+{
+  mpz_t temp;
+  mpz_init(temp);
+
+  if (a->sign == b->sign) {
+    temp->sign = a->sign;
+    mpz_addabs(temp, a, b);
+  }
+  else if (mpz_cmpabs(a, b) > 0) {
+    temp->sign = a->sign;
+    mpz_subabs(temp, a, b);
+  }
+  else {
+    temp->sign = b->sign;
+    mpz_subabs(temp, b, a);
+  }
+
+  reduce_mpz(temp);
+  mpz_clear(dest);
+  copyref_mpz(dest, temp);
+}
+
+void mpz_sub(dest, a, b)
+     mpz_t dest;
+     const mpz_t a;
+     const mpz_t b;
+{
+  mpz_t temp;
+  mpz_init(temp);
+
+  if (a->sign != b->sign) {
+    temp->sign = a->sign;
+    mpz_addabs(temp, a, b);
+  }
+  else if (mpz_cmpabs(a, b) > 0) {
+    temp->sign = a->sign;
+    mpz_subabs(temp, a, b);
+  }
+  else {
+    temp->sign = -(b->sign);
+    mpz_subabs(temp, b, a);
+  }
+
+  reduce_mpz(temp);
+  mpz_clear(dest);
+  copyref_mpz(dest, temp);
+}
+
+void mpz_add_ui(dest, a, b)
+     mpz_t dest;
+     const mpz_t a;
+     unsigned int b;
+{
+  size_t i;
+  mpz_t temp;
+  mpz_init(temp);
+
+  temp->size = a->size + 1;
+  temp->sign = a->sign;
+  allocate_mpz(temp);
+
+  for (i = 0; i < a->size; i++) {
+    IDX(temp, i) = IDX(a, i) + b;
+    b = (IDX(temp, i) < IDX(a, i)) ? 1 : 0;
+  }
+  IDX(temp, i) = b;
+
+  reduce_mpz(temp);
+  mpz_clear(dest);
+  copyref_mpz(dest, temp);
+}
+
+void mpz_sub_ui(dest, a, b)
+     mpz_t dest;
+     const mpz_t a;
+     unsigned int b;
+{
+  size_t i;
+  mpz_t temp;
+  mpz_init(temp);
+
+  temp->size = a->size;
+  temp->sign = a->sign;
+  allocate_mpz(temp);
+
+  for (i = 0; i < a->size; i++) {
+    IDX(temp, i) = IDX(a, i) - b;
+    b = (IDX(temp, i) > IDX(a, i)) ? 1 : 0;
+  }
+  assert(b == 0);
+
+  reduce_mpz(temp);
+  mpz_clear(dest);
+  copyref_mpz(dest, temp);
+}
+
+/**************** Multiplication ****************/
+
+void mpz_mul(dest, a, b)
+     mpz_t dest;
+     const mpz_t a;
+     const mpz_t b;
+{
+  double_limb_t carry = 0, newcarry;
+  size_t i, j, k;
+  mpz_t temp;
+  mpz_init(temp);
+
+  temp->size = a->size + b->size;
+  temp->sign = a->sign * b->sign;
+  allocate_mpz(temp);
+  zero_mpz(temp);
+
+  for (i = 0; i < a->size; i++) {
+    for (j = 0; j < b->size; j++) {
+      carry = IDX(a, i);
+      carry *= IDX(b, j);
+      for (k = i + j; k < temp->size && carry; k++) {
+	newcarry = carry + IDX(temp, k);
+	if (newcarry < carry) {
+	  IDX(temp, k) = newcarry & LIMB_MASK;
+	  carry = (newcarry >> LIMB_BITS) + LIMB_MASK + 1;
+	}
+	else {
+	  IDX(temp, k) = newcarry & LIMB_MASK;
+	  carry = (newcarry >> LIMB_BITS);
+	}
+      }
+      assert(carry == 0);
+    }
+  }
+
+  reduce_mpz(temp);
+  mpz_clear(dest);
+  copyref_mpz(dest, temp);
+}
+
+void mpz_mul_ui(dest, a, b)
+     mpz_t dest;
+     const mpz_t a;
+     unsigned int b;		/* must be fairly small */
+{
+  double_limb_t carry = 0;
+  size_t i;
+  mpz_t temp;
+  mpz_init(temp);
+
+  temp->size = a->size + 1;
+  temp->sign = a->sign;
+  allocate_mpz(temp);
+
+  for (i = 0; i < a->size; i++) {
+    carry += (double_limb_t) IDX(a, i) * b;
+    IDX(temp, i) = carry & LIMB_MASK;
+    carry >>= LIMB_BITS;
+  }
+  IDX(temp, i) = carry & LIMB_MASK;
+
+  reduce_mpz(temp);
+  mpz_clear(dest);
+  copyref_mpz(dest, temp);
+}
+
+/**************** Division ****************/
+
+void mpz_fdiv_q_2exp(dest, a, b)
+     mpz_t dest;
+     const mpz_t a;
+     unsigned int b;		/* must be <= LIMB_BITS */
+{
+  size_t i;
+  mpz_t temp;
+  mpz_init(temp);
+
+  assert(b <= LIMB_BITS);
+
+  temp->size = a->size;
+  temp->sign = a->sign;
+  allocate_mpz(temp);
+
+  if (a->size > 0) {
+    for (i = 0; i < (a->size - 1); i++) {
+      IDX(temp, i) = (((IDX(a, i) >> b) | (IDX(a, i + 1) << (LIMB_BITS - b)))
+		      & LIMB_MASK);
+    }
+    IDX(temp, a->size - 1) = IDX(a, a->size - 1) >> b;
+  }
+
+  reduce_mpz(temp);
+  mpz_clear(dest);
+  copyref_mpz(dest, temp);
+}
+
+/**************** Division / Modulus ****************/
+
+static void mpz_setbit(dest, n)
+     mpz_t dest;
+     unsigned int n;
+{
+  size_t i, j;
+
+  i = n / LIMB_BITS + 1;
+
+  if (dest->size < i) {
+    j = dest->size;
+    dest->size = i;
+    allocate_mpz(dest);
+    while (j < i) {
+      IDX(dest, j) = 0;
+      j++;
+    }
+  }
+
+  IDX(dest, i - 1) |= (1 << (n % LIMB_BITS));
+}
+
+static void mpz_fdiv_qr(q, r, num, den)
+     mpz_t q;
+     mpz_t r;
+     const mpz_t num;
+     const mpz_t den;
+{
+  size_t shiftct = 0;
+  size_t i;
+  mpz_t remainder;
+  mpz_t quotient;
+  mpz_t shifted;		/* shifted = mod * 2^(shiftct) */
+
+  mpz_init(remainder);
+  mpz_init(shifted);
+  if (q) {
+    mpz_init(quotient);
+    quotient->sign = num->sign * den->sign;
+  }
+
+  shifted->size = num->size;
+  allocate_mpz(shifted);
+
+  mpz_set(remainder, num);
+  mpz_set(shifted, den);
+
+  reduce_mpz(shifted);
+  assert(shifted->size > 0);
+
+  while (mpz_cmpabs(remainder, shifted) > 0) {
+    shifted->size++;
+    allocate_mpz(shifted);
+    for (i = shifted->size - 1; i > 0; i--)
+      IDX(shifted, i) = IDX(shifted, i - 1);
+    IDX(shifted, 0) = 0;
+    shiftct += LIMB_BITS;
+  }
+
+  while (shiftct != 0) {
+    if (mpz_cmpabs(remainder, shifted) >= 0) {
+      mpz_subabs(remainder, remainder, shifted);
+      reduce_mpz(remainder);
+      if (q)
+	mpz_setbit(quotient, shiftct);
+    }
+    mpz_fdiv_q_2exp(shifted, shifted, 1);
+    shiftct--;
+  }
+
+  if (mpz_cmpabs(remainder, den) >= 0) {
+    mpz_subabs(remainder, remainder, den);
+    if (q)
+      mpz_setbit(quotient, 0);
+  }
+
+  if (mpz_sgn(remainder) == -1) {
+    mpz_add(remainder, remainder, den);
+    if (q)
+      mpz_sub_ui(quotient, quotient, 1);
+  }
+
+  mpz_clear(shifted);
+  reduce_mpz(remainder);
+  mpz_clear(r);
+  copyref_mpz(r, remainder);
+
+  if (q) {
+    reduce_mpz(quotient);
+    mpz_clear(q);
+    copyref_mpz(q, quotient);
+  }
+}
+
+void mpz_mod(dest, a, mod)
+     mpz_t dest;
+     const mpz_t a;
+     const mpz_t mod;
+{
+  mpz_fdiv_qr(NULL, dest, a, mod);
+}
+
+/**************** Modular exponent ****************/
+
+void mpz_powm(dest, base, exp, mod)
+     mpz_t dest;
+     const mpz_t base;
+     const mpz_t exp;
+     const mpz_t mod;
+{
+  mpz_t exp_bits;
+  mpz_t base_power;
+  mpz_t temp;
+  mpz_init(exp_bits);
+  mpz_init(base_power);
+  mpz_init(temp);
+
+  mpz_set(exp_bits, exp);
+  mpz_set(base_power, base);
+  mpz_set_ui(temp, 1);
+
+  reduce_mpz(exp_bits);
+  assert(exp_bits->sign == 1 || exp_bits->size == 0);
+
+  while (exp_bits->size > 0) {
+    if (IDX(exp_bits, 0) & 1) {
+      mpz_mul(temp, temp, base_power);
+      mpz_mod(temp, temp, mod);
+    }
+    mpz_mul(base_power, base_power, base_power);
+    mpz_mod(base_power, base_power, mod);
+    mpz_fdiv_q_2exp(exp_bits, exp_bits, 1);
+  }
+
+  mpz_clear(exp_bits);
+  mpz_clear(base_power);
+  reduce_mpz(temp);
+  mpz_clear(dest);
+  copyref_mpz(dest, temp);
+}
+
+/**************** Legendre symbol ****************/
+
+int mpz_legendre(a, p)
+     const mpz_t a;
+     const mpz_t p;
+{
+  int x;
+  mpz_t exp;
+  mpz_t pow;
+  mpz_init(exp);
+  mpz_init(pow);
+
+  mpz_set(exp, p);
+  mpz_sub_ui(exp, exp, 1);
+  mpz_fdiv_q_2exp(exp, exp, 1);
+  mpz_powm(pow, a, exp, p);
+
+  if (pow->size == 1 && IDX(pow, 0) == 1)
+    x = 1;
+  else if (pow->size == 0)
+    x = 0;
+  else
+    x = -1;
+
+  mpz_clear(exp);
+  mpz_clear(pow);
+
+  return x;
+}
+
+/**************** GCD ****************/
+
+static void mpz_gcdext_main(g, ai, bi, a, b)
+     mpz_t g;
+     mpz_t ai;
+     mpz_t bi;
+     const mpz_t a;
+     const mpz_t b;
+{
+  mpz_t rem_last, rem_cur;
+  mpz_t ai_last, ai_cur;
+  mpz_t bi_last, bi_cur;
+  mpz_t q, temp;
+
+  mpz_init(rem_last);
+  mpz_init(rem_cur);
+  mpz_init(q);
+
+  mpz_set(rem_last, a);
+  mpz_set(rem_cur, b);
+
+  if (ai) {
+    mpz_init(ai_last);
+    mpz_init(ai_cur);
+    mpz_set_ui(ai_last, 1);
+  }
+
+  if (bi) {
+    mpz_init(bi_last);
+    mpz_init(bi_cur);
+    mpz_set_ui(bi_cur, 1);
+  }
+
+  assert(a->sign == 1 && a->size > 0);
+  assert(b->sign == 1 && b->size > 0);
+
+  while (1) {
+    mpz_fdiv_qr(q, rem_last, rem_last, rem_cur);
+    mpz_swap(rem_last, rem_cur);
+    if (!mpz_sgn(rem_cur))
+      break;
+
+    if (ai) {
+      mpz_init(temp);
+      mpz_mul(temp, q, ai_cur);
+      mpz_sub(temp, ai_last, temp);
+      mpz_clear(ai_last);
+      copyref_mpz(ai_last, ai_cur);
+      copyref_mpz(ai_cur, temp);
+    }
+
+    if (bi) {
+      mpz_init(temp);
+      mpz_mul(temp, q, bi_cur);
+      mpz_sub(temp, bi_last, temp);
+      mpz_clear(bi_last);
+      copyref_mpz(bi_last, bi_cur);
+      copyref_mpz(bi_cur, temp);
+    }
+  }
+
+  mpz_clear(g);
+  copyref_mpz(g, rem_last);
+  mpz_clear(rem_cur);
+  mpz_clear(q);
+
+  if (ai) {
+    mpz_clear(ai);
+    copyref_mpz(ai, ai_cur);
+    mpz_clear(ai_last);
+  }
+  if (bi) {
+    mpz_clear(bi);
+    copyref_mpz(bi, bi_cur);
+    mpz_clear(bi_last);
+  }
+}
+
+void mpz_gcdext(g, ai, bi, a, b)
+     mpz_t g;
+     mpz_t ai;
+     mpz_t bi;
+     const mpz_t a;
+     const mpz_t b;
+{
+  if (mpz_cmpabs(a, b) > 0)
+    mpz_gcdext_main(g, ai, bi, a, b);
+  else
+    mpz_gcdext_main(g, bi, ai, b, a);  
+}
+
+/**************** Output ****************/
+
+#define PUTCH(bbb, sss, nnn, ccc) do {		\
+    if ((sss) > 1) {				\
+      *(bbb) = (ccc);				\
+      (bbb)++;					\
+      (sss)--;					\
+    }						\
+    (nnn)++;					\
+  } while (0)
+
+static int putnum(char** buf, size_t* size, unsigned long value,
+		  unsigned int base)
+{
+  unsigned long s;
+  unsigned int d;
+  int count = 0;
+
+  if (value == 0) {
+    PUTCH(*buf, *size, count, '0');
+  }
+  else {
+    s = value / base;
+    if (s)
+      count = putnum(buf, size, value / base, base);
+    d = value % base;
+    if (d < 10)
+      PUTCH(*buf, *size, count, d + '0');
+    else
+      PUTCH(*buf, *size, count, d + 'A' - 10);
+  }
+
+  return count;
+}
+
+/* Supported conversions:
+   %% %s %c %d %i %o %u %X %ld %li %lo %lu %lX %ZX
+ */
+
+int rs_vsnprintf(char* buf, size_t size, const char* fmt, va_list ap)
+{
+  int count = 0;
+  int argtype, convtype;
+  const char* strval;
+  long longval;
+  struct _mpz *mpval;
+  limb_t v, d;
+  size_t i;
+  int j;
+
+  while (fmt[0]) {
+    if (fmt[0] != '%') {
+      PUTCH(buf, size, count, fmt[0]);
+      fmt++;
+    }
+    else if (fmt[1] == '%') {
+      PUTCH(buf, size, count, '%');
+      fmt += 2;
+    }
+    else {
+      if (fmt[1] == 'l' || fmt[1] == 'Z') {
+	argtype = fmt[1];
+	convtype = fmt[2];
+	fmt += 3;
+      }
+      else {
+	argtype = 0;
+	convtype = fmt[1];
+	fmt += 2;
+      }
+
+      if (!convtype)
+	break;
+
+      if (convtype == 's') {
+	/* string argument */
+	strval = va_arg(ap, const char *);
+	if (!strval)
+	  strval = "(NULL)";
+	while (*strval) {
+	  PUTCH(buf, size, count, *strval);
+	  strval++;
+	}
+      }
+      else if (convtype == 'c' || convtype == 'd' || convtype == 'i'
+	       || convtype == 'u' || convtype == 'x' || convtype == 'X') {
+	if (argtype == 'Z') {
+	  /* mpz argument -- always print in hexadecimal */
+	  mpval = va_arg(ap, struct _mpz *);
+
+	  if (mpval->sign < 0)
+	    PUTCH(buf, size, count, '-');
+
+	  v = IDX(mpval, mpval->size - 1);
+	  count += putnum(&buf, &size, v, 16);
+
+	  for (i = mpval->size - 1; i > 0; i--) {
+	    v = IDX(mpval, i - 1);
+	    for (j = LIMB_BITS - 4; j >= 0; j -= 4) {
+	      d = ((v >> j) & 0x0f);
+	      if (d < 10)
+		PUTCH(buf, size, count, d + '0');
+	      else
+		PUTCH(buf, size, count, d + 'A' - 10);
+	    }
+	  }
+	}
+	else {
+	  /* int or long argument */
+	  if (argtype == 'l')
+	    longval = va_arg(ap, long);
+	  else
+	    longval = va_arg(ap, int);
+
+	  if (convtype == 'c') {
+	    PUTCH(buf, size, count, (char) (unsigned char) longval);
+	  }
+	  else if (convtype == 'x' || convtype == 'X') {
+	    count += putnum(&buf, &size, longval, 16);
+	  }
+	  else if (convtype == 'o') {
+	    count += putnum(&buf, &size, longval, 8);
+	  }
+	  else if (convtype == 'u' || longval >= 0) {
+	    count += putnum(&buf, &size, longval, 10);
+	  }
+	  else {
+	    PUTCH(buf, size, count, '-');
+	    count += putnum(&buf, &size, -longval, 10);
+	  }
+	}
+      }
+      else {
+	fprintf(stderr, "*** ERROR: mpz: unsupported conversion '%%%c'",
+		convtype);
+	if (argtype)
+	  PUTCH(buf, size, count, argtype);
+	PUTCH(buf, size, count, convtype);
+      }
+    }
+  }
+
+  if (size != 0)
+    *buf = 0;
+
+  return count;
+}
+
+int rs_snprintf(char* buf, size_t size, const char* fmt, ...)
+{
+  va_list ap;
+  int count;
+
+  va_start(ap, fmt);
+  count = rs_vsnprintf(buf, size, fmt, ap);
+  va_end(ap);
+
+  return count;
+}
+
diff --git a/tool/rabbitsign-src/mpz.h b/tool/rabbitsign-src/mpz.h
new file mode 100644
index 0000000..2eb2667
--- /dev/null
+++ b/tool/rabbitsign-src/mpz.h
@@ -0,0 +1,123 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __RABBITSIGN_MPZ_H__
+#define __RABBITSIGN_MPZ_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if (SIZEOF_INT != 0) && (SIZEOF_LONG >= 2 * SIZEOF_INT)
+typedef unsigned int limb_t;
+typedef unsigned long double_limb_t;
+typedef signed long signed_double_limb_t;
+#else
+# if (SIZEOF_SHORT != 0) && (SIZEOF_INT >= 2 * SIZEOF_SHORT)
+typedef unsigned short limb_t;
+typedef unsigned int double_limb_t;
+typedef signed int signed_double_limb_t;
+# else
+typedef unsigned short limb_t;
+typedef unsigned long double_limb_t;
+typedef signed long signed_double_limb_t;
+# endif
+#endif
+
+#define LIMB_BITS (sizeof(limb_t)*8)
+#define LIMB_BYTES (sizeof(limb_t))
+#define LIMB_MASK ((((double_limb_t) 1) << LIMB_BITS) - 1)
+
+struct _mpz {
+  size_t size;
+  size_t size_alloc;
+  limb_t* m;
+  int sign;
+};
+
+typedef struct _mpz mpz_t[1];
+
+#undef __P
+#ifdef PROTOTYPES
+# define __P(x) x
+#else
+# define __P(x) ()
+#endif
+
+void mpz_init __P((mpz_t x));
+void mpz_clear __P((mpz_t x));
+
+/* Set */
+void mpz_set __P((mpz_t dest, const mpz_t src));
+void mpz_set_ui __P((mpz_t dest, unsigned int a));
+unsigned int mpz_get_ui __P((const mpz_t a));
+
+/* Import/export: assume order=-1, size=1, endian=0, nails=0 */
+void mpz_import __P((mpz_t dest, size_t count, int order, int size,
+		     int endian, size_t nails, const void* op));
+void mpz_export __P((void* dest, size_t* count, int order, int size,
+		     int endian, size_t nails, const mpz_t op));
+
+/* Check sign */
+int mpz_sgn __P((const mpz_t a));
+
+/* Compare */
+int mpz_cmp __P((const mpz_t a, const mpz_t b));
+int mpz_cmp_ui __P((const mpz_t a, unsigned int b));
+
+/* Add */
+void mpz_add __P((mpz_t dest, const mpz_t a, const mpz_t b));
+void mpz_add_ui __P((mpz_t dest, const mpz_t a, unsigned int b));
+
+/* Subtract */
+void mpz_sub __P((mpz_t dest, const mpz_t a, const mpz_t b));
+void mpz_sub_ui __P((mpz_t dest, const mpz_t a, unsigned int b));
+
+/* Multiply */
+void mpz_mul __P((mpz_t dest, const mpz_t a, const mpz_t b));
+void mpz_mul_ui __P((mpz_t dest, const mpz_t a, unsigned int b));
+
+/* Divide: requires b <= LIMB_BITS */
+void mpz_fdiv_q_2exp __P((mpz_t dest, const mpz_t a, unsigned int b));
+
+/* Modulus */
+void mpz_mod __P((mpz_t dest, const mpz_t a, const mpz_t mod));
+
+/* Modular exponent */
+void mpz_powm __P((mpz_t dest, const mpz_t base, const mpz_t exp,
+		   const mpz_t mod));
+
+/* Legendre symbol */
+int mpz_legendre __P((const mpz_t a, const mpz_t p));
+
+/* Extended GCD */
+void mpz_gcdext __P((mpz_t g, mpz_t ai, mpz_t bi,
+		     const mpz_t a, const mpz_t b));
+
+/* Output */
+int rs_snprintf __P((char* buf, size_t size, const char* fmt, ...));
+
+#ifdef va_start
+int rs_vsnprintf __P((char* buf, size_t size, const char* fmt, va_list ap));
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tool/rabbitsign-src/os8x.c b/tool/rabbitsign-src/os8x.c
new file mode 100644
index 0000000..b5e9ced
--- /dev/null
+++ b/tool/rabbitsign-src/os8x.c
@@ -0,0 +1,298 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+#include "md5.h"
+
+/*
+ * Check/fix OS header fields and data.
+ *
+ * The OS header is much simpler than an application header, and its
+ * correctness is not as crucial to validation.  The most important
+ * parts of the OS header are the key ID and (for newer calculators)
+ * the hardware compatibility level.  There is no date stamp required.
+ * The page count is not required, and if present, is used only to
+ * display the transfer percentage (when using the 84+ boot code.)
+ *
+ * TI only sets the OS and program image size fields in their TI-73 OS
+ * headers.  (Bizarrely, they are set in the true OS header, but not
+ * in the fake OS header that is transferred to page 1A.  Furthermore,
+ * the OS size field is incorrect.)  In any case, these fields appear
+ * to be ignored by all versions of the boot code.
+ */
+int rs_repair_ti8x_os(RSProgram* os,      /* OS */
+		      unsigned int flags) /* flags */
+{
+  unsigned long hdrstart, hdrsize, fieldhead, fieldstart,
+    fieldsize, ossize;
+  unsigned char* hdr;
+  int i;
+
+  /* Pad the OS to a multiple of 16384.  (While strictly speaking we
+     could get away with only padding each page to a multiple of 256,
+     such "partial OSes" are not supported by most linking
+     software.) */
+
+  rs_program_set_length(os, ((os->length + 0x3fff) & ~0x3fff));
+
+  /* If no OS header was provided in the input, try to get a header
+     from page 1A instead */
+
+  if (os->header_length < 6
+      || os->header[0] != 0x80
+      || os->header[1] != 0x0f) {
+    for (i = 0; i < os->npagenums; i++) {
+      if (os->pagenums[i] == 0x1a) {
+	rs_free(os->header);
+	if (!(os->header = rs_malloc(256)))
+	  return RS_ERR_OUT_OF_MEMORY;
+	memcpy(os->header, os->data + ((unsigned long) i << 14), 256);
+	os->header_length = 256;
+	break;
+      }
+    }
+  }
+
+  /* Clear old header/signature (not done on the TI-73 because
+     official TI-73 OSes contain a fake header; I don't recommend
+     doing this for third-party OSes) */
+
+  if (os->calctype != RS_CALC_TI73)
+    for (i = 0; i < os->npagenums; i++)
+      if (os->pagenums[i] == 0x1a)
+	memset(os->data + ((unsigned long) i << 14), 0xff, 512);
+
+  /* Fix header size.  OS headers must always begin with an 800x field
+     and end with an 807x field (TI always uses 800F and 807F, as for
+     apps; I'm not sure whether it's required.) */
+
+  if (os->header_length < 6
+      || os->header[0] != 0x80
+      || (os->header[1] & 0xf0) != 0) {
+    rs_error(NULL, os, "no OS header found");
+    return RS_ERR_MISSING_HEADER;
+  }
+
+  rs_get_field_size(os->header, &hdrstart, NULL);
+  hdr = os->header + hdrstart;
+  hdrsize = os->header_length - hdrstart;
+
+  if (rs_find_app_field(0x8070, hdr, hdrsize,
+			&fieldhead, &fieldstart, &fieldsize)) {
+    rs_error(NULL, os, "OS header has no program image field");
+    return RS_ERR_MISSING_PROGRAM_IMAGE;
+  }
+
+  hdrsize = fieldstart;
+  os->header_length = hdrstart + hdrsize;
+
+  if ((os->header_length % 64) == 55) {
+    if (flags & RS_IGNORE_ALL_WARNINGS) {
+      rs_warning(NULL, os, "OS header has length 55 mod 64");
+      rs_warning(NULL, os, "(this will fail to validate on TI-83+ BE)");
+    }
+    else {
+      rs_error(NULL, os, "OS header has length 55 mod 64");
+      rs_error(NULL, os, "(this will fail to validate on TI-83+ BE)");
+      return RS_ERR_INVALID_PROGRAM_SIZE;
+    }
+  }
+
+  /* Fix OS / OS image sizes if requested */
+
+  if (flags & RS_FIX_OS_SIZE) {
+    ossize = os->length + hdrsize;
+    if (rs_set_field_size(os->header, ossize)) {
+      rs_error(NULL, os, "cannot set OS length");
+      return RS_ERR_FIELD_TOO_SMALL;
+    }
+
+    if (rs_set_field_size(hdr + fieldhead, os->length)) {
+      rs_error(NULL, os, "cannot set OS image length");
+      return RS_ERR_FIELD_TOO_SMALL;
+    }
+  }
+
+  /* Check for key ID */
+
+  if (rs_find_app_field(0x8010, hdr, hdrsize, NULL, NULL, NULL)) {
+    if (flags & RS_IGNORE_ALL_WARNINGS) 
+      rs_warning(NULL, os, "OS header has no key ID field");
+    else {
+      rs_error(NULL, os, "OS header has no key ID field");
+      return RS_ERR_MISSING_KEY_ID;
+    }
+  }
+
+  /* Check/fix page count */
+
+  if (rs_find_app_field(0x8080, hdr, hdrsize,
+			NULL, &fieldstart, &fieldsize)) {
+    if (os->length != 14 * 0x4000L) {
+      rs_warning(NULL, os, "OS header has no page count field");
+    }
+  }
+  else if (fieldsize != 1) {
+    rs_warning(NULL, os, "OS header has an invalid page count field");
+  }
+  else if (flags & RS_FIX_PAGE_COUNT) {
+    hdr[fieldstart] = os->length >> 14;
+  }
+  else if (hdr[fieldstart] != (os->length >> 14)) {
+    rs_warning(NULL, os, "OS header has an incorrect page count field");
+  }
+
+  /* Check and reset validation flag bytes */
+
+  if (os->data[0x56] != 0xff && os->data[0x56] != 0x5a) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, os, "OS has invalid data at 0056h");
+    else {
+      rs_error(NULL, os, "OS has invalid data at 0056h");
+      return RS_ERR_INVALID_PROGRAM_DATA;
+    }
+  }
+
+  if (os->data[0x56] == 0x5a)
+    os->data[0x56] = 0xff;
+
+  if (os->data[0x57] != 0xff && os->data[0x57] != 0xa5) {
+    if (flags & RS_IGNORE_ALL_WARNINGS)
+      rs_warning(NULL, os, "OS has invalid data at 0057h");
+    else {
+      rs_error(NULL, os, "OS has invalid data at 0057h");
+      return RS_ERR_INVALID_PROGRAM_DATA;
+    }
+  }
+
+  if (os->data[0x57] == 0xff)
+    os->data[0x57] = 0xa5;
+
+  return RS_SUCCESS;
+}
+
+/*
+ * Compute signature for an OS.
+ */
+int rs_sign_ti8x_os(RSProgram* os, /* OS */
+		    RSKey* key)	   /* signing key */
+{
+  struct md5_ctx ctx;
+  md5_uint32 hash[4];
+  mpz_t hashv, sigv;
+  unsigned char sigdata[512];
+  size_t siglength;
+  int e;
+
+  md5_init_ctx(&ctx);
+  md5_process_bytes(os->header, os->header_length, &ctx);
+  md5_process_bytes(os->data, os->length, &ctx);
+  md5_finish_ctx(&ctx, hash);
+
+  mpz_init(hashv);
+  mpz_init(sigv);
+
+  mpz_import(hashv, 16, -1, 1, 0, 0, hash);
+  rs_message(2, NULL, os, "hash = %ZX", hashv);
+
+  if ((e = rs_sign_rsa(sigv, hashv, key))) {
+    mpz_clear(hashv);
+    mpz_clear(sigv);
+    return e;
+  }
+
+  rs_message(2, NULL, os, "sig = %ZX", sigv);
+
+  sigdata[0] = 0x02;
+  sigdata[1] = 0x0d;
+  mpz_export(sigdata + 3, &siglength, -1, 1, 0, 0, sigv);
+  sigdata[2] = siglength & 0xff;
+  siglength += 3;
+
+  while (siglength < 96)
+    sigdata[siglength++] = 0xff;
+
+  rs_free(os->signature);
+  if (!(os->signature = rs_malloc(siglength)))
+    return RS_ERR_OUT_OF_MEMORY;
+
+  memcpy(os->signature, sigdata, siglength);
+  os->signature_length = siglength;
+  return RS_SUCCESS;
+}
+
+/*
+ * Validate OS signature.
+ */
+int rs_validate_ti8x_os(const RSProgram* os,
+			const RSKey* key)
+{
+  unsigned long fieldstart, fieldsize;
+  struct md5_ctx ctx;
+  md5_uint32 hash[4];
+  mpz_t hashv, sigv;
+  int e;
+
+  if (os->signature_length < 3) {
+    rs_error(NULL, os, "OS does not have a signature");
+    return RS_ERR_MISSING_RSA_SIGNATURE;
+  }
+
+  if (os->signature[0] != 0x02 || (os->signature[1] & 0xf0) != 0x00) {
+    rs_error(NULL, os, "OS does not have an RSA signature");
+    return RS_ERR_MISSING_RSA_SIGNATURE;
+  }
+  rs_get_field_size(os->signature, &fieldstart, &fieldsize);
+
+  md5_init_ctx(&ctx);
+  md5_process_bytes(os->header, os->header_length, &ctx);
+  md5_process_bytes(os->data, os->length, &ctx);
+  md5_finish_ctx(&ctx, hash);
+
+  mpz_init(hashv);
+  mpz_init(sigv);
+
+  mpz_import(hashv, 16, -1, 1, 0, 0, hash);
+  rs_message(2, NULL, os, "hash = %ZX", hashv);
+
+  mpz_import(sigv, fieldsize, -1, 1, 0, 0, os->signature + fieldstart);
+  rs_message(2, NULL, os, "sig = %ZX", sigv);
+
+  e = rs_validate_rsa(sigv, hashv, key);
+  if (e == RS_SIGNATURE_INCORRECT)
+    rs_message(0, NULL, os, "OS signature incorrect");
+
+  mpz_clear(hashv);
+  mpz_clear(sigv);
+  return e;
+}
diff --git a/tool/rabbitsign-src/output.c b/tool/rabbitsign-src/output.c
new file mode 100644
index 0000000..6004b9b
--- /dev/null
+++ b/tool/rabbitsign-src/output.c
@@ -0,0 +1,39 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "rabbitsign.h"
+
+/*
+ * Write program contents to a file.
+ */
+int rs_write_program_file(const RSProgram* prgm, FILE* f,
+			  int month, int day, int year,
+			  unsigned int flags)
+{
+  if (rs_calc_is_ti8x(prgm->calctype)
+      && (prgm->datatype == RS_DATA_OS || prgm->datatype == RS_DATA_APP))
+    return rs_write_ti8x_file(prgm, f, month, day, year, flags);
+  else
+    return rs_write_ti9x_file(prgm, f, month, day, year, flags);
+}
diff --git a/tool/rabbitsign-src/output8x.c b/tool/rabbitsign-src/output8x.c
new file mode 100644
index 0000000..d08f061
--- /dev/null
+++ b/tool/rabbitsign-src/output8x.c
@@ -0,0 +1,248 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Write a single record to an Intel hex file.
+ */
+static int write_hex_record(FILE* outfile,	 /* output file */
+			    unsigned int nbytes, /* number of bytes */
+			    unsigned int addr,	 /* address */
+			    unsigned int type,	 /* record type */
+			    unsigned char* data, /* data */
+			    unsigned int flags,	 /* flags */
+			    int final)
+{
+  char buf[256];
+  unsigned int i;
+  unsigned int sum;
+
+  sum = nbytes + addr + (addr >> 8) + type;
+  sprintf(buf, ":%02X%04X%02X", nbytes, addr, type);
+
+  for (i = 0; i < nbytes; i++) {
+    sprintf(buf + 9 + 2 * i, "%02X", data[i]);
+    sum += data[i];
+  }
+
+  sum = ((-sum) & 0xff);
+
+  sprintf(buf + 9 + 2 * i, "%02X", sum);
+
+  if (!final) {
+    if (flags & RS_OUTPUT_APPSIGN)
+      strcpy(buf + 11 + 2 * i, "\n");
+    else
+      strcpy(buf + 11 + 2 * i, "\r\n");
+  }
+
+  if (fputs(buf, outfile) == EOF) {
+    rs_error(NULL, NULL, "file I/O error");
+    return RS_ERR_FILE_IO;
+  }
+
+  return RS_SUCCESS;
+}
+
+/*
+ * Write a chunk of data to an Intel hex file.
+ */
+static int write_hex_data(FILE* outfile,	/* output file */
+			  unsigned long length, /* number of bytes */
+			  unsigned long addr,	/* starting address */
+			  unsigned char* data,	/* data */
+			  unsigned int flags)
+{
+  unsigned int count;
+  int e;
+
+  while (length > 0) {
+    if (length < 0x20)
+      count = length;
+    else
+      count = 0x20;
+
+    if ((e = write_hex_record(outfile, count, addr, 0, data, flags, 0)))
+      return e;
+
+    length -= count;
+    addr += count;
+    data += count;
+  }
+
+  return RS_SUCCESS;
+}
+
+/*
+ * Write program to a .73k/.73u/.8xk/.8xu or .app file.
+ *
+ * If month = day = year = 0, use the current time.
+ *
+ * Note: on platforms where it matters, all output files must be
+ * opened in "binary" mode.
+ */
+int rs_write_ti8x_file(const RSProgram* prgm,  /* program */
+		       FILE* outfile,	       /* output file */
+		       int month,	       /* timestamp month */
+		       int day,		       /* timestamp day */
+		       int year,	       /* timestamp year*/
+		       unsigned int flags)     /* flags */
+{
+  const unsigned char *hdr;
+  unsigned long hdrstart, hdrsize, fieldstart, fieldsize;
+  int major, minor, i;
+  unsigned long npages, nrecords, hexsize;
+  char name[9];
+  unsigned int pagenum, addr;
+  unsigned long count;
+  unsigned char pnbuf[2];
+  int e;
+
+  if (!(flags & RS_OUTPUT_HEX_ONLY)) {
+    if (prgm->header_length) {
+      hdr = prgm->header;
+      hdrsize = prgm->header_length;
+    }
+    else {
+      hdr = prgm->data;
+      hdrsize = prgm->length;
+    }
+
+    if (hdrsize >= 6) {
+      rs_get_field_size(hdr, &hdrstart, NULL);
+      hdr += hdrstart;
+      hdrsize -= hdrstart;
+      if (hdrsize > 128)
+	hdrsize = 128;
+
+      major = rs_get_numeric_field(0x8020, hdr, hdrsize);
+      minor = rs_get_numeric_field(0x8030, hdr, hdrsize);
+
+      if (prgm->datatype == RS_DATA_OS) {
+	if (prgm->calctype == RS_CALC_TI73)
+	  strcpy(name, "BASECODE");
+	else
+	  strcpy(name, "basecode");
+      }
+      else if (!rs_find_app_field(0x8040, hdr, hdrsize,
+				  NULL, &fieldstart, &fieldsize)) {
+	if (fieldsize > 8)
+	  fieldsize = 8;
+	strncpy(name, (char*) hdr + fieldstart, fieldsize);
+	name[fieldsize] = 0;
+      }
+      else {
+	name[0] = 0;
+      }
+    }
+    else {
+      major = minor = 0;
+      name[0] = 0;
+    }
+
+    npages = ((prgm->length + 0x3fff) >> 14);
+    nrecords = 1 + npages + ((prgm->length + 0x1f) >> 5);
+
+    if (prgm->header_length)
+      nrecords += 1 + ((prgm->header_length + 0x1f) >> 5);
+    if (prgm->signature_length)
+      nrecords += 1 + ((prgm->signature_length + 0x1f) >> 5);
+
+    if (flags & RS_OUTPUT_APPSIGN) {
+      hexsize = (npages * 4
+		 + prgm->length * 2
+		 + prgm->header_length * 2
+		 + prgm->signature_length * 2
+		 + nrecords * 12 - 1);
+    }
+    else {
+      hexsize = (npages * 4
+		 + prgm->length * 2
+		 + prgm->header_length * 2
+		 + prgm->signature_length * 2
+		 + nrecords * 13 - 2);
+    }
+
+    if ((e = rs_write_tifl_header(outfile, 1, major, minor,
+				  month, day, year, name,
+				  prgm->calctype, prgm->datatype,
+				  hexsize)))
+      return e;
+  }
+
+  if (prgm->header_length) {
+    if ((e = write_hex_data(outfile, prgm->header_length, 0,
+			    prgm->header, flags)))
+      return e;
+    if ((e = write_hex_record(outfile, 0, 0, 1, NULL, flags, 0)))
+      return e;
+  }
+
+  for (i = 0; ((unsigned long) i << 14) < prgm->length; i++) {
+    if (i < prgm->npagenums)
+      pagenum = prgm->pagenums[i];
+    else
+      pagenum = i;
+
+    if (pagenum == 0 && prgm->header_length)
+      addr = 0;
+    else
+      addr = 0x4000;
+
+    pnbuf[0] = (pagenum >> 8) & 0xff;
+    pnbuf[1] = pagenum & 0xff;
+
+    if ((e = write_hex_record(outfile, 2, 0, 2, pnbuf, flags, 0)))
+      return e;
+
+    count = prgm->length - i * 0x4000;
+    if (count > 0x4000)
+      count = 0x4000;
+
+    if ((e = write_hex_data(outfile, count, addr,
+			    prgm->data + i * 0x4000, flags)))
+      return e;
+  }
+
+  if (prgm->signature_length) {
+    if ((e = write_hex_record(outfile, 0, 0, 1, NULL, flags, 0)))
+      return e;
+    if ((e = write_hex_data(outfile, prgm->signature_length, 0,
+			    prgm->signature, flags)))
+      return e;
+  }
+
+  return write_hex_record(outfile, 0, 0, 1, NULL, flags, 1);
+}
+
diff --git a/tool/rabbitsign-src/output9x.c b/tool/rabbitsign-src/output9x.c
new file mode 100644
index 0000000..d765abe
--- /dev/null
+++ b/tool/rabbitsign-src/output9x.c
@@ -0,0 +1,96 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Write program to a .89k/.89u/.9xk/.9xu file.
+ *
+ * If month = day = year = 0, use the current time.
+ *
+ * Note: on platforms where it matters, all output files must be
+ * opened in "binary" mode.
+ */
+int rs_write_ti9x_file(const RSProgram* prgm, /* program */
+		       FILE* outfile,	      /* output file */
+		       int month,	      /* timestamp month */
+		       int day,		      /* timestamp day */
+		       int year,	      /* timestamp year*/
+		       unsigned int flags RS_ATTR_UNUSED)
+{
+  const unsigned char *hdr;
+  unsigned long hdrstart, hdrsize, fieldstart, fieldsize;
+  char name[9];
+  int e;
+
+  if (prgm->length >= 6) {
+    rs_get_field_size(prgm->data, &hdrstart, &hdrsize);
+    hdr = prgm->data + hdrstart;
+    if (hdrsize > 128)
+      hdrsize = 128;
+
+    if (prgm->datatype == RS_DATA_OS) {
+      strcpy(name, "basecode");
+    }
+    else if (!rs_find_app_field(0x8140, hdr, hdrsize,
+				NULL, &fieldstart, &fieldsize)) {
+      if (fieldsize > 8)
+	fieldsize = 8;
+      strncpy(name, (char*) hdr + fieldstart, fieldsize);
+      name[fieldsize] = 0;
+    }
+    else {
+      name[0] = 0;
+    }
+  }
+  else {
+    name[0] = 0;
+  }
+
+  /* Note: the "version" header fields used in TI's 68k apps and
+     OSes seem to have no relation to the actual version numbers. */
+
+  if ((e = rs_write_tifl_header(outfile, 0, 0, 0,
+				month, day, year, name,
+				prgm->calctype, prgm->datatype,
+				prgm->length)))
+    return e;
+
+  if (fwrite(prgm->data, 1, prgm->length, outfile) != prgm->length) {
+    rs_error(NULL, NULL, "file I/O error");
+    return RS_ERR_FILE_IO;
+  }
+
+  return RS_SUCCESS;
+}
+
diff --git a/tool/rabbitsign-src/program.c b/tool/rabbitsign-src/program.c
new file mode 100644
index 0000000..e7cf140
--- /dev/null
+++ b/tool/rabbitsign-src/program.c
@@ -0,0 +1,187 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Create a new program.
+ */
+RSProgram* rs_program_new()
+{
+  RSProgram* prgm = rs_malloc(sizeof(RSProgram));
+
+  if (!prgm)
+    return NULL;
+
+  prgm->filename = NULL;
+  prgm->calctype = 0;
+  prgm->datatype = 0;
+  prgm->data = NULL;
+  prgm->length = 0;
+  prgm->length_a = 0;
+  prgm->header = NULL;
+  prgm->header_length = 0;
+  prgm->signature = NULL;
+  prgm->signature_length = 0;
+  prgm->pagenums = NULL;
+  prgm->npagenums = 0;
+
+  return prgm;
+}
+
+/*
+ * Create a new program by wrapping an existing data buffer.
+ *
+ * If buffer_size is zero, data will be copied from the source buffer
+ * into the new program.
+ *
+ * If buffer_size is nonzero, then the source buffer must have been
+ * allocated using malloc(); buffer_size is the total amount of memory
+ * allocated.  The data will not be copied, and the new program object
+ * assumes ownership of the buffer.
+ */
+RSProgram* rs_program_new_with_data(RSCalcType ctype, /* calc type */
+				    RSDataType dtype, /* data type */
+				    void* data,	      /* source buffer */
+				    unsigned long length, /* length of data */
+				    unsigned long buffer_size) /* amount of
+								  memory
+								  allocated */
+{
+  RSProgram* prgm = rs_program_new();
+
+  if (!prgm)
+    return NULL;
+
+  prgm->calctype = ctype;
+  prgm->datatype = dtype;
+
+  if (data) {
+    if (buffer_size) {
+      prgm->data = data;
+      prgm->length = length;
+      prgm->length_a = buffer_size;
+    }
+    else {
+      if (rs_program_append_data(prgm, data, length)) {
+	rs_program_free(prgm);
+	return NULL;
+      }
+    }
+  }
+
+  return prgm;
+}
+
+/*
+ * Free program data.
+ */
+void rs_program_free(RSProgram* prgm)
+{
+  if (!prgm)
+    return;
+
+  rs_free(prgm->filename);
+  rs_free(prgm->data);
+  rs_free(prgm->header);
+  rs_free(prgm->signature);
+  rs_free(prgm->pagenums);
+  rs_free(prgm);
+}
+
+/*
+ * Truncate or extend program.
+ *
+ * If length is less than the program's current length, the program is
+ * truncated.  If length is greater than the current size of the
+ * program, additional space is added.  The extra space is padded with
+ * 0xFF, with the exception of bytes that fall at the start of a page.
+ */
+int rs_program_set_length(RSProgram* prgm,	/* program */
+			  unsigned long length) /* new length of program */
+{
+  unsigned long length_a, i;
+  unsigned char* dptr;
+
+  if (length <= prgm->length) {
+    prgm->length = length;
+    return RS_SUCCESS;
+  }
+  else {
+    if (length > prgm->length_a) {
+      length_a = length + 16384;
+
+      dptr = rs_realloc(prgm->data, length_a);
+      if (!dptr)
+	return RS_ERR_OUT_OF_MEMORY;
+      prgm->data = dptr;
+      prgm->length_a = length_a;
+    }
+    
+    memset(prgm->data + prgm->length, 0xff, length - prgm->length);
+
+    for (i = ((prgm->length + 0x3fff) & ~0x3fff);
+	 i < length;
+	 i += 0x4000)
+      prgm->data[i] = 0x42;
+
+    prgm->length = length;
+    return RS_SUCCESS;
+  }
+}
+
+/*
+ * Add data to the end of the program.
+ */
+int rs_program_append_data(RSProgram* prgm,           /* program */
+			   const unsigned char* data, /* data */
+			   unsigned long length)      /* size of data */
+{
+  unsigned long nlength, length_a;
+  unsigned char* dptr;
+
+  nlength = prgm->length + length;
+  if (nlength > prgm->length_a) {
+    length_a = nlength + 16384;
+
+    dptr = rs_realloc(prgm->data, length_a);
+    if (!dptr)
+      return RS_ERR_OUT_OF_MEMORY;
+    prgm->data = dptr;
+    prgm->length_a = length_a;
+  }
+    
+  memcpy(prgm->data + prgm->length, data, length);
+  prgm->length = nlength;
+  return RS_SUCCESS;
+}
diff --git a/tool/rabbitsign-src/rabbitsign.c b/tool/rabbitsign-src/rabbitsign.c
new file mode 100644
index 0000000..94877de
--- /dev/null
+++ b/tool/rabbitsign-src/rabbitsign.c
@@ -0,0 +1,463 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+#if !defined(strcasecmp) && !defined(HAVE_STRCASECMP)
+# ifdef HAVE_STRICMP
+#  define strcasecmp stricmp
+# else
+#  define strcasecmp strcmp
+# endif
+#endif
+
+#if !defined(strrchr) && !defined(HAVE_STRRCHR) && defined(HAVE_RINDEX)
+# define strrchr rindex
+#endif
+
+static const char* getbasename(const char* f)
+{
+  const char *p;
+
+  if ((p = strrchr(f, '/')))
+    f = p + 1;
+
+#if defined(__MSDOS__) || defined(__WIN32__)
+  if ((p = strrchr(f, '\\')))
+    f = p + 1;
+#endif
+
+  return f;
+}
+
+static const char* usage[]={
+  "Usage: %s [options] app-file ...\n",
+  "Where options may include:\n",
+  "   -a:          appsign compatibility mode (Unix-style output)\n",
+  "   -b:          read raw binary data (default: auto-detect)\n",
+  "   -c:          check existing app signatures rather than signing\n",
+  "   -f:          force signing despite errors\n",
+  "   -g:          write app in GraphLink (XXk) format\n",
+  "   -k KEYFILE:  use specified key file\n",
+  "   -K NUM:      use specified key ID (hexadecimal)\n",
+  "   -n:          do not alter the app header\n",
+  "   -o OUTFILE:  write to specified output file (default is <name>.app\n",
+  "                or <name>.8xk)\n",
+  "   -p:          fix the pages field if found\n",
+  "   -P:          add an extra page if necessary\n",
+  "   -q:          suppress warning messages\n",
+  "   -r:          re-sign a previously signed app (i.e. strip off all\n",
+  "                data beyond that indicated by the size header)\n",
+  "   -R R:        specify the root to use (0, 1, 2, or 3) (default is 0)\n",
+  "   -t TYPE:     specify program type (e.g. 8xk, 73u)\n",
+  "   -u:          assume plain hex input is unsorted (default is sorted)\n",
+  "   -v:          be verbose (-vv for even more verbosity)\n",
+  "   --help:      describe options\n",
+  "   --version:   print version info\n",
+  NULL};
+
+int main(int argc, char** argv)
+{
+  unsigned int flags = (RS_INPUT_SORTED | RS_OUTPUT_HEX_ONLY);
+
+  int rootnum = 0;		/* which of the four valid signatures
+				   to generate
+
+				   0 = standard (r,s)
+				   1 = (-r,s)
+				   2 = (r,-s)
+				   3 = (-r,-s) */
+
+  int rawmode = 0;		/* 0 = fix app headers
+				   1 = sign "raw" data */
+
+  int valmode = 0;		/* 0 = sign apps
+				   1 = validate apps */
+
+  const char* infilename;	/* file name for input */
+
+  const char* outfilename = NULL; /* file name for output */
+
+  const char* keyfilename = NULL; /* file name for key */
+
+  int verbose = 0;		/* -1 = quiet (errors only)
+				   0 = default (warnings + errors)
+				   1 = verbose (print file names / status)
+				   2 = very verbose (details of computation) */
+
+  static const char optstring[] = "abcfgk:K:no:pPqrR:t:uv";
+  const char *progname;
+  int i, j, c, e;
+  const char* arg;
+  char *tempname;
+
+  FILE* infile;
+  FILE* outfile;
+  RSKey* key;
+  RSProgram* prgm;
+  unsigned long keyid = 0, appkeyid;
+  RSCalcType ctype = RS_CALC_UNKNOWN;
+  RSDataType dtype = RS_DATA_UNKNOWN;
+
+  char *ptr;
+  const char *ext;
+  int invalidapps = 0;
+
+  progname = getbasename(argv[0]);
+  rs_set_progname(progname);
+
+  if (argc == 1) {
+    fprintf(stderr, usage[0], progname);
+    for (i = 1; usage[i]; i++)
+      fputs(usage[i], stderr);
+    return 5;
+  }
+
+  i = j = 1;
+  while ((c = rs_parse_cmdline(argc, argv, optstring, &i, &j, &arg))) {
+    switch (c) {
+    case RS_CMDLINE_HELP:
+      printf(usage[0], progname);
+      for (i = 1; usage[i]; i++)
+	      fputs(usage[i], stdout);
+      return 0;
+
+    case RS_CMDLINE_VERSION:
+      printf("rabbitsign\n");
+      fputs("Copyright (C) 2009 Benjamin Moody\n", stdout);
+      fputs("This program is free software.  ", stdout);
+      fputs("There is NO WARRANTY of any kind.\n", stdout);
+      return 0;
+
+    case 'o':
+      outfilename = arg;
+      break;
+
+    case 'k':
+      keyfilename = arg;
+      break;
+
+    case 'K':
+      if (!sscanf(arg, "%lx", &keyid)) {
+	fprintf(stderr, "%s: -K: invalid argument %s\n", progname, arg);
+	return 5;
+      }
+      break;
+
+    case 'b':
+      flags |= RS_INPUT_BINARY;
+      break;
+
+    case 'u':
+      flags &= ~RS_INPUT_SORTED;
+      break;
+
+    case 'f':
+      flags |= RS_IGNORE_ALL_WARNINGS;
+      break;
+
+    case 'g':
+      flags &= ~RS_OUTPUT_HEX_ONLY;
+      break;
+
+    case 'a':
+      flags |= RS_OUTPUT_APPSIGN;
+      break;
+
+    case 'R':
+      if (!sscanf(arg, "%d", &rootnum)) {
+	fprintf(stderr, "%s: -R: invalid argument %s\n", progname, arg);
+	return 5;
+      }
+      break;
+
+    case 't':
+      if (rs_suffix_to_type(arg, &ctype, &dtype)) {
+	fprintf(stderr, "%s: unrecognized file type %s\n", progname, arg);
+	return 5;
+      }
+      break;
+
+    case 'n':
+      rawmode = 1;
+      break;
+ 
+    case 'r':
+      flags |= RS_REMOVE_OLD_SIGNATURE;
+      break;
+
+    case 'P':
+      flags |= RS_ZEALOUSLY_PAD_APP;
+      break;
+
+    case 'p':
+      flags |= RS_FIX_PAGE_COUNT;
+      break;
+
+    case 'c':
+      valmode = 1;
+      break;
+
+    case 'v':
+      verbose++;
+      break;
+
+    case 'q':
+      verbose--;
+      break;
+
+    case RS_CMDLINE_FILENAME:
+      break;
+
+    case RS_CMDLINE_ERROR:
+      return 5;
+
+    default:
+      fprintf(stderr, "%s: internal error: unknown option -%c\n",
+	      progname, c);
+      abort();
+    }
+  }
+
+  rs_set_verbose(verbose);
+
+  if (outfilename && (ptr = strrchr(outfilename, '.'))
+      && !rs_suffix_to_type(ptr + 1, NULL, NULL))
+    flags &= ~RS_OUTPUT_HEX_ONLY;
+
+
+  /* Read key file (if manually specified) */
+
+  key = rs_key_new();
+
+  if (keyfilename) {
+    infile = fopen(keyfilename, "rb");
+    if (!infile) {
+      perror(keyfilename);
+      rs_key_free(key);
+      return 3;
+    }
+    if (rs_read_key_file(key, infile, keyfilename, 1)) {
+      fclose(infile);
+      rs_key_free(key);
+      return 3;
+    }
+    fclose(infile);
+  }
+  else if (keyid) {
+    if (rs_key_find_for_id(key, keyid, valmode)) {
+      rs_key_free(key);
+      return 3;
+    }
+  }
+
+  /* Process applications */
+
+  i = j = 1;
+  while ((c = rs_parse_cmdline(argc, argv, optstring, &i, &j, &arg))) {
+    if (c != RS_CMDLINE_FILENAME)
+      continue;
+
+    /* Read input file */
+
+    if (strcmp(arg, "-")) {
+      infilename = arg;
+      infile = fopen(arg, "rb");
+      if (!infile) {
+	perror(arg);
+	rs_key_free(key);
+	return 4;
+      }
+    }
+    else {
+      infilename = "(standard input)";
+      infile = stdin;
+    }
+
+    prgm = rs_program_new();
+
+    if (ctype && dtype) {
+      prgm->calctype = ctype;
+      prgm->datatype = dtype;
+    }
+    else if ((ptr = strrchr(infilename, '.'))) {
+      rs_suffix_to_type(ptr + 1, &prgm->calctype, &prgm->datatype);
+    }
+
+    if (rs_read_program_file(prgm, infile, infilename, flags)) {
+      rs_program_free(prgm);
+      rs_key_free(key);
+      if (infile != stdin)
+	fclose(infile);
+      return 4;
+    }
+    if (infile != stdin)
+      fclose(infile);
+
+    /* Read key file (if automatic) */
+
+    if (!keyfilename && !keyid) {
+      appkeyid = rs_program_get_key_id(prgm);
+      if (!appkeyid) {
+	fprintf(stderr, "%s: unable to determine key ID\n", infilename);
+	rs_program_free(prgm);
+	rs_key_free(key);
+	return 3;
+      }
+
+      if (appkeyid != key->id) {
+	if (rs_key_find_for_id(key, appkeyid, valmode)) {
+	  rs_program_free(prgm);
+	  rs_key_free(key);
+	  return 3;
+	}
+      }
+    }
+
+    if (valmode) {
+      /* Validate application */
+      if (verbose > 0)
+	fprintf(stderr, "Validating %s %s %s...\n",
+		rs_calc_type_to_string(prgm->calctype),
+		rs_data_type_to_string(prgm->datatype),
+		infilename);
+
+      if (rs_validate_program(prgm, key))
+	invalidapps++;
+    }
+    else {
+      /* Sign application */
+      if (verbose > 0)
+	fprintf(stderr, "Signing %s %s %s...\n",
+		rs_calc_type_to_string(prgm->calctype),
+		rs_data_type_to_string(prgm->datatype),
+		infilename);
+
+      if (!rawmode) {
+	if ((e = rs_repair_program(prgm, flags))) {
+	  if (!(flags & RS_IGNORE_ALL_WARNINGS)
+	      && e < RS_ERR_CRITICAL)
+	    fprintf(stderr, "(use -f to override)\n");
+	  rs_program_free(prgm);
+	  rs_key_free(key);
+	  return 2;
+	}
+      }
+      if (rs_sign_program(prgm, key, rootnum)) {
+	rs_program_free(prgm);
+	rs_key_free(key);
+	return 2;
+      }
+
+      /* Generate output file name */
+
+      if (outfilename) {
+	if (strcmp(outfilename, "-")) {
+	  outfile = fopen(outfilename, "wb");
+	  if (!outfile) {
+	    perror(outfilename);
+	    rs_program_free(prgm);
+	    rs_key_free(key);
+	    return 4;
+	  }
+	}
+	else {
+	  outfile = stdout;
+	}
+      }
+      else if (infile == stdin) {
+	outfile = stdout;
+      }
+      else {
+	ext = rs_type_to_suffix(prgm->calctype, prgm->datatype,
+				(flags & RS_OUTPUT_HEX_ONLY));
+
+	tempname = rs_malloc(strlen(infilename) + 32);
+	if (!tempname) {
+	  rs_program_free(prgm);
+	  rs_key_free(key);
+	  return 4;
+	}
+	strcpy(tempname, infilename);
+
+	ptr = strrchr(tempname, '.');
+	if (!ptr) {
+	  strcat(tempname, ".");
+	  strcat(tempname, ext);
+	}
+	else if (strcasecmp(ptr + 1, ext)) {
+	  strcpy(ptr + 1, ext);
+	}
+	else {
+	  strcpy(ptr, "-signed.");
+	  strcat(ptr, ext);
+	}
+
+	outfile = fopen(tempname, "wb");
+	if (!outfile) {
+	  perror(tempname);
+	  rs_free(tempname);
+	  rs_program_free(prgm);
+	  rs_key_free(key);
+	  return 4;
+	}
+	rs_free(tempname);
+      }
+
+      /* Write signed application to output file */
+
+      if (rs_write_program_file(prgm, outfile, 0, 0, 0, flags)) {
+	if (outfile != stdout)
+	  fclose(outfile);
+	rs_program_free(prgm);
+	rs_key_free(key);
+	return 4;
+      }
+
+      if (outfile != stdout)
+	fclose(outfile);
+    }
+    rs_program_free(prgm);
+  }
+
+  rs_key_free(key);
+
+  if (invalidapps)
+    return 1;
+  else
+    return 0;
+}
diff --git a/tool/rabbitsign-src/rabbitsign.h b/tool/rabbitsign-src/rabbitsign.h
new file mode 100644
index 0000000..6c2b7b1
--- /dev/null
+++ b/tool/rabbitsign-src/rabbitsign.h
@@ -0,0 +1,342 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __RABBITSIGN_H__
+#define __RABBITSIGN_H__
+
+#ifdef HAVE_GMP_H
+# include <gmp.h>
+# define rs_snprintf gmp_snprintf
+# define rs_vsnprintf gmp_vsnprintf
+#else
+# include "mpz.h"
+#endif
+
+#if __GNUC__ >= 3
+# define RS_ATTR_PURE __attribute__((pure))
+# define RS_ATTR_MALLOC __attribute__((malloc))
+# define RS_ATTR_UNUSED __attribute__((unused))
+# define RS_ATTR_PRINTF(f,i) __attribute__((format(printf,f,i)))
+#else
+# define RS_ATTR_PURE
+# define RS_ATTR_MALLOC
+# define RS_ATTR_UNUSED
+# define RS_ATTR_PRINTF(f,i)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Calculator types */
+typedef enum _RSCalcType {
+  RS_CALC_UNKNOWN = 0,
+  RS_CALC_TI73    = 0x74,
+  RS_CALC_TI83P   = 0x73,
+  RS_CALC_TI89    = 0x98,
+  RS_CALC_TI92P   = 0x88
+} RSCalcType;
+
+#define rs_calc_is_ti8x(ttt) ((ttt) == RS_CALC_TI73 || (ttt) == RS_CALC_TI83P)
+#define rs_calc_is_ti9x(ttt) ((ttt) == RS_CALC_TI89 || (ttt) == RS_CALC_TI92P)
+
+/* Data types */
+typedef enum _RSDataType {
+  RS_DATA_UNKNOWN = 0,
+  RS_DATA_OS      = 0x23,
+  RS_DATA_APP     = 0x24,
+  RS_DATA_CERT    = 0x25
+} RSDataType;
+
+/* Flags for app signing */
+typedef enum _RSRepairFlags {
+  RS_IGNORE_ALL_WARNINGS     = 1,
+  RS_REMOVE_OLD_SIGNATURE    = 2, /* Remove existing signature */
+  RS_FIX_PAGE_COUNT          = 4, /* Fix page count header field */
+  RS_FIX_OS_SIZE             = 8, /* Fix size in OS header */
+  RS_ZEALOUSLY_PAD_APP       = 16 /* Pad application with an extra
+                                     page if necessary */
+} RSRepairFlags;
+
+/* Flags for file input */
+typedef enum _RSInputFlags {
+  RS_INPUT_BINARY            = 32, /* Assume input is raw binary
+                                      data */
+  RS_INPUT_SORTED            = 64  /* Assume plain hex input is sorted
+                                      (implicit page switch) */
+} RSInputFlags;
+
+/* Flags for file output */
+typedef enum _RSOutputFlags {
+  RS_OUTPUT_HEX_ONLY         = 128, /* Write plain hex (.app) format */
+  RS_OUTPUT_APPSIGN          = 256  /* Write hex data in
+                                       appsign-compatible format */
+} RSOutputFlags;
+
+/* Encryption key structure */
+typedef struct _RSKey {
+  char* filename;               /* Filename */
+  unsigned long id;             /* Key ID */
+  mpz_t n;                      /* Modulus (public key) */
+  mpz_t p;                      /* First factor */
+  mpz_t q;                      /* Second factor */
+  mpz_t qinv;                   /* q^-1 mod p (for Rabin)
+                                   (rs_sign_rabin() will calculate
+                                   this based on p and q, if
+                                   needed) */
+  mpz_t d;                      /* Signing exponent (for RSA)
+                                   (rs_sign_rsa() will calculate this
+                                   based on p and q, if needed) */
+} RSKey;
+
+/* Program data structure */
+typedef struct _RSProgram {
+  char* filename;                /* Filename */
+  RSCalcType calctype;           /* Calculator type */
+  RSDataType datatype;           /* Program data type */
+  unsigned char* data;           /* Program data */
+  unsigned long length;          /* Length of program data */
+  unsigned long length_a;        /* Size of buffer allocated */
+
+  /* Additional metadata (only used by TI-8x OS) */
+  unsigned char* header;         /* OS header */
+  unsigned int header_length;    /* Length of OS header */
+  unsigned char* signature;      /* OS signature */
+  unsigned int signature_length; /* Length of OS signature */
+  unsigned int* pagenums;        /* List of page numbers */
+  int npagenums;                 /* Number of page numbers */
+} RSProgram;
+
+/* Status codes */
+typedef enum _RSStatus {
+  RS_SUCCESS = 0,
+
+  RS_ERR_MISSING_PAGE_COUNT,
+  RS_ERR_MISSING_KEY_ID,
+  RS_ERR_MISSING_DATE_STAMP,
+  RS_ERR_MISSING_PROGRAM_IMAGE,
+  RS_ERR_MISALIGNED_PROGRAM_IMAGE,
+  RS_ERR_INVALID_PROGRAM_DATA,
+  RS_ERR_INVALID_PROGRAM_SIZE,
+  RS_ERR_INCORRECT_PAGE_COUNT,
+  RS_ERR_FINAL_PAGE_TOO_LONG,
+  RS_ERR_FIELD_TOO_SMALL,
+
+  RS_ERR_CRITICAL = 1000,
+
+  RS_ERR_OUT_OF_MEMORY,
+  RS_ERR_FILE_IO,
+  RS_ERR_HEX_SYNTAX,
+  RS_ERR_UNKNOWN_FILE_FORMAT,
+  RS_ERR_UNKNOWN_PROGRAM_TYPE,
+  RS_ERR_MISSING_HEADER,
+  RS_ERR_MISSING_RABIN_SIGNATURE,
+  RS_ERR_MISSING_RSA_SIGNATURE,
+  RS_ERR_INCORRECT_PROGRAM_SIZE,
+  RS_ERR_KEY_NOT_FOUND,
+  RS_ERR_KEY_SYNTAX,
+  RS_ERR_INVALID_KEY,
+  RS_ERR_MISSING_PUBLIC_KEY,
+  RS_ERR_MISSING_PRIVATE_KEY,
+  RS_ERR_UNSUITABLE_RABIN_KEY,
+  RS_ERR_UNSUITABLE_RSA_KEY,
+
+  RS_SIGNATURE_INCORRECT = -1
+} RSStatus;
+
+
+/**** Key handling (keys.c) ****/
+
+/* Create a new key. */
+RSKey* rs_key_new (void) RS_ATTR_MALLOC;
+
+/* Free a key. */
+void rs_key_free (RSKey* key);
+
+/* Read key from a file. */
+RSStatus rs_read_key_file (RSKey* key, FILE* f,
+			   const char* fname, int verify);
+
+/* Parse a number written in TI's hexadecimal key format. */
+RSStatus rs_parse_key_value (mpz_t dest, const char* str);
+
+
+/**** Program data manipulation (program.c) ****/
+
+/* Create a new program. */
+RSProgram* rs_program_new (void) RS_ATTR_MALLOC;
+
+/* Create a new program from an existing data buffer. */
+RSProgram* rs_program_new_with_data (RSCalcType ctype, RSDataType dtype,
+				     void* data, unsigned long length,
+				     unsigned long buffer_size)
+  RS_ATTR_MALLOC;
+
+/* Free program data. */
+void rs_program_free (RSProgram* prgm);
+
+/* Truncate or extend program. */
+RSStatus rs_program_set_length (RSProgram* prgm, unsigned long length);
+
+/* Add data to the end of the program. */
+RSStatus rs_program_append_data (RSProgram* prgm, const unsigned char* data,
+				 unsigned long length);
+
+
+/**** Search for key file (autokey.c) ****/
+
+/* Get key ID for the given program. */
+unsigned long rs_program_get_key_id (const RSProgram* prgm) RS_ATTR_PURE;
+
+/* Find key file for the given ID. */
+RSStatus rs_key_find_for_id (RSKey* key, unsigned long keyid, int publiconly);
+
+
+/**** Program signing and validation (apps.c) ****/
+
+/* Check/fix program header and data. */
+RSStatus rs_repair_program (RSProgram* prgm, RSRepairFlags flags);
+
+/* Add a signature to the program. */
+RSStatus rs_sign_program (RSProgram* prgm, RSKey* key, int rootnum);
+
+/* Validate program signature. */
+RSStatus rs_validate_program (const RSProgram* prgm, const RSKey* key);
+
+
+/**** TI-73/83+/84+ app signing (app8x.c) ****/
+
+/* Check/fix Flash app header and data. */
+RSStatus rs_repair_ti8x_app (RSProgram* app, RSRepairFlags flags);
+
+/* Add a signature to a Flash app. */
+RSStatus rs_sign_ti8x_app (RSProgram* app, RSKey* key, int rootnum);
+
+/* Validate Flash app signature. */
+RSStatus rs_validate_ti8x_app (const RSProgram* app, const RSKey* key);
+
+
+/**** TI-73/83+/84+ OS signing (os8x.c) ****/
+
+/* Check/fix OS header and data. */
+RSStatus rs_repair_ti8x_os (RSProgram* os, RSRepairFlags flags);
+
+/* Add a signature to an OS. */
+RSStatus rs_sign_ti8x_os (RSProgram* os, RSKey* key);
+
+/* Validate OS signature. */
+RSStatus rs_validate_ti8x_os (const RSProgram* os, const RSKey* key);
+
+
+/**** TI-89/92+ app/OS signing (app9x.c) ****/
+
+/* Check/fix Flash app header and data. */
+RSStatus rs_repair_ti9x_app (RSProgram* app, RSRepairFlags flags);
+
+/* Check/fix OS header and data. */
+RSStatus rs_repair_ti9x_os (RSProgram* app, RSRepairFlags flags);
+
+/* Add a signature to a 68k app/OS. */
+RSStatus rs_sign_ti9x_app (RSProgram* app, RSKey* key);
+
+/* Validate app/OS signature. */
+RSStatus rs_validate_ti9x_app (const RSProgram* app, const RSKey* key);
+
+#define rs_sign_ti9x_os rs_sign_ti9x_app
+#define rs_validate_ti9x_os rs_validate_ti9x_app
+
+
+/**** File input (input.c) ****/
+
+/* Read program contents from a file. */
+RSStatus rs_read_program_file (RSProgram* prgm, FILE* f,
+			       const char* fname, RSInputFlags flags);
+
+
+/**** File output (output.c) ****/
+
+/* Write program contents to a file. */
+RSStatus rs_write_program_file(const RSProgram* prgm, FILE* f,
+			       int month, int day, int year,
+			       RSOutputFlags flags);
+
+
+/**** Hex file output (output8x.c) ****/
+
+/* Write program to a .73k/.73u/.8xk/.8xu or .app file. */
+RSStatus rs_write_ti8x_file (const RSProgram* prgm, FILE* f,
+			     int month, int day, int year,
+			     RSOutputFlags flags);
+
+
+/**** Binary file output (output9x.c) ****/
+
+/* Write program to a .89k/.89u/.9xk/.9xu file. */
+RSStatus rs_write_ti9x_file (const RSProgram* prgm, FILE* f,
+			     int month, int day, int year,
+			     RSOutputFlags flags);
+
+
+/**** App header/certificate utility functions (header.c) ****/
+
+/* Get length of a header field. */
+void rs_get_field_size (const unsigned char* data,
+			unsigned long* fieldstart,
+			unsigned long* fieldsize);
+
+/* Set length of a header field. */
+int rs_set_field_size (unsigned char* data,
+		       unsigned long fieldsize);
+
+/* Find a given header field in the data. */
+int rs_find_app_field (unsigned int type,
+		       const unsigned char* data,
+		       unsigned long length,
+		       unsigned long* fieldhead,
+		       unsigned long* fieldstart,
+		       unsigned long* fieldsize);
+
+/* Get value of a numeric header field. */
+unsigned long rs_get_numeric_field (unsigned int type,
+				    const unsigned char* data,
+				    unsigned long length) RS_ATTR_PURE;
+
+
+/**** Error/message logging (error.c) ****/
+  
+typedef void (*RSMessageFunc) (const RSKey*, const RSProgram*,
+			       const char*, void*);
+
+/* Set program name */
+void rs_set_progname (const char* s);
+
+/* Set verbosity level */
+void rs_set_verbose (int v);
+
+/* Set error logging function */
+void rs_set_error_func (RSMessageFunc func, void* data);
+
+/* Set message logging function */
+void rs_set_message_func (RSMessageFunc func, void* data);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __RABBITSIGN_H__ */
diff --git a/tool/rabbitsign-src/rabin.c b/tool/rabbitsign-src/rabin.c
new file mode 100644
index 0000000..aaa599c
--- /dev/null
+++ b/tool/rabbitsign-src/rabin.c
@@ -0,0 +1,425 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Compute square root of x modulo p, where p === 3 (mod 4).
+ *
+ * (Assume that (x|p) = 1.)
+ *
+ * Notice that:
+ *
+ *  p = 4k + 3
+ *
+ *  x^[(p-1)/2] = x^(2k+1) = (x|p) = 1
+ *
+ *  x^(2k+2) = x
+ *
+ *  [x^(k+1)]^2 = x
+ *
+ *  so x^(k+1) = x^[(p+1)/4] is a square root of x.
+ */
+static void mpz_sqrtm_3 (mpz_t res,     /* mpz to store result */
+			 const mpz_t x, /* number to get square root of */
+			 const mpz_t p) /* prime modulus === 3 (mod 4) */
+{
+  mpz_add_ui(res, p, 1);
+  mpz_fdiv_q_2exp(res, res, 2);	/* (p + 1)/4 */
+  mpz_powm(res, x, res, p);
+}
+
+
+/*
+ * Compute square root of x modulo p, where p === 5 (mod 8).
+ *
+ * (Assume that (x|p) = 1.)
+ *
+ * Notice that:
+ *
+ *  p = 4k + 1
+ *
+ *  x^[(p-1)/2] = x^(2k) = (x|p) = 1
+ *
+ *  x^[(k+1)/2]^2 * x^(4k-1) = x^(5k) = x^k
+ *
+ *  Since x^k^2 = 1, x^k = +/- 1.
+ *
+ *  CASE 1:
+ *    If x^k = 1, x^[(k+1)/2]^2 = x, so x^[(k+1)/2] = x^[(p+3)/8] is
+ *    the square root of x.
+ *
+ *  CASE 2:
+ *    Otherwise, x^[(k+1)/2]^2 = -x; we need to find a square root of
+ *    -1.
+ *
+ *    Since (2|p) = -1, 2^[(p-1)/2] = 2^(2k) = -1, so (2^k)^2 = -1
+ *
+ *    (x^[(k+1)/2] * 2^k)^2 = -x * -1 = x
+ *
+ *    so x^[(k+1)/2] * 2^k = x^[(p+3)/8] * 2^[(p-1)/4] is the square
+ *    root of x.
+ */
+static void mpz_sqrtm_5 (mpz_t res,	/* mpz to store result */
+			 const mpz_t x,	/* number to get square root of */
+			 const mpz_t p)	/* prime modulus === 5 (mod 8) */
+{
+  mpz_t a, b;
+  mpz_init(a);
+  mpz_init(b);
+
+  mpz_add_ui(a, p, 3);
+  mpz_fdiv_q_2exp(b, a, 3);
+  mpz_powm(res, x, b, p);	/* x ^ (p+3)/8 */
+
+  /* Check if res^2 = x */
+  mpz_mul(a, res, res);
+  mpz_sub(b, a, x);
+  mpz_mod(a, b, p);
+
+  if (0 != mpz_sgn(a)) {
+    mpz_sub_ui(a, p, 1);
+    mpz_fdiv_q_2exp(b, a, 2);
+    mpz_set_ui(a, 2);
+    mpz_powm(a, a, b, p);	/* 2 ^ (p-1)/4 */
+    mpz_mul(res, res, a);
+  }
+
+  mpz_clear(a);
+  mpz_clear(b);
+}
+
+
+/*
+ * Compute square root of x modulo p.
+ *
+ * This still won't work with p === 1 mod 8, but then, TI's system
+ * won't work at all for 50% of apps if one of your factors is 1 mod
+ * 8.  (See the discussion of f values below.)
+ *
+ */
+static void mpz_sqrtm (mpz_t res,      /* mpz to store result */
+		       const mpz_t x,  /* number to get square root of */
+		       const mpz_t p)  /* prime modulus === 3, 5, or 7
+					  (mod 8) */
+{
+  if ((mpz_get_ui(p) % 8) == 5)
+    mpz_sqrtm_5(res, x, p);
+  else 
+    mpz_sqrtm_3(res, x, p);
+}
+
+
+/*
+ * Compute x s.t. x === r (mod p) and x === s (mod q).
+ *
+ * We compute this as:
+ *
+ *  [(r-s) * q^-1 mod p] * q + s
+ *
+ */
+static void mpz_crt(mpz_t res,	      /* mpz to store result */
+		    const mpz_t r,    /* root modulo p */
+		    const mpz_t s,    /* root modulo q */
+		    const mpz_t p,    /* first modulus */
+		    const mpz_t q,    /* second modulus */
+		    const mpz_t qinv) /* q^(p-2) mod p */
+{
+  /* ((r - s) */
+  mpz_sub(res, r, s);
+
+  /* * q^-1) */
+  mpz_mul(res, res, qinv);
+  mpz_mod(res, res, p);
+
+  /* * q + s */
+  mpz_mul(res, res, q);
+  mpz_add(res, res, s);
+}
+
+/*
+ * Compute the T_f transform modulo n.
+ *
+ * Because only one quarter of the possible hashes can be signed with
+ * a given key, we need to transform the hash.  First, we want to
+ * ensure that the result is nonzero, so we shift the hash by 8 bits
+ * and add a 1 to the end.  The resulting number is called m'.
+ *
+ * Second, we want to multiply it by a number k whose Legendre symbols
+ * (k|p) and (k|q) are known, so that (km'|p) = (k|p)(m'|p) = 1 and
+ * (km'|q) = (k|q)(km'|q) = 1.  Since we need both to be true
+ * simultaneously, regardless of the values of (m'|p) and (m'|q), we
+ * clearly need four possible values of k.
+ *
+ * As it happens, TI's keys all follow a precise format: they all have
+ * p === 3 and q === 7 (mod 8).  As a result, we know that
+ *
+ *  (-1|p) = (-1|q) = -1
+ *
+ *  (2|p) = -1, (2|q) = 1
+ *
+ * So TI has defined the following transformation functions:
+ *
+ *  T_0(x) = -2x'
+ *  T_1(x) = -x'
+ *  T_2(x) = x'
+ *  T_3(x) = 2x'
+ *
+ * where x' = 256x + 1.
+ *
+ * In the usual case of p === 3 and q === 7 (mod 8), then, two of the
+ * possible (T_f(m)|p) will equal 1:
+ *
+ *  If (m'|p) = 1, then (T_0(m)|p) = (T_2(m)|p) = 1.
+ *  If (m'|p) = -1, then (T_1(m)|p) = (T_3(m)|p) = 1.
+ *
+ * Two of the possible (T_f(m)|q) will equal 1:
+ *
+ *  If (m'|q) = 1, then (T_2(m)|q) = (T_3(m)|q) = 1.
+ *  If (m'|q) = -1, then (T_0(m)|q) = (T_1(m)|q) = 1.
+ *
+ * Thus we can choose exactly one f value with
+ * (T_f(m)|p) = (T_f(m)|q) = 1.
+ *
+ * If r === 5 (mod 8) is a prime, (-1|r) = 1, while (2|r) = -1.  Thus
+ * a similar logic holds:
+ *
+ *  If (m'|r) = 1, then (T_1(m)|r) = (T_2(m)|r) = 1.
+ *  If (m'|r) = -1, then (T_0(m)|r) = (T_3(m)|r) = 1.
+ *
+ * So if {p,q} === {3,5}, {5,7}, or {3,7} (mod 8), given any m, we can
+ * pick an f with (T_f(m)|p) = (T_f(m)|q) = 1.
+ *
+ */
+static void applyf(mpz_t res,	  /* mpz to store result */
+		   const mpz_t m, /* MD5 hash */
+		   const mpz_t n, /* public key */
+		   int f)	  /* f (0, 1, 2, 3) */
+{
+  mpz_mul_ui(res, m, 256);
+  mpz_add_ui(res, res, 1);
+
+  switch (f) {
+  case 0:
+    mpz_add(res, res, res);
+  case 1:
+    mpz_sub(res, n, res);
+    break;
+  case 2:
+    break;
+  case 3:
+    mpz_add(res, res, res);
+    break;
+  }
+}
+
+/*
+ * Compute the Rabin signature with a given f.
+ */
+static void rabsigf(mpz_t res,	      /* mpz to store result */
+		    const mpz_t m,    /* MD5 hash */
+		    const mpz_t n,    /* public key */
+		    const mpz_t p,    /* first factor */
+		    const mpz_t q,    /* second factor */
+		    const mpz_t qinv, /* q^(p-2) mod p */
+		    int f,	      /* f (0, 1, 2, 3) */
+		    int rootnum)      /* root number (0, 1, 2, 3) */
+{
+  mpz_t mm;
+  mpz_t r,s;
+
+  mpz_init(r);
+  mpz_init(s);
+  mpz_init(mm);
+
+  applyf(mm, m, n, f);
+
+  mpz_sqrtm(r, mm, p);
+  mpz_sqrtm(s, mm, q);
+
+  if (rootnum & 1) {
+    mpz_sub(r, p, r);
+  }
+
+  if (rootnum & 2) {
+    mpz_sub(s, q, s);
+  }
+
+  mpz_crt(res, r, s, p, q, qinv);
+
+  mpz_clear(r);
+  mpz_clear(s);
+  mpz_clear(mm);
+}
+
+/* 
+ * Table of f values. 
+ *
+ * Remember that
+ *
+ * f = 0 corresponds to multiplying by -2
+ * f = 1 corresponds to multiplying by -1
+ * f = 2 corresponds to multiplying by 1
+ * f = 3 corresponds to multiplying by 2
+ */
+static const int ftab[36] = {
+  /************* (m'|p) = (m'|q) = 1 */
+     /********** (m'|p) = -1, (m'|q) = 1 */
+         /****** (m'|p) = 1, (m'|q) = -1 */
+            /*** (m'|p) = (m'|q) = -1 */
+
+  /* p === 3, q === 3 */
+  2, 99, 99,1,  /* (-1|p) = (-1|q) = -1     ==> if both -1, multiply by -1 */
+
+  /* p === 3, q === 5 */
+  2, 1,  0, 3,  /* (-1|p) = -1, (-1|q) = 1  ==> if (m'|p) = -1, multiply by -1 */
+                /* (-2|p) = 1, (-2|q) = -1  ==> if (m'|q) = -1, multiply by -2 */
+
+  /* p === 3, q === 7 */
+  2, 3,  0, 1,  /* (2|p) = -1, (2|q) = 1    ==> if (m'|p) = -1, multiply by 2 */
+                /* (-2|p) = 1, (-2|q) = -1  ==> if (m'|q) = -1, multiply by -2 */
+
+  /* p === 5, q === 3 */
+  2, 0,  1, 3,
+
+  /* p === 5, q === 5 */
+  2, 99, 99,3,  /* (2|p) = (2|q) = -1       ==> if both -1, multiply by 2 */
+
+  /* p === 5, q === 7 */
+  2, 3,  1, 0,  /* (2|p) = -1, (2|q) = 1    ==> if (m'|p) = -1, multiply by 2 */
+                /* (-1|p) = 1, (-1|q) = -1  ==> if (m'|q) = -1, multiply by -1 */
+
+  /* p === 7, q === 3 */
+  2, 0,  3, 1,
+
+  /* p === 7, q === 5 */
+  2, 1,  3, 0,
+
+  /* p === 7, q === 7 */
+  2, 99, 99,1   /* (-1|p) = (-1|q) = -1     ==> if both -1, multiply by -1 */
+};
+
+/*
+ * Compute the Rabin signature and the useful value of f.
+ */
+int rs_sign_rabin(mpz_t res,	        /* mpz to store signature */
+		  int* f,	        /* f value chosen */
+		  const mpz_t hash,	/* MD5 hash of app */
+		  int rootnum,		/* root number (0, 1, 2, 3) */
+		  RSKey* key)		/* key structure */
+{
+  mpz_t mm;
+  int mLp, mLq;
+  int pm8, qm8;
+
+  if (!mpz_sgn(key->n)) {
+    rs_error(key, NULL, "unable to sign: public key missing");
+    return RS_ERR_MISSING_PUBLIC_KEY;
+  }
+
+  if (!mpz_sgn(key->p) || !mpz_sgn(key->q)) {
+    rs_error(key, NULL, "unable to sign: private key missing");
+    return RS_ERR_MISSING_PRIVATE_KEY;
+  }
+
+  mpz_init(mm);
+
+  /* Calculate q^-1 if necessary */
+
+  if (!mpz_sgn(key->qinv)) {
+#ifndef USE_MPZ_GCDEXT
+    mpz_sub_ui(mm, key->p, 2);
+    mpz_powm(key->qinv, key->q, mm, key->p);
+#else
+    mpz_gcdext(mm, key->qinv, NULL, key->q, key->p);
+    if (mpz_cmp_ui(mm, 1)) {
+      mpz_clear(mm);
+      rs_error(key, NULL, "unable to sign: unsuitable key");
+      return RS_ERR_UNSUITABLE_RABIN_KEY;
+    }
+#endif
+  }
+
+  applyf(mm, hash, key->n, 2);
+
+  mLp = mpz_legendre(mm, key->p);
+  mLq = mpz_legendre(mm, key->q);
+
+  pm8 = mpz_get_ui(key->p) % 8;
+  qm8 = mpz_get_ui(key->q) % 8;
+
+  if (pm8 == 1 || qm8 == 1 || (pm8 % 2) == 0 || (qm8 % 2) == 0) {
+    mpz_clear(mm);
+    rs_error(key, NULL, "unable to sign: unsuitable key");
+    return RS_ERR_UNSUITABLE_RABIN_KEY;
+  }
+
+  *f = ftab[(mLp == 1 ? 0 : 1) +
+	    (mLq == 1 ? 0 : 2) +
+	    (((qm8 - 3) / 2) * 4) +
+	    (((pm8 - 3) / 2) * 12)];
+
+  if (*f == 99) {
+    mpz_clear(mm);
+    rs_error(key, NULL, "unable to sign: unsuitable key");
+    return RS_ERR_UNSUITABLE_RABIN_KEY;
+  }
+
+  rabsigf(res, hash, key->n, key->p, key->q, key->qinv, *f, rootnum);
+  mpz_clear(mm);
+  return RS_SUCCESS;
+}
+
+/* Check that the given Rabin signature is valid. */
+int rs_validate_rabin (const mpz_t sig,  /* purported signature of app */
+		       int f,		 /* f value */
+		       const mpz_t hash, /* MD5 hash of app */
+                       const RSKey* key) /* key structure */
+{
+  mpz_t a, b;
+  int result;
+
+  if (!mpz_sgn(key->n)) {
+    rs_error(key, NULL, "unable to validate: public key missing");
+    return RS_ERR_MISSING_PUBLIC_KEY;
+  }
+
+  if (f < 0 || f > 3)
+    return RS_SIGNATURE_INCORRECT;
+
+  mpz_init(a);
+  mpz_init(b);
+
+  mpz_mul(a, sig, sig);
+  mpz_mod(a, a, key->n);
+
+  applyf(b, hash, key->n, f);
+
+  result = mpz_cmp(a, b);
+
+  mpz_clear(a);
+  mpz_clear(b);
+  return (result ? RS_SIGNATURE_INCORRECT : RS_SUCCESS);
+}
diff --git a/tool/rabbitsign-src/rsa.c b/tool/rabbitsign-src/rsa.c
new file mode 100644
index 0000000..05139a9
--- /dev/null
+++ b/tool/rabbitsign-src/rsa.c
@@ -0,0 +1,137 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+#define VALIDATION_EXPONENT 17
+
+/*
+ * Calculate the RSA signing exponent.
+ *
+ * The validation exponent, e, is 17 for all TI-related RSA
+ * signatures.  The signing exponent, d, depends on n, and is
+ * calculated so that e * d === 1 (mod (p-1)(q-1)).
+ *
+ * (This means that for any number x,
+ *
+ *  x^(e * d) = x * x^[k0 * (p-1)] === x * 1 (mod p),
+ *
+ * and likewise
+ *
+ *  x^(e * d) = x * x^[k1 * (q-1)] === x * 1 (mod q).
+ *
+ * Therefore (Chinese remainder theorem) x^(e * d) === x (mod n).
+ *
+ * Note that there is no way of calculating d without knowing the
+ * factors of n; this is a key point in the security of RSA.)
+ */
+static int get_exponent(mpz_t res,	/* mpz to store result */
+			int e,		/* validation exponent */
+			const mpz_t p,  /* first factor */
+			const mpz_t q)  /* second fatctor */
+{
+  mpz_t a, b;
+  mpz_init(a);
+  mpz_init(b);
+
+  mpz_sub_ui(a, p, 1);
+  mpz_sub_ui(b, q, 1);
+  mpz_mul(a, a, b);
+
+  mpz_set_ui(b, e);
+
+  mpz_gcdext(b, res, NULL, b, a);
+  if (mpz_cmp_ui(b, 1)) {
+    mpz_clear(a);
+    mpz_clear(b);
+    return RS_ERR_UNSUITABLE_RSA_KEY;
+  }
+
+  mpz_mod(res, res, a);
+
+  mpz_clear(a);
+  mpz_clear(b);
+  return RS_SUCCESS;
+}
+
+/*
+ * Compute an RSA signature.
+ *
+ * This is simply the hash raised to the d-th power mod n (where d is
+ * defined above.)
+ */
+int rs_sign_rsa(mpz_t res,	   /* mpz to store signature */
+		const mpz_t hash, /* MD5 hash of app */
+		RSKey* key)	   /* key structure */
+{
+  if (!mpz_sgn(key->n)) {
+    rs_error(key, NULL, "unable to sign: public key missing");
+    return RS_ERR_MISSING_PUBLIC_KEY;
+  }
+
+  if (!mpz_sgn(key->d)) {
+    if (!mpz_sgn(key->p) || !mpz_sgn(key->q)) {
+      rs_error(key, NULL, "unable to sign: private key missing");
+      return RS_ERR_MISSING_PRIVATE_KEY;
+    }
+    if (get_exponent(key->d, VALIDATION_EXPONENT, key->p, key->q)) {
+      rs_error(key, NULL, "unable to sign: unsuitable key");
+      return RS_ERR_UNSUITABLE_RSA_KEY;
+    }
+  }
+
+  mpz_powm(res, hash, key->d, key->n);
+  return RS_SUCCESS;
+}
+
+/*
+ * Check that the given RSA signature is valid.
+ *
+ * To do this, we raise the signature to the 17th power mod n, and see
+ * if it matches the hash.
+ */
+int rs_validate_rsa(const mpz_t sig,  /* purported signature of app */
+		    const mpz_t hash, /* MD5 hash of app */
+                    const RSKey* key) /* key structure */
+{
+  mpz_t e, m;
+  int result;
+
+  if (!mpz_sgn(key->n)) {
+    rs_error(key, NULL, "unable to validate: public key missing");
+    return RS_ERR_MISSING_PUBLIC_KEY;
+  }
+
+  mpz_init(e);
+  mpz_init(m);
+
+  mpz_set_ui(e, VALIDATION_EXPONENT);
+  mpz_powm(m, sig, e, key->n);
+  result = mpz_cmp(hash, m);
+
+  mpz_clear(e);
+  mpz_clear(m);
+  return (result ? RS_SIGNATURE_INCORRECT : RS_SUCCESS);
+}
diff --git a/tool/rabbitsign-src/typestr.c b/tool/rabbitsign-src/typestr.c
new file mode 100644
index 0000000..00bfb92
--- /dev/null
+++ b/tool/rabbitsign-src/typestr.c
@@ -0,0 +1,161 @@
+/*
+ * RabbitSign - Tools for signing TI graphing calculator software
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif
+
+#include "rabbitsign.h"
+#include "internal.h"
+
+/*
+ * Get default file suffix for a given calc/data type.
+ */
+const char* rs_type_to_suffix(RSCalcType calctype, /* calculator type */
+			      RSDataType datatype, /* program data type */
+			      int hexonly)	   /* 1 = plain hex output */
+{
+  if (calctype == RS_CALC_TI73) {
+    if (datatype == RS_DATA_APP)
+      return (hexonly ? "app" : "73k");
+    else if (datatype == RS_DATA_OS)
+      return (hexonly ? "hex" : "73u");
+    else if (datatype == RS_DATA_CERT)
+      return "73q";
+  }
+  else if (calctype == RS_CALC_TI83P) {
+    if (datatype == RS_DATA_APP)
+      return (hexonly ? "app" : "8xk");
+    else if (datatype == RS_DATA_OS)
+      return (hexonly ? "hex" : "8xu");
+    else if (datatype == RS_DATA_CERT)
+      return "8xq";
+  }
+  else if (calctype == RS_CALC_TI89) {
+    if (datatype == RS_DATA_APP)
+      return "89k";
+    else if (datatype == RS_DATA_OS)
+      return "89u";
+    else if (datatype == RS_DATA_CERT)
+      return "89q";
+  }
+  else if (calctype == RS_CALC_TI92P) {
+    if (datatype == RS_DATA_APP)
+      return "9xk";
+    else if (datatype == RS_DATA_OS)
+      return "9xu";
+    else if (datatype == RS_DATA_CERT)
+      return "9xq";
+  }
+
+  return "sig";
+}
+
+/*
+ * Get implied calc/data type for a given file suffix.
+ */
+int rs_suffix_to_type(const char* suff,     /* file suffix (not
+					       including .) */
+		      RSCalcType* calctype, /* implied calculator
+					       type */
+		      RSDataType* datatype) /* implied program type */
+{
+  int calc, data;
+
+  if (strlen(suff) != 3)
+    return -1;
+
+  if (suff[0] == '7' && suff[1] == '3')
+    calc = RS_CALC_TI73;
+  else if (suff[0] == '8' && (suff[1] == 'x' || suff[1] == 'X'))
+    calc = RS_CALC_TI83P;
+  else if (suff[0] == '8' && suff[1] == '9')
+    calc = RS_CALC_TI89;
+  else if (suff[0] == '9' && (suff[1] == 'x' || suff[1] == 'X'))
+    calc = RS_CALC_TI92P;
+  else if ((suff[0] == 'v' || suff[0] == 'V') && suff[1] == '2')
+    calc = RS_CALC_TI92P;
+  else
+    return -1;
+
+  if (suff[2] == 'k' || suff[2] == 'K')
+    data = RS_DATA_APP;
+  else if (suff[2] == 'u' || suff[2] == 'U')
+    data = RS_DATA_OS;
+  else if (suff[2] == 'q' || suff[2] == 'Q')
+    data = RS_DATA_CERT;
+  else
+    return -1;
+
+  if (calctype) *calctype = calc;
+  if (datatype) *datatype = data;
+  return 0;
+}
+
+/*
+ * Get a human-readable description of a calculator type.
+ */
+const char* rs_calc_type_to_string(RSCalcType calctype)
+{
+  switch (calctype) {
+  case RS_CALC_TI73:
+    return "TI-73";
+
+  case RS_CALC_TI83P:
+    return "TI-83/84 Plus";
+
+  case RS_CALC_TI89:
+    return "TI-89";
+
+  case RS_CALC_TI92P:
+    return "TI-92 Plus/Voyage 200";
+
+  default:
+    return "unknown";
+  }
+}
+
+/*
+ * Get a human-readable description of a data type.
+ */
+const char* rs_data_type_to_string(RSDataType datatype)
+{
+  switch (datatype) {
+  case RS_DATA_OS:
+    return "OS";
+
+  case RS_DATA_APP:
+    return "application";
+
+  case RS_DATA_CERT:
+    return "certificate";
+
+  default:
+    return "program";
+  }
+}
diff --git a/tool/tilem-src/CHANGELOG b/tool/tilem-src/CHANGELOG
new file mode 100644
index 0000000..78a8f7f
--- /dev/null
+++ b/tool/tilem-src/CHANGELOG
@@ -0,0 +1,316 @@
+/* This file only contains the changelog for the gui/doc/data, some things could be missing (core changes...)*/
+/* This changelog was started when I started to work on TilEm2 */
+
+/*  contra-sh : 
+* ---18/08/09---
+* - Draw the TI83 
+* - Copy from testemu.c : Create the savname, Open the Rom, Get the model, Draw the lcdscreen (no animation for the moment)
+* - Function event OnDestroy 
+* - Modification of the Makefile (I hope it's good !? If you can control this... thx)
+* - LEARNING HOW COMMIT !!   :D
+* ---19/08/09---
+* - New structure TilemCalcSkin : define the different filenames for the SkinSet (4 pieces).
+* - Draw the other models _automatically_ . ;D
+* ---20/08/09---
+* - Create skin.c
+* - Create gui.h (equivalent of tilem.h for the gui directory)
+* - Move the code struct in gui.h and TilemCalcSkin* tilem_guess_skin_set(TilemCalc* calc) into skin.c (only one call in the main file to define the skin set) ;D
+* - Detect a keyboard event (function keyboard_event() in skin.c actually).No treatment.
+* - Detect an event click on mouse
+* ---21/08/09---
+* - Get the x and y values when click on mouse (now it will be easy to know how key is click on the pixmap, todo in priority : detect right click)
+* ---24/08/09---
+* - Detect right click.
+* ---26/08/09---
+* - New function : TilemKeyMap* tilem_guess_key_map(int id).Choose a TilemKeyMap with an id given in parameter. 
+* ---27/08/09---
+* - Extract the choice of the key_map from the mouse_event function.Execute only one time and it's more properly (and it will be easier to add the possibility to manage many skins and many keymaps).
+* ---01/09/09---
+* - Choose automatically the key_list. The TilemKeyList is already included in the TilemKeyMap structure...
+* - New structure TilemKeyCoord (old TilemKeyMap).TilemKeyMap already contains TilemKeyCoord and TilemKeyList... 
+* ---08/09/09---
+* - New function tilem_set_coord to change the keypad coord.
+* - New file event.c to group the GDKevent handling.
+* - New function tilem_set_skin to change the skin.
+* ---10/09/09---
+* - Add the right click menu :D (0 signal connected without OnDestroy).Largely inspired from Tilem 0.973 and http://www.linux-france.org/article/devl/gtk/gtk_tut-11.html was a great inspiration too. 
+* ---21/09/09---
+* - Aouch I had seen that the left click doesn't work now! Problem : two callback for the only one button_press_event. (sorry for this version...)
+* ---22/09/09---
+* - Correction : only one callback now. mouse_event contains the create_menu and the gtk_menu_popup. (lot of time was spent on this problem)*
+* ---23/09/09---
+* - Change TilemKeyCoord to the 82 stats skin.Change Event.c too.
+* ---06/10/09---
+* - Beginning the correction : change the method for testing coordinates clicks (one line now) . The coordinates will be imported from a file soon.
+* ---20/11/09---
+* - After 1 week to learn Tiemu skin format...I have made my own Tilem skin Generator.Inspired from skinedit.It just generate compatible file with 0 coordinates values, and an default lcd coordinates.Not really necessary but very useful for testing and for learning how this f****** skin format works.It will be called skintool for the moment.
+* ---27/11/09---
+* - After blocking a problem for a week grrr... I succeed to adapt the TiEmu skinloader to TilEm (skinops.c and skinops.h).Little modifications
+* ---28/11/09---
+* - The mouse_event function now use a SKIN_INFOS struct. So delete TilemKeyCoord struct.
+* ---02/12/09---
+* - Add a GtkFileSelection (access by right click menu) and try to load another skin with this method.
+* ---03/12/09---
+* - Create a GLOBAL_SKIN_INFOS that contains a KEY_LIST struct (old TilemKeyList) and a SKIN_INFOS struct.
+* ---04/12/09---
+* - Delete the TilemKeyCoord, TilemKeyMap, TilemCalcSkin and TilemKeyList structs...
+* ---05/12/09---
+* - The GtkWidget *pWindow creation was moved from testemu2.c to event.c .The function is called GtkWidget* Draw_Screen(GLOBAL_SKIN_INFOS *gsi);
+* ---07/12/09---
+* - New feature : TilEm could load a skin by the right_click_menu ! It use GtkWidget* ReDraw_Screen(GLOBAL_SKIN_INFOS *gsi) in event.c. WAOUH !
+* ---08/12/09---
+* - Move Draw_Screen, ReDraw_Screen and create_menus in a new screen.c file. Change Makefile.
+* ---14/12/09---
+* - New feature : add a popup rom type selector (radio button) at the start of tilem. Showed but not used for the moment.
+* - Connect the thread (no animation yet). Thanks to the "battle programming" 's spirit (hey bob ^^)
+* ---18/12/09---
+* - Launch the interactivity with emulation core. Could print into the draw area.
+* ---09/03/10---
+* - Restart working on this program ;D
+* ---11/03/10---
+* - I finally succeeded to connect the core. To print something into the lcd screen ! WahoOO ! This day is a great day !
+* - I succeded to type numbers etc...
+* - And now it works very well !! (the "button-release-event" is not catched by pWindow but by pLayout)
+* ---15/03/10---
+* - Create the scan_click function.Return wich keys was clicked.Print debug infos.
+* - Create the debuginfos.h to group the ifdef for debugging. (different level and different type of debug msg)
+* ---17/03/10---
+* - Create the rom_choose_popup function to replace choose_rom_type.It use GtkDialog instead of GtkWindow.
+* - rom_choose_popup _freeze_ the system... and get wich radio button is selected. So it will be easy to create the good emu.calc (and choose the default skin).
+* ---18/03/10---
+* - Resize the (printed) lcd area (gsi->emu->lcdwin) to fit(perfectly) into the skin.
+* - Replace a lot of printf by D****_L*_A* to easily switch what debug infos were printed.
+* - Try to make a nice debugging output (frames in ASCII ^^) :p
+* - WahooOO , load a skin works perfectly.You can easily change skin _while running_, no error, no warning.
+* - Could load automatically the good skin and run the good core using the choose_rom_popup() function and choose_skin_filename() function.
+* ---30/03/10---
+* - Create skin for following models : 73, 81, 82, 83+ and 84+.
+* - Fix bug in tool.c .Modification of tool.c to create radio button more properly.
+* ---31/03/10---
+* - Create skin for following model : 83 . Based on my own calc (take a foto with my iphone 3GS :D)
+* ---01/04/10---
+* - New feature : Save calc state and load state. State are stored in a separate dir called sav/ . (using benjamin 's function)
+* - New feature : Change view to see only the lcd. I finally choose to add it into a GtkLayout. So you can maximize it, but there was problem with add_event.
+* ---02/04/10---
+* - Add popup function to just print error message.You must give the message and gsi as parameter, and it run a modal dialog.
+* - Some cleaning.
+* ---23/04/10---
+* - Add config.c file to manage config.dat (create, read, modif etc...).
+* ---31/05/10---
+* - Start from scratch a totally new debugger :D.Just draw button with nice GtkImages.Actually in essai2 directory.
+* - Get and resize pixmaps (png) to 36 * 36 pixels for the debugger.
+* ---01/06/10---
+* - Add the debugger to tilem. Load registers values.
+* - Add a new feature : switch the top level window "borderless".It can be switch by clicking on right click menu entry.
+* ---02/06/10---
+* - Create the GtkTreeView (debugger).
+* - Refresh register on click.
+* ---05/08/10---
+* - More than one month without working on tilem...Only commit old modif, and skn/README file.
+* - Refresh stack on click (number of entry is correct but value not)
+* ---06/08/10---
+* - Working on a new system of configuration file.The config.dat is now a binary file (as .skn but totally differennt). At start, a CONFIG_INFOS struct is filling, then when clicking on right menu, this struct is modified, then when you stop tilem, config.dat is writed on disc. 
+* ---09/08/10---
+* - Correction of the SEG_FAULT (never use sizeof(char*) inside malloc, strlen(char*) is better :P).
+* ---10/08/10---
+* - Working on a new config file called romconfig.dat (inspired from config.dat) using to do not ask for model each time tilem is started.
+* - It works :D
+* ---12/08/10---
+* - NEW feature : Can load a file using libticalcs2 (inspired from the wokr of Benjamin Moody). Basically it works, but it's not OK. (many bugs)
+* - Drop the deprecated GtkFileSelection.Use GtkFileChooser instead. :)  
+* ---13/08/10---
+* - NEW feature : Add the screenshot feature.
+* ---17/08/10---
+* - Change the ti84plus.skn (old was really ugly).
+* - Add doc/ directory : add romconfig_file_format.txt and skinconfig_file_format.txt.
+* ---18/08/10---
+* - Correct the bug in link.c (unlock mutex ...)
+* - Start working on macro handling : Always do the same things to load and launch a file into an emulator become quickly noisy for the programmer (1 time, 10 times, 30 times, 50 times...argh!). Simply record a sequence and play it to test a program, this is one solution. (feature request from Guillaume Hoffman gh@gmail.com).
+* ---19/08/10---
+* - The macro feature works including loading file (very important). The implementation is very basic (record and read a text file) so many bug could (should?) appear. But I wait to see how it will be use.
+* ---20/08/10--
+* - Better implementation of GtkFileChooser (to be cleaner).
+* - Some work on macro (no seg fault now ^^).
+* - Add a Save state... entry in right click menu (no need to stop tilem to save state)
+* - Add a Play macro with GtkFileChooser (to play another macro than play.txt).
+* - Fix minor bug in GtkFileChooser (forget to init a char* to NULL).
+* ---23/08/10---
+* - Add a new args handler using getop library (add -m for macro and -k for skin).
+* ---06/09/10---
+* - NEW feature : could print the lcd content into the terminal. So useless but so funny ;) (feature request from Guillaume Hoffman).
+* - The output is saved into a file called lcd_content.txt.
+* ---08/09/10---
+* - Could choose wich char to display. This not really works as I want, but this is not a really important feature (works 1/2)
+* - Add an option at startup (-l). Could now start in skinless mode.
+* ---04/10/10---
+* - Start working on animated gif. Some research on GIF file format. Oops it will be hard, file is encoded (!).
+* ---09/10/10---
+* - Creation of 1 little static gif file seems working, but always no LZW encoding.
+* ---12/10/10---
+* - Finally I decided to use external source to encode the pix data.I use a file called "gifencode.c" by Hans Dinsen-Hansen and Michael A. Mayer. And it works !!!
+* ---14/10/10---
+* - It works ! It works !!! Tilem2 is now able to generate animated gif, functions are currently working (but totally buggy) and it successfully create animated gif :)
+* - Need to be integrated (and lot of debug).I commit it just to save it...Wait another commit to really use this feature :P
+* ---01/02/11---
+* - Starting to work on a new config file using glibc to do not hard code keypad values.
+* - And it works !!!! (but only load one keypad model currently)
+* - Add the other models into keylist.ini (but the content is completely false). Change scan_click method (correct a bug) to use kp->nb_of_buttons. Only need to give correct value into the keylist.ini file. For the rest it's seems ok.
+* ---07/02/11---
+* - Start to work on config.ini. A new generation config file using GKeyFile library (glib). Add some work in essai4 directory.
+* ---08/02/11---
+* - Remove romconfig.c romconfig.h config.c config.h (handle binary config file). Remove ROM_CONFIG_INFOS and CONFIG_INFOS from gsi.
+* - Add a new config.c and config.h file to handle config (last rom used, default skin to load, etc...). It uses glib GKeyFile library.
+* - Fix the macro bug pointed by Benjamin.
+* ---15/02/11---
+* - Replace correct copyright/licence informations into skinops.* .Sorry for the inconvenience.
+* ---16/02/11---
+* - Fix an important mistake into the gif creator function (palette should be before gif frame information).
+* ---18/02/11---
+* - Connect a timer to automatically add frame to a animated screenshot (using screenshot box).
+* ---14/03/11---
+* - Improve default skin choice.
+* - Define the skin's basedir into the config.ini. Add a universal getter into config.c .
+* ---19/03/11---
+* - Add a bouncy progress bar to show the link status (send file).
+* - Add a file saver.  
+* - Create a new TilemCmdlineArgs structure.
+* ---07/04/11---
+* - Drop deprecated gtkitemfactory
+* - Prepare GT3 migration
+* ---10/04/11---
+* - Benjamin add a configure script and all associated things (Makefile.in, config.h etc...).
+* - Fix dependency and a lot of cleaning.
+* - Benjamin add a lot of function to handle data directory.
+* ---13/04/11---
+* - Completely refactoring the screenshot window. New preview possibility.
+* - Add some cool features for screenshot menu (replay from file, preview animation, preview screenshot, 2 gtkfilechooserbutton, change default folders etc...).
+* - Remove old pix directory.
+* - Benjamin begins to work on new debugger.Add a menu, some basic actions.
+* ---15/04/11---
+* - Benjamin add a set of function to handle user defined icons. A lot of improvement on the debugger (add step action, pause).
+* - Use tilem logo as default background in the screenshot menu, add some pix into the shared data directory.
+* - Remove old debugger pix.
+* - Add GtkSpinnerAnimation in the screenshot menu. Improve look and feel (fix fill/expand properties for the box, size of the widgets etc...).
+* ---17/04/11---
+* - Benjamin add a ti83p symbol file which allow to replace flag values by theirs labels in the debugger (and more other things). Debugger looks like very good.
+* ---19/04/11---
+* - I've added a ti83 symbol file (but it could contains some mistakes...). Flags are correctly printed for my loved ti83 regular ;)
+* ---27/04/11---
+* - Root window size could be forced (but the ratio could be strand if you specify stanges values ... Because ration is calculated on the start width and heigth).
+* ---02/05/11---
+* - Add the icons for the right click menu (stock icons currently).
+* - Export all menu related code in a separate file called menu.c. It could maybe be done by UI in the future, but it works fine as it for the moment ;)
+* - Add drag and drop support for loading files :) :) :) :) :) (code is currently really ugly)
+* ---09/05/11---
+* - Create a new TilemGuiStateFlags structure ot group the running state of the gui (skinless, recording macro, etc...) and export it into TilemCalcEmu instead GLOBAL_SKIN_INFOS.
+* - Completely remove GLOBAL_SKIN_INFOS structure from tilem2.
+* ---10/05/11---
+* - Change strutures to look like a object oriented model. But in some case it's not really well implemented.
+* ---11/05/11---
+* - Refactor a lot of gif header's code.
+* ---12/05/11---
+* - I finally found what's wrong in my gif creation. So I can do multicolor gif now.
+* ---14/05/11---
+* - We have finally completely reorganized the code to drop GLOBAL_SKIN_INFOS, and rename a lot of stuff including files (emuwin instead screen by example).
+* - Benjamin have imported his nice filedlg functions (filedlg.c) from flashbook ui (see it on SF.net). Now use it for the entire code.
+* ---14/05/11---
+* - Refactor the gif creation to separate clearly which value can be modified, which one are magic number etc... Create separate functions for each task.
+* - Benjamin begins to work on animation (instead of writing file while recording it). It provides a wonderful objects to handle screenshot.
+* ---15/05/11---
+* - Add a doxyfile for generating documentation including callgraphs.
+* - Improvement to keybindings (Benjamin).
+* ---16/05/11---
+* - Add some preliminary work on get var stuff. Only dirlist and allow user to retrieve a var using cmd line but it works. The list is printed in a tree view.
+* - Add multi drag and drop feature.
+* ---19/05/11---
+* - Add functions for animations (Benjamin). Could now save a animation into a gif file.
+* - Improve screenshot menu by setting some buttons inactives depending the current state of screenshot creation.
+* - Add combo box for size to screenshot menu.
+* ---21/05/11---
+* - A lot of improvement on screenshot menu (Benjamin). Settings are now independent of screenshot dialog. Default directory for output.
+* - Add mnemonic label to screenshot menu buttons (Benjamin).
+* ---22/05/11---
+* - Benjamin redesign the entire menu popup to use the better GtkAction system. Now there's keybindings for menu and code is really beautiful and shorter.
+* - Add grayscale option to screenshot menu (Benjamin).
+* ---23/05/11---
+* - Save the last rom opened and use it at startup if no rom is given.
+* - Allow user to load another rom while running.
+* - Refactor the entire macro creation to separate creation from writing as TilemAnimation does. Code is cleaner and simply better.
+* ---25/05/11---
+* - Add an option to change animation speed. The current printed animation is updated on change.
+* ---26/05/11---
+* - Add an option to change foreground and background color. No visual feedback.
+* ---27/05/11---
+* - Correct some stuff (see mailing list to know why). And add a palette setter in animation.c and a visual feedback when foreground/background color are changed.
+* - Some other minor improvement.
+* ---19/09/11---
+* - Restart working on tilem2 after holidays:)
+* - Add new file getvar.c to handle get var function (receiving var from calc to PC).
+* ---20/09/11---
+* - Some improvement to the rcvmenu. 
+* ---22/09/11---
+* - Add the ti83pfr.skn file. This skin is for ti83plus.fr. The creator is Claude Clerc (aka claudiux). He donates the skin under GPL v3+ licence.Thanks to him !!!
+* ---11/10/11---
+* - Correct the getvar.c code to work correctly. Add columns to the tree view.
+* - Add app receive handling. Set index column invisible.
+* ---12/10/11---
+* - Some corrections : receive dialog is transcient. Receive dialog is not destroyed when closed just hided. New refresh button to refresh var/app list.
+* - Use a separate thread to receive files.
+* ---14/10/11---
+* - Add a feature to list ti82 and ti85 entries. User must prepare manually the link. Can't save a ti82 or ti85 entry (when selected in the rcvdialog). 
+* - Lot of bug, code is really ugly. No error handling. The prepare_for_link_receive is not working. etc...
+* ---11/11/11---
+* - Benjamin add a totally new prefs dialog. Some unused function (print lcd in terminal by example) are deleted.
+* ---12/11/11---
+* - Working on ns silent get var.
+* ---14/11/11--- 
+* - New files : emucore.c and emucore.h .
+* - Benjamin adds a totally new core task manager in the gui. Begin to convert the send files function to use it.
+* - Use this task manager for macro. There's a little priority problem when a macro load a file. Load a file then run a macro at startup works fine.
+* ---15/11/11--- 
+* - Receive non silent vars : Benjamin get it working!
+* - Remove unused getvar.c file.
+* ---16/11/11---
+* - Allow multiple selection and multiple receive file. Need to fix a segfault (tomorow... xD).
+* ---17/11/11---
+* - Repare the file loading inside a macro (it was broken by new task manager).
+* - Delete a lot of unused functions/printf.
+* - Remove debuginfo.h file.
+* ---22/11/11---
+* - Benjamin finished the last things left to do on receive dialog. Nice!
+* ---25/11/11---
+* - New command line parser. Now tilem2 uses g_option from the glib instead of getopt. Easy handling of long options. Do not need to take care about correct parsing. A lot of new options are provided but not implemented !
+* - Delete args.c (old _but working well _ cmd line parser using getopt) and TilemCmdlineArgs structure from gui.h.
+* - Add the possibility to getvar a file at startup. I had to use a weird solution to do this with task manager. But it's working :)
+* ---12/12/11---
+* - Benjamin do a lot of improvements on file chooser (filters, ...).
+* ---22/12/11---
+* - Benjamin fix certificate patching.
+* ---19/03/12---
+* - Icons are finally commited into the trunk. These pictures are originaly designed by Scott Zeid and modified by me. No .desktop and icons installer for the moment.
+* - Scott, thank you a lot for these wonderful pictures!
+* ---21/03/12---
+* - Adding documentation (LaTeX). The documentation is not finished yet. Lot of pictures added.
+* ---24/04/12---
+* - Benjamin added some correction to install properly the icons.
+* ---27/04/12---
+* - Some modifications on the configure script because something was failing on my debian wheezy. It works fine yet (using squeeze and gtk/glib downgraded packages and some minor modifications on configure scripts).
+* ---03/05/12---
+* - Reverting changes on the configure script because it was not the fault of configure script.
+* ---07/05/12---
+* - Update doc (add "basic task" chapter).
+* - Update .desktop files.
+* ---08/05/12---
+* - Benjamin added a rule to install in the $HOME directory.
+* - Benjamin added MIME type files.
+* ---09/05/12---
+* - Benjamin added a piece of documentation about "getting ROM".
+* ---11/05/12---
+* - Add an huge explanation for debugger part into the documentation.
+* ---13/05/12---
+* - Add some screenshot documentation.
+* ---15/05/12---
+* - Benjamin add README, THANKS and COPYING file.
+* - Update screenshot doc.
+*/
+
diff --git a/tool/tilem-src/COPYING b/tool/tilem-src/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/tool/tilem-src/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 3 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, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/tool/tilem-src/INSTALL b/tool/tilem-src/INSTALL
new file mode 100644
index 0000000..84008d7
--- /dev/null
+++ b/tool/tilem-src/INSTALL
@@ -0,0 +1,53 @@
+                           Installing TilEm
+                          ------------------
+
+To compile TilEm, you will need:
+
+  * a standard C compiler
+
+  * the GTK+ library, version 2.6.0 or later
+
+  * the libticalcs2 library (part of the TiLP project), and its
+    dependencies (libticables2, libtifiles2, and libticonv)
+
+Some OS distributions package the "development files" separately from
+the libraries themselves; you need both.  If you are using Debian (or
+a related distribution, such as Ubuntu or Mint), install the packages
+'build-essential', 'libticalcs-dev', and 'libgtk2.0-dev'.
+
+If these packages aren't available from your package manager, you'll
+need to compile them yourself; see the TiLP website for more
+information (http://lpg.ticalc.org/prj_tilp/).
+
+Mac OS X has not been tested by the TilEm team, but if TiLP works,
+TilEm should, too.  Please let us know if you do get it to work.
+
+Once the above dependencies are installed, open a terminal and 'cd' to
+the directory containing this INSTALL file.  Then run
+
+   ./configure
+
+which will check whether you have all of the necessary software
+installed, and figure out how to compile TilEm for your particular
+system.  If configure is successful, you can then run
+
+   make
+
+to compile TilEm.  Finally, you can run
+
+   sudo make install
+
+to install it (or run 'make install' as the superuser.)  The program
+will be installed in '/usr/local/bin/', and data files will be
+installed in '/usr/local/share/tilem2/'.  Alternatively, you can run
+
+   make install-home
+
+to install TilEm in your own home directory ('$HOME/bin/', with data
+files stored in '$HOME/.local/share/tilem2/'), which does not require
+superuser privileges.  (If $HOME/bin didn't exist already, you might
+have to run 'export PATH=$HOME/bin:$PATH', or log out and log back in,
+before running TilEm.)
+
+Once TilEm is installed, start it by running 'tilem2', or through your
+system's applications menu.
diff --git a/tool/tilem-src/KEYS b/tool/tilem-src/KEYS
new file mode 100644
index 0000000..d547d84
--- /dev/null
+++ b/tool/tilem-src/KEYS
@@ -0,0 +1,270 @@
+KEYBOARD BINDINGS
+
+ This list shows the default keyboard controls for TilEm.  You can
+ modify these, if you like, by editing 'keybindings.ini'.  (Place the
+ modified version of keybindings.ini in your TilEm configuration
+ directory - $HOME/.config/tilem2/ on Unix, or
+ $PROFILE\Local Settings\Application Data\tilem2 on Windows.)
+
+ In the following list:
+   S = Shift
+   C = Control
+   ↑ = 2nd
+   α = Alpha
+
+ ² in the TI-82/83 column indicates that the key is only for the TI-82.
+ ³ indicates that it is only for the TI-83/TI-83 Plus/TI-84 Plus.
+
+ Key          TI-73        TI-76.fr     TI-81        TI-82/83     TI-85/86
+ ──────────────────────────────────────────────────────────────────────────────
+ F12          ON           ON           ON           ON           ON
+ S+F12        ↑ [OFF]      ↑ [OFF]      ↑ [OFF]      ↑ [OFF]      ↑ [OFF]
+
+ Up           Up           Up           Up           Up           Up
+ Down         Down         Down         Down         Down         Down
+ Left         Left         Left         Left         Left         Left
+ Right        Right        Right        Right        Right        Right
+ S+Up         ↑ Up         ↑ Up         ↑ Up         ↑ Up         ↑ Up
+ S+Down       ↑ Down       ↑ Down       ↑ Down       ↑ Down       ↑ Down
+ Home         ↑ Left       ↑ Left                    ↑ Left       ↑ Left
+ End          ↑ Right      ↑ Right                   ↑ Right      ↑ Right
+ PageUp                                              α Up    ³
+ PageDown                                            α Down  ³
+
+ F1           Y=           f(x)=        Y=           Y=           F1
+ F2           WINDOW       fenêtre      RANGE        WINDOW       F2
+ F3           ZOOM         zoom         ZOOM         ZOOM         F3
+ F4           TRACE        trace        TRACE        TRACE        F4
+ F5           GRAPH        graphe       GRAPH        GRAPH        F5
+ S+F1         ↑ [PLOT]     ↑ [gr stat]  ↑ Y=         ↑ [STAT PLT] ↑ [M1]
+ S+F2         ↑ [TBLSET]   ↑ [déf tab]  ↑ RANGE      ↑ [TBLSET]   ↑ [M2]
+ S+F3         ↑ [FORMAT]   ↑ [format]   ↑ ZOOM       ↑ [FORMAT]   ↑ [M3]
+ S+F4         ↑ TRACE      ↑ [calculs]  ↑ TRACE      ↑ [CALC]     ↑ [M4]
+ S+F5         ↑ [TABLE]    ↑ [table]    ↑ GRAPH      ↑ [TABLE]    ↑ [M5]
+ PageDown                                                         MORE
+
+ Tab          2nd          2nde         2nd          2nd          2nd
+ ' or Menu    ↑ [TEXT]     texte        Alpha        Alpha        Alpha
+
+ Insert       ↑ [INS]      ↑ [insérer]  INS          ↑ [INS]      ↑ [INS]
+ Delete       DEL          suppr        DEL          DEL          DEL
+ Backspace    Left, DEL    Left, suppr  Left, DEL    Left, DEL    Left, DEL
+ C+Backspace  CLEAR        annul        CLEAR        CLEAR        CLEAR
+  or C+Delete
+
+ Escape       CLEAR        annul        CLEAR        CLEAR        EXIT
+ S+Escape     ↑ [QUIT]     ↑ [quitter]  ↑ [QUIT]     ↑ [QUIT]     ↑ [QUIT]
+
+ F6                        math         MATH         MATH         GRAPH
+ F7           APPS         angle        MATRX        MATRX/APPS   STAT
+ F8           PRGM         prgm         PRGM         PRGM         PRGM
+ F9                        var          VARS         VARS         CUSTOM
+ F10                       stats                     STAT
+ F11          MODE         mode         MODE         MODE         ↑ [MODE]
+ c            CONST
+ d            DRAW
+ l            LIST
+ m            MATH
+ p                         prgm         PRGM         PRGM
+ C+Tab        ↑ [CATALOG]  ↑ [catalog]               ↑ [CATALOG]³ ↑ [CATALOG]
+
+ _            UNIT
+ | or ½       a/b
+ f            F◂▸D
+ a            Ab/c◂▸d/e
+ s            SIMP
+ %            %
+
+ |                                      ↑ [ABS]      ↑ [ABS] ²
+ s                         sin          SIN          SIN
+ c                         cos          COS          COS
+ t                         tan          TAN          TAN
+ o                         log          LOG          LOG
+ l                         ln           LN           LN
+
+ u                         ↑ [uₙ]                    ↑ [u]  ³
+ v                         ↑ [vₙ]                    ↑ [v]  ³
+ w                         ↑ [wₙ]                    ↑ [w]  ³
+ u                                                   ↑ [Uₙ₋₁]  ²
+ v                                                   ↑ [Vₙ₋₁]  ²
+ n                                                   ↑ [n]     ²
+
+ C+1 or \     ↑ [x⁻¹]      x⁻¹          x⁻¹          x⁻¹          ↑ [x⁻¹]
+ C+2 or ²     x²           x²           x²           x²           x²
+ (            (            (            (            (            (
+ )            )            (            )            )            )
+ {                         ↑ [{]                     ↑ [{]
+ }                         ↑ [}]                     ↑ [}]
+ [                                                   ↑ [[]        ↑ [[]
+ ]                                                   ↑ []]        ↑ []]
+ ^            ^            ^            ^            ^            ^
+ /            ÷            ÷            ÷            ÷            ÷
+ *            ×            ×            ×            ×            ×
+ -            -            -            -            -            -
+ +            +            +            +            +            +
+ ,            ,            ,            α [,]        ,            ,
+
+ >            STO▸         STO▸         STO▸         STO▸         STO▸
+ <            ↑ [RCL]      ↑ [rappel]   ↑ STO▸       ↑ [RCL]      ↑ [RCL]
+
+ 0 - 9        0 - 9        0 - 9        0 - 9        0 - 9        0 - 9
+ .            .            .            .            .            .
+ ~ or ±       (-)          (-)          (-)          (-)          (-)
+ & or €       ↑ [EE]       ↑ […×10ⁿ]    EE           ↑ [EE]       EE
+
+ x            X            x,n          X|T          X,T,θ,n
+ $            ↑ [ANS]      ↑ [rép]      ↑ [ANS]      ↑ [ANS]      ↑ [ANS]
+ #            ↑ [π]        ↑ [π]        ↑ [π]        ↑ [π]        ↑ [π]
+ e                         ↑ [e]                     ↑ [e]  ³
+ i                                                   ↑ [i]  ³
+
+ A - Z                                  α [A]-[Z]    α [A]-[Z]    α [A]-[Z]
+ a - z                                                            ↑α [a]-[z]
+ Space                                  α [space]    α [space]    α [space]
+ @                                      α [θ]        α [θ]
+ "                                      α ["]        α ["]
+ ?                                      α [?]        α [?]
+ :                                                   α [:]        ↑ [:]
+ =                                                                α [=]
+
+ Return       ENTER        entrer       ENTER        ENTER        ENTER
+ S+Return     ↑ [ENTRY]    ↑ [précéd]   ↑ [ENTRY]    ↑ [ENTRY]    ↑ [ENTRY]
+ ──────────────────────────────────────────────────────────────────────────────
+
+ For the TI-83 Plus and TI-84 Plus, in addition to the keys listed
+ above for the TI-83, pressing Shift+letter while Caps Lock is enabled
+ will type a lowercase letter (Alpha, Alpha, letter.)  Lowercase mode
+ will need to be enabled on the calculator for this to work; it's not
+ enabled by default, but there are many assembly programs that can do
+ so.
+
+
+KEYPAD CHARTS
+
+                TI-73                                  TI-76.fr
+
+ ╭──────┬──────┬──────┬──────┬──────╮    ╭──────┬──────┬──────┬──────┬──────╮
+ │S+F1  │S+F2  │S+F3  │S+F4  │S+F5  │    │S+F1  │S+F2  │S+F3  │S+F4  │S+F5  │
+ │F1    │F2    │F3    │F4    │F5    │    │F1    │F2    │F3    │F4    │F5    │
+ ╰──────┴──────┴──────┴────┬─┴──┬───╯    ╰──────┴──────┴──────┴────┬─┴──┬───╯
+ ╭──────┬──────┬──────╮    │S+Up│        ╭──────┬──────┬──────╮    │S+Up│
+ │      │S+Esc │Ins   ├────┤Up  ├─────╮  │      │S+Esc │Ins   ├────┤Up  ├─────╮
+ │Tab   │F11   │Del   │Home├────┤End  │  │Tab   │F11   │Del   │Home├────┤End  │
+ ├──────┼──────┼──────┤Left├────┤Right│  ├──────┼──────┼──────┤Left├────┤Right│
+ │'     │      │      ├────┤S+Dn├─────╯  │      │      │      ├────┤S+Dn├─────╯
+ │m     │d     │l     │    │Down│        │'     │x     │F10   │    │Down│
+ ├──────┼──────┼──────┼────┴─┬──┴───╮    ├──────┼──────┼──────┼────┴─┬──┴───╮
+ │      │&     │C+Tab │      │      │    │      │      │      │      │      │
+ │C+2   │^     │F8    │F7    │Esc   │    │F6    │F7    │F8    │F9    │Esc   │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │\     │#     │      │      │    │      │      │      │      │#     │
+ │_     │|     │f     │a     │c     │    │\     │s     │c     │t     │^     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │      │      │      │      │    │      │&     │{     │}     │e     │
+ │s     │%     │(     │)     │/     │    │C+2   │,     │(     │)     │/     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │      │      │      │      │    │      │u     │v     │w     │      │
+ │x     │7     │8     │9     │*     │    │o     │7     │8     │9     │*     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │      │      │      │      │    │      │      │      │      │      │
+ │,     │4     │5     │6     │-     │    │l     │4     │5     │6     │-     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │<     │      │      │      │      │    │<     │      │      │      │      │
+ │>     │1     │2     │3     │+     │    │>     │1     │2     │3     │+     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │S+F12 │      │      │$     │S+Ret │    │S+F12 │C+Tab │      │$     │S+Ret │
+ │F12   │0     │.     │~     │Ret   │    │F12   │0     │.     │~     │Ret   │
+ ╰──────┴──────┴──────┴──────┴──────╯    ╰──────┴──────┴──────┴──────┴──────╯
+
+
+                TI-81                                   TI-82
+
+ ╭──────┬──────┬──────┬──────┬──────╮    ╭──────┬──────┬──────┬──────┬──────╮
+ │S+F1  │S+F2  │S+F3  │S+F4  │S+F5  │    │S+F1  │S+F2  │S+F3  │S+F4  │S+F5  │
+ │F1    │F2    │F3    │F4    │F5    │    │F1    │F2    │F3    │F4    │F5    │
+ ╰──────┴──────┴──────┴────┬─┴──┬───╯    ╰──────┴──────┴──────┴────┬─┴──┬───╯
+ ╭──────┬──────┬──────╮    │S+Up│        ╭──────┬──────┬──────╮    │S+Up│
+ │      │      │      ├────┤Up  ├─────╮  │      │S+Esc │Ins   ├────┤Up  ├─────╮
+ │Tab   │Ins   │Del   │    ├────┤     │  │Tab   │F11   │Del   │Home├────┤End  │
+ ├──────┼──────┼──────┤Left├────┤Right│  ├──────┼──────┼──────┤Left├────┤Right│
+ │      │      │      ├────┤S+Dn├─────╯  │      │      │      ├────┤S+Dn├─────╯
+ │'     │x     │F11   │    │Down│        │'     │x     │F10   │    │Down│
+ ├──────┼──────┼──────┼────┴─┬──┴───╮    ├──────┼──────┼──────┼────┴─┬──┴───╮
+ │      │      │      │      │S+Esc │    │      │      │      │      │      │
+ │F6    │F7    │F8    │F9    │Esc   │    │F6    │F7    │F8    │F9    │Esc   │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │|     │      │      │      │#     │    │|     │      │      │      │#     │
+ │\     │s     │c     │t     │^     │    │\     │s     │c     │t     │^     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │      │      │      │      │    │      │&     │{     │}     │      │
+ │C+2   │&     │(     │)     │/     │    │C+2   │,     │(     │)     │/     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │      │      │      │      │    │      │u     │v     │n     │[     │
+ │o     │7     │8     │9     │*     │    │o     │7     │8     │9     │*     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │      │      │      │      │    │      │      │      │      │]     │
+ │l     │4     │5     │6     │-     │    │l     │4     │5     │6     │-     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │<     │      │      │      │      │    │<     │      │      │      │      │
+ │>     │1     │2     │3     │+     │    │>     │1     │2     │3     │+     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │S+F12 │      │      │$     │S+Ret │    │S+F12 │      │      │$     │S+Ret │
+ │F12   │0     │.     │~     │Ret   │    │F12   │0     │.     │~     │Ret   │
+ ╰──────┴──────┴──────┴──────┴──────╯    ╰──────┴──────┴──────┴──────┴──────╯
+ 
+   Not shown:                              Not shown:
+     A-Z      Alpha, [A]-[Z]                 A-Z      Alpha, [A]-[Z]
+     @        Alpha, 3                       @        Alpha, 3
+     "        Alpha, +                       "        Alpha, +
+     Space    Alpha, 0                       Space    Alpha, 0
+     ,        Alpha, .                       :        Alpha, .
+     ?        Alpha, (-)                     ?        Alpha, (-)
+ 
+ 
+    TI-83 / TI-83 Plus / TI-84 Plus                 TI-85 / TI-86
+ 
+ ╭──────┬──────┬──────┬──────┬──────╮    ╭──────┬──────┬──────┬──────┬──────╮
+ │S+F1  │S+F2  │S+F3  │S+F4  │S+F5  │    │S+F1  │S+F2  │S+F3  │S+F4  │S+F5  │
+ │F1    │F2    │F3    │F4    │F5    │    │F1    │F2    │F3    │F4    │F5    │
+ ╰──────┴──────┴──────┴────┬─┴──┬───╯    ╰──────┴──────┴──────┴────┬─┴──┬───╯
+ ╭──────┬──────┬──────╮    │S+Up│        ╭──────┬──────┬──────╮    │S+Up│
+ │      │S+Esc │Ins   ├────┤Up  ├─────╮  │      │S+Esc │F11   ├────┤Up  ├─────╮
+ │Tab   │F11   │Del   │Home├────┤End  │  │Tab   │Esc   │PgDn  │Home├────┤End  │
+ ├──────┼──────┼──────┤Left├────┤Right│  ├──────┼──────┼──────┤Left├────┤Right│
+ │      │      │      ├────┤S+Dn├─────╯  │      │      │Ins   ├────┤S+Dn├─────╯
+ │'     │x     │F10   │    │Down│        │'     │      │Del   │    │Down│
+ ├──────┼──────┼──────┼────┴─┬──┴───╮    ├──────┼──────┼──────┼────┴─┬──┴───╮
+ │      │      │      │      │      │    │      │      │      │C+Tab │      │
+ │F6    │F7    │F8    │F9    │Esc   │    │F6    │F7    │F8    │F9    │C+BkSp│
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │      │      │      │#     │    │      │      │      │      │#     │
+ │\     │s     │c     │t     │^     │    │      │      │      │      │^     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │&     │{     │}     │e     │    │      │\     │[     │]     │      │
+ │C+2   │,     │(     │)     │/     │    │      │&     │(     │)     │/     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │u     │v     │w     │[     │    │      │      │      │      │      │
+ │o     │7     │8     │9     │*     │    │C+2   │7     │8     │9     │*     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │      │      │      │      │]     │    │      │      │      │      │      │
+ │l     │4     │5     │6     │-     │    │,     │4     │5     │6     │-     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │<     │      │      │      │      │    │<     │      │      │      │      │
+ │>     │1     │2     │3     │+     │    │>     │1     │2     │3     │+     │
+ ├──────┼──────┼──────┼──────┼──────┤    ├──────┼──────┼──────┼──────┼──────┤
+ │S+F12 │C+Tab │i     │$     │S+Ret │    │S+F12 │      │:     │$     │S+Ret │
+ │F12   │0     │.     │~     │Ret   │    │F12   │0     │.     │~     │Ret   │
+ ╰──────┴──────┴──────┴──────┴──────╯    ╰──────┴──────┴──────┴──────┴──────╯
+ 
+   Not shown:                              Not shown:
+     PageUp    Alpha, Up                     A-Z      Alpha, [A]-[Z]
+     PageDown  Alpha, Down                   a-z      2nd, Alpha, [a]-[z]
+     A-Z       Alpha, [A]-[Z]                Space    Alpha, (-)
+     @         Alpha, 3                      =        Alpha, Sto▸
+     "         Alpha, +
+     Space     Alpha, 0
+     :         Alpha, .
+     ?         Alpha, (-)
+ 
+   TI-83 Plus / TI-84 Plus only:
+     CapsLock + Shift + a-z  =  Alpha, Alpha, [a]-[z]
diff --git a/tool/tilem-src/Makefile.in b/tool/tilem-src/Makefile.in
new file mode 100644
index 0000000..77de808
--- /dev/null
+++ b/tool/tilem-src/Makefile.in
@@ -0,0 +1,91 @@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+bindir = @bindir@
+datadir = @datadir@
+pkgdatadir = @datadir@/tilem2
+mandir = @mandir@
+
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+@SET_MAKE@
+SHELL = @SHELL@
+
+INSTALL = @INSTALL@
+
+distname = @PACKAGE_TARNAME@-@PACKAGE_VERSION@
+
+distfiles = CHANGELOG COPYING INSTALL KEYS NEWS README THANKS TODO \
+	aclocal.m4 config.h.in configure configure.ac install-sh \
+	Makefile.in \
+	data/Makefile.in \
+	data/keybindings.ini \
+	data/desktop/*.desktop data/desktop/*.xml \
+	data/icons/hicolor/index.theme data/icons/hicolor/*/*/*.png \
+	data/icons-svg/*.svg \
+	data/skins/README data/skins/*.skn \
+	data/symbols/*.sym \
+	db/Makefile.in db/*.c db/*.h \
+	emu/Makefile.in emu/*.c emu/*.h emu/x*/*.c emu/x*/*.h \
+	gui/Makefile.in gui/*.c gui/*.h gui/*.ico gui/*.rc.in \
+	installer/win32/Makefile.in \
+	installer/win32/installer.nsi.in installer/win32/gtkrc \
+	installer/win32/COPYING-ZLIB installer/win32/COPYING-PIXMAN
+
+all:
+	cd emu && $(MAKE)
+	cd db && $(MAKE)
+	cd gui && $(MAKE)
+
+clean:
+	cd emu && $(MAKE) clean
+	cd db && $(MAKE) clean
+	cd gui && $(MAKE) clean
+	cd installer/win32 && $(MAKE) clean
+
+install: all
+	cd gui && $(MAKE) install
+	cd data && $(MAKE) install
+
+uninstall:
+	cd gui && $(MAKE) uninstall
+	cd data && $(MAKE) uninstall
+
+install-home: all
+	$(MAKE) install \
+	  bindir=$(HOME)/bin \
+	  datarootdir=$${XDG_DATA_HOME:-$(HOME)/.local/share}
+
+uninstall-home:
+	$(MAKE) uninstall \
+	  bindir=$(HOME)/bin \
+	  datarootdir=$${XDG_DATA_HOME:-$(HOME)/.local/share}
+
+distclean: clean
+	rm -f config.status config.log config.h configure.lineno
+	rm -rf autom4te.cache
+	rm -f installer/win32/Makefile installer/win32/installer.nsi
+	rm -f gui/tilem2.rc
+	rm -f Makefile emu/Makefile db/Makefile gui/Makefile data/Makefile
+
+dist:
+	rm -rf $(distname)
+	mkdir $(distname)
+	set -e ; files=`cd $(srcdir) ; echo $(distfiles)` ; \
+	for f in $$files ; do \
+	  dir=`echo $(distname)/$$f | sed 's,/[^/]*$$,,'` ; \
+	  [ -d $$dir ] || $(INSTALL) -d $$dir ; \
+	  cp -p $(srcdir)/$$f $$dir ; \
+	done
+	tar cv $(distname) | bzip2 -c -9 > $(distname).tar.bz2
+
+Makefile: Makefile.in config.status
+	$(SHELL) ./config.status
+
+config.status: configure
+	$(SHELL) ./config.status --recheck
+
+.PRECIOUS: Makefile config.status
+.PHONY: all clean dist distclean install install-home uninstall uninstall-home
diff --git a/tool/tilem-src/NEWS b/tool/tilem-src/NEWS
new file mode 100644
index 0000000..45ef967
--- /dev/null
+++ b/tool/tilem-src/NEWS
@@ -0,0 +1,73 @@
+                           Version History
+                          -----------------
+
+2012-06-07 -- version 2.0
+
+  This is the first official release of the "new" TilEm.  Much of the
+  old TilEm code has been rewritten, and there are many improvements
+  (and probably some new bugs.)
+
+  Please note, if you have used older versions of TilEm:
+
+    * Your existing ROM files and settings in ~/.TilEm will not be
+      used (in fact, you can keep TilEm 0.97x installed alongside
+      TilEm 2.0 if you wish.)  TilEm 2.0 no longer uses a "library" of
+      ROM files; you can store ROM files anywhere you like.
+
+    * TilEm 2.0 uses a new format for calculator state (SAV) files.
+      State files created by TilEm 0.97x can be loaded by TilEm 2.0,
+      but if you then save the state, it will be stored in the new
+      format, which older versions of TilEm will not support.
+
+  New features and bugs fixed since version 0.975 include:
+
+    * All code that was covered by the Z80em license has been removed.
+
+    * Support for the TI-81 (both hardware versions) and TI-76.fr, and
+      experimental support for the TI-Nspire's TI-84 Plus emulation
+      mode.
+
+    * Many hardware emulation improvements for all calculator models.
+      In particular, major improvements have been made concerning Z80
+      interrupts, timers, the LCD driver, and the link port.
+
+    * The emulator window uses TiEmu-format skin files.
+
+    * Greatly improved grayscale emulation.
+
+    * Commands for saving still screenshots (in PNG, BMP, JPEG, or GIF
+      format) and animations (GIF format only.)
+
+    * Keypad macros can be recorded and replayed.
+
+    * Programs and/or ROM files can be loaded from the command line.
+
+    * Link I/O uses libticalcs2, which allows all types of variables,
+      as well as Flash apps and OSes, to be transferred through the
+      link port.  For the TI-81, PRG files can be transferred to and
+      from the calculator memory directly.
+
+    * TilEm does not consume 100% of the host CPU when idle.
+
+    * Improved disassembler (macros; distinct "labels" and "romcalls";
+      named IY flags.)
+
+    * The debugger offers a "Finish Subroutine" command.  In addition,
+      the "Step Over" command behaves more sensibly.
+
+    * Breakpoints can be set on absolute memory addresses, and on Z80
+      opcodes.
+
+    * Many minor improvements.
+
+  Features of 0.975 that are not yet supported in TilEm 2.0 include:
+
+    * External link cables.
+
+    * Custom symbol files in the disassembler.
+
+    * Program counter history tracking.
+
+  Most of the new code is due to Benjamin Moody (floppusmaximus) and
+  Thibault Duponchelle (contra-sh).  See THANKS for a full list of
+  contributors.
diff --git a/tool/tilem-src/README b/tool/tilem-src/README
new file mode 100644
index 0000000..bcd613c
--- /dev/null
+++ b/tool/tilem-src/README
@@ -0,0 +1,92 @@
+                                TilEm
+                               -------
+
+TilEm is an emulator and debugger for Texas Instruments Z80-based
+graphing calculators.  It can emulate any of the following calculator
+models:
+
+    TI-73 / TI-73 Explorer
+    TI-76.fr
+    TI-81
+    TI-82
+    TI-82 STATS / TI-82 STATS.fr
+    TI-83
+    TI-83 Plus / TI-83 Plus Silver Edition / TI-83 Plus.fr
+    TI-84 Plus / TI-84 Plus Silver Edition / TI-84 pocket.fr
+    TI-85
+    TI-86
+
+TilEm fully supports all known versions of the above calculators (as
+of 2012), and attempts to reproduce the behavior of the original
+calculator hardware as faithfully as possible.
+
+In addition, TilEm can emulate the TI-Nspire's virtual TI-84 Plus
+mode.  This is currently experimental, and some programs may not work
+correctly.
+
+TilEm runs on the X Window System on GNU/Linux and other Unix-like
+platforms, as well as on Microsoft Windows, and any other platform
+supported by the GTK+ library.
+
+
+                             Installation
+                            --------------
+Packages for Microsoft Windows are available from the TilEm project
+website (http://lpg.ticalc.org/prj_tilem/).  For other platforms, you
+will need to compile TilEm from source; please see the file 'INSTALL'
+in the source package.
+
+
+                             Using TilEm
+                            -------------
+TilEm requires a copy of the operating system from the calculator
+model(s) you wish to emulate.  This file is called a "ROM image",
+since the calculator OS was traditionally stored in Read-Only Memory.
+ROM images are copyrighted by TI and may not be distributed without
+permission.  See the TilEm User's Manual for more information about
+how to create a ROM image.
+
+The main TilEm window shows an image of the calculator (if possible -
+we are still missing background images for a few models.)  Clicking
+with the left mouse button presses a key; clicking with the middle
+button presses a key and holds it down.  Clicking with the right
+button, or pressing Shift+F10, opens the menu.
+
+When you run TilEm for the first time, it will ask you to select a ROM
+image to use.  The file you select will be used by default the next
+time you run TilEm.  You can switch to a different ROM image by
+right-clicking and selecting "Open Calculator".
+
+The state of the emulated calculator is not saved by default; to save
+the state, right-click and select "Save Calculator".  The state is
+saved to a ".sav" file, stored in the same directory as the ROM image.
+
+You can send program and variable files to the emulated calculator,
+either by dragging and dropping them from your file manager, or by
+right-clicking and selecting "Send File".  To retrieve program or
+variable files from the calculator, select "Receive File".
+
+Other features of TilEm include:
+
+ - A debugger for assembly programs
+ - Capturing screenshots, both normal and animated
+ - Recording and replaying keystroke macros
+
+For more information, see the TilEm User's Manual:
+  http://lpg.ticalc.org/prj_tilem2/doc.html
+
+
+                          About this program
+                         --------------------
+Many people deserve credit for helping to make this program possible.
+See the file 'THANKS'.
+
+This program is free software, which means that you are allowed to
+modify it, and to distribute it (or your modified version) to others.
+When you received this program, you should also have been offered a
+complete copy of its source code.  For more information, see the file
+'COPYING'.
+
+You can contact the authors at <tilem-devel@lists.sourceforge.net>.
+Please let us know of any problems you encounter, or ideas for how we
+could make TilEm better.
diff --git a/tool/tilem-src/THANKS b/tool/tilem-src/THANKS
new file mode 100644
index 0000000..b76e949
--- /dev/null
+++ b/tool/tilem-src/THANKS
@@ -0,0 +1,37 @@
+                                Thanks
+                               --------
+The current maintainers of TilEm are Thibault Duponchelle and Benjamin
+Moody, but many other people have played a part in making this program
+possible.
+
+The original TilEm was written in 2001 by Julien Solignac.  The
+current version is partially based on the original, but large portions
+have been rewritten, starting in 2009, by Benjamin Moody and Thibault
+Duponchelle.  Portions of the hardware emulation code are also due to
+Luc Bruant.
+
+The code for reading skin files is based on code from TiEmu, written
+by Julien Blache.  The GIF compression code is based on whirlgif,
+written by Hans Dinsen-Hansen and Michael A. Mayer.
+
+Thanks to Claude Clerc for the photo of his TI-83 Plus, and to Danilo
+Šegan for the photo of his TI-86, which we have used as skins.  Thanks
+to Scott Zeid for the design of the program icon.
+
+TilEm uses the TiLP libraries (libticalcs2, libticables2, libtifiles2,
+and libticonv) to send and receive variables.  Thanks are due to the
+current maintainer of TiLP, Lionel Debroux, for his assistance, as
+well as to all of the past maintainers of TiLP, including Romain
+Liévin, Kevin Kofler, and Julien Blache.
+
+Finally, this program would never have been possible without the
+efforts of countless programmers and researchers over the years to
+discover and document the inner workings of the calculator hardware.
+The following people deserve special recognition for their research:
+Randy Compton, Tijl Coosemans, Brian Coventry, Dan Eble, Dan
+Englender, Dines Justesen, Julien Lasson, Mattias Lindqvist, James
+Montelongo, Michael Vincent, Brandon Wilson, and Joerg Woerner.
+
+(Thibault) In addition of above:
+Thanks to Michael Nock and Guillaume Hoffman for testing, feature request and encouragement.
+Thanks to Xavier Andreani for his encouragement.
diff --git a/tool/tilem-src/TODO b/tool/tilem-src/TODO
new file mode 100644
index 0000000..53b4599
--- /dev/null
+++ b/tool/tilem-src/TODO
@@ -0,0 +1,7 @@
+- Linking between 2 tilem instances
+- Sound
+- Rewrite macro (parser and tokens).
+- Add scripting (lua or something else?)
+- Teacher mode (record keys pressed and produce a picture file with keys icons)
+
+
diff --git a/tool/tilem-src/aclocal.m4 b/tool/tilem-src/aclocal.m4
new file mode 100644
index 0000000..af3c83d
--- /dev/null
+++ b/tool/tilem-src/aclocal.m4
@@ -0,0 +1,173 @@
+# generated automatically by aclocal 1.11.1 -*- Autoconf -*-
+
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+# 2005, 2006, 2007, 2008, 2009  Free Software Foundation, Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+# pkg.m4 - Macros to locate and utilise pkg-config.            -*- Autoconf -*-
+# serial 1 (pkg-config-0.24)
+# 
+# Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# PKG_PROG_PKG_CONFIG([MIN-VERSION])
+# ----------------------------------
+AC_DEFUN([PKG_PROG_PKG_CONFIG],
+[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
+m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$])
+m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])
+AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])
+AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path])
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+	AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
+fi
+if test -n "$PKG_CONFIG"; then
+	_pkg_min_version=m4_default([$1], [0.9.0])
+	AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
+	if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+		AC_MSG_RESULT([yes])
+	else
+		AC_MSG_RESULT([no])
+		PKG_CONFIG=""
+	fi
+fi[]dnl
+])# PKG_PROG_PKG_CONFIG
+
+# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# Check to see whether a particular set of modules exists.  Similar
+# to PKG_CHECK_MODULES(), but does not set variables or print errors.
+#
+# Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+# only at the first occurence in configure.ac, so if the first place
+# it's called might be skipped (such as if it is within an "if", you
+# have to call PKG_CHECK_EXISTS manually
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_EXISTS],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+if test -n "$PKG_CONFIG" && \
+    AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
+  m4_default([$2], [:])
+m4_ifvaln([$3], [else
+  $3])dnl
+fi])
+
+# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
+# ---------------------------------------------
+m4_define([_PKG_CONFIG],
+[if test -n "$$1"; then
+    pkg_cv_[]$1="$$1"
+ elif test -n "$PKG_CONFIG"; then
+    PKG_CHECK_EXISTS([$3],
+                     [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes ],
+		     [pkg_failed=yes])
+ else
+    pkg_failed=untried
+fi[]dnl
+])# _PKG_CONFIG
+
+# _PKG_SHORT_ERRORS_SUPPORTED
+# -----------------------------
+AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi[]dnl
+])# _PKG_SHORT_ERRORS_SUPPORTED
+
+
+# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+# [ACTION-IF-NOT-FOUND])
+#
+#
+# Note that if there is a possibility the first call to
+# PKG_CHECK_MODULES might not happen, you should be sure to include an
+# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
+#
+#
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_MODULES],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
+AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
+
+pkg_failed=no
+AC_MSG_CHECKING([for $1])
+
+_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
+_PKG_CONFIG([$1][_LIBS], [libs], [$2])
+
+m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
+and $1[]_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.])
+
+if test $pkg_failed = yes; then
+   	AC_MSG_RESULT([no])
+        _PKG_SHORT_ERRORS_SUPPORTED
+        if test $_pkg_short_errors_supported = yes; then
+	        $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1`
+        else 
+	        $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
+
+	m4_default([$4], [AC_MSG_ERROR(
+[Package requirements ($2) were not met:
+
+$$1_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+_PKG_TEXT])[]dnl
+        ])
+elif test $pkg_failed = untried; then
+     	AC_MSG_RESULT([no])
+	m4_default([$4], [AC_MSG_FAILURE(
+[The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+_PKG_TEXT
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.])[]dnl
+        ])
+else
+	$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
+	$1[]_LIBS=$pkg_cv_[]$1[]_LIBS
+        AC_MSG_RESULT([yes])
+	$3
+fi[]dnl
+])# PKG_CHECK_MODULES
+
diff --git a/tool/tilem-src/config.h.in b/tool/tilem-src/config.h.in
new file mode 100644
index 0000000..f2aa9aa
--- /dev/null
+++ b/tool/tilem-src/config.h.in
@@ -0,0 +1,91 @@
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define if building universal (internal helper macro) */
+#undef AC_APPLE_UNIVERSAL_BUILD
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if the system has the type `uintptr_t'. */
+#undef HAVE_UINTPTR_T
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+   significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+#  define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+#  undef WORDS_BIGENDIAN
+# endif
+#endif
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+   calls it, or to nothing if 'inline' is not supported under any name.  */
+#ifndef __cplusplus
+#undef inline
+#endif
+
+/* Define to the equivalent of the C99 'restrict' keyword, or to
+   nothing if this is not supported.  Do not define if restrict is
+   supported directly.  */
+#undef restrict
+/* Work around a bug in Sun C++: it does not support _Restrict or
+   __restrict__, even though the corresponding Sun C compiler ends up with
+   "#define restrict _Restrict" or "#define restrict __restrict__" in the
+   previous line.  Perhaps some future version of Sun C++ will work with
+   restrict; if so, hopefully it defines __RESTRICT like Sun C does.  */
+#if defined __SUNPRO_CC && !defined __RESTRICT
+# define _Restrict
+# define __restrict__
+#endif
+
+/* Define to the type of an unsigned integer type wide enough to hold a
+   pointer, if such a type exists, and if the system does not define it. */
+#undef uintptr_t
diff --git a/tool/tilem-src/configure b/tool/tilem-src/configure
new file mode 100755
index 0000000..cc4e5ad
--- /dev/null
+++ b/tool/tilem-src/configure
@@ -0,0 +1,6127 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.68 for TilEm 2.0.
+#
+# Report bugs to <tilem-devel@lists.sourceforge.net>.
+#
+#
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
+# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
+# Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+	expr "X$arg" : "X\\(.*\\)$as_nl";
+	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+if test "x$CONFIG_SHELL" = x; then
+  as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+"
+  as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+  exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1"
+  as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+  as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+  eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+  if (eval "$as_required") 2>/dev/null; then :
+  as_have_required=yes
+else
+  as_have_required=no
+fi
+  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  as_found=:
+  case $as_dir in #(
+	 /*)
+	   for as_base in sh bash ksh sh5; do
+	     # Try only shells that exist, to save several forks.
+	     as_shell=$as_dir/$as_base
+	     if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+		    { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  CONFIG_SHELL=$as_shell as_have_required=yes
+		   if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  break 2
+fi
+fi
+	   done;;
+       esac
+  as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+	      { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+  CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+      if test "x$CONFIG_SHELL" != x; then :
+  # We cannot yet assume a decent shell, so we have to provide a
+	# neutralization value for shells without unset; and this also
+	# works around shells that cannot unset nonexistent variables.
+	# Preserve -v and -x to the replacement shell.
+	BASH_ENV=/dev/null
+	ENV=/dev/null
+	(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+	export CONFIG_SHELL
+	case $- in # ((((
+	  *v*x* | *x*v* ) as_opts=-vx ;;
+	  *v* ) as_opts=-v ;;
+	  *x* ) as_opts=-x ;;
+	  * ) as_opts= ;;
+	esac
+	exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"}
+fi
+
+    if test x$as_have_required = xno; then :
+  $as_echo "$0: This script requires a shell more modern than all"
+  $as_echo "$0: the shells that I found on your system."
+  if test x${ZSH_VERSION+set} = xset ; then
+    $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+    $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+  else
+    $as_echo "$0: Please tell bug-autoconf@gnu.org and
+$0: tilem-devel@lists.sourceforge.net about your system,
+$0: including any error possibly output before this
+$0: message. Then install a modern shell, or manually run
+$0: the script under such a shell if you do have one."
+  fi
+  exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_dir" : 'X\(//\)[^/]' \| \
+	 X"$as_dir" : 'X\(//\)$' \| \
+	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+	 X"$0" : 'X\(//\)$' \| \
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+  as_lineno_1=$LINENO as_lineno_1a=$LINENO
+  as_lineno_2=$LINENO as_lineno_2a=$LINENO
+  eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+  test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+  # Blame Lee E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='	';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -p'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -p'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -p'
+  fi
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+	test -d "$1/.";
+      else
+	case $1 in #(
+	-*)set "./$1";;
+	esac;
+	case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #((
+	???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='TilEm'
+PACKAGE_TARNAME='tilem'
+PACKAGE_VERSION='2.0'
+PACKAGE_STRING='TilEm 2.0'
+PACKAGE_BUGREPORT='tilem-devel@lists.sourceforge.net'
+PACKAGE_URL='http://tilem.sourceforge.net/'
+
+ac_unique_file="emu/tilem.h"
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='LTLIBOBJS
+LIBOBJS
+EGREP
+GREP
+DLLPATH
+TICALCS_BINDIR
+GTK_BINDIR
+LN_S
+MAKENSIS
+WINDRES
+OBJDUMP
+STRIP
+TICALCS_LIBS
+TICALCS_CFLAGS
+gui_extra_objects
+GUI_LDFLAGS
+GTK_LIBS
+GTK_CFLAGS
+PKG_CONFIG_LIBDIR
+PKG_CONFIG_PATH
+PKG_CONFIG
+UPDATE_MIME_DATABASE
+UPDATE_DESKTOP_DATABASE
+SET_MAKE
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+RANLIB
+AR_FLAGS
+AR
+OPT_CFLAGS
+CPP
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+enable_gtk_deprecated
+'
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CPP
+OPT_CFLAGS
+AR
+AR_FLAGS
+RANLIB
+PKG_CONFIG
+PKG_CONFIG_PATH
+PKG_CONFIG_LIBDIR
+GTK_CFLAGS
+GTK_LIBS
+TICALCS_CFLAGS
+TICALCS_LIBS'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval $ac_prev=\$ac_option
+    ac_prev=
+    continue
+  fi
+
+  case $ac_option in
+  *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *=)   ac_optarg= ;;
+  *)    ac_optarg=yes ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
+    datadir=$ac_optarg ;;
+
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=no ;;
+
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
+  -enable-* | --enable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=\$ac_optarg ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst | --locals)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=\$ac_optarg ;;
+
+  -without-* | --without-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=no ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    case $ac_envvar in #(
+      '' | [0-9]* | *[!_$as_cr_alnum]* )
+      as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+    esac
+    eval $ac_envvar=\$ac_optarg
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+  case $enable_option_checking in
+    no) ;;
+    fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+    *)     $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+  esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in	exec_prefix prefix bindir sbindir libexecdir datarootdir \
+		datadir sysconfdir sharedstatedir localstatedir includedir \
+		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+		libdir localedir mandir
+do
+  eval ac_val=\$$ac_var
+  # Remove trailing slashes.
+  case $ac_val in
+    */ )
+      ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+      eval $ac_var=\$ac_val;;
+  esac
+  # Be sure to have absolute directory names.
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* )  continue;;
+    NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+  esac
+  as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+    $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host.
+    If a cross compiler is detected then cross compile mode will be used" >&2
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+  as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+  as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then the parent directory.
+  ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_myself" : 'X\(//\)[^/]' \| \
+	 X"$as_myself" : 'X\(//\)$' \| \
+	 X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r "$srcdir/$ac_unique_file"; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+  test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+  as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+	cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+	pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+  srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+  eval ac_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_env_${ac_var}_value=\$${ac_var}
+  eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures TilEm 2.0 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking ...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc [/usr/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --docdir=DIR            documentation root [DATAROOTDIR/doc/tilem]
+  --htmldir=DIR           html documentation [DOCDIR]
+  --dvidir=DIR            dvi documentation [DOCDIR]
+  --pdfdir=DIR            pdf documentation [DOCDIR]
+  --psdir=DIR             ps documentation [DOCDIR]
+_ACEOF
+
+  cat <<\_ACEOF
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+  case $ac_init_help in
+     short | recursive ) echo "Configuration of TilEm 2.0:";;
+   esac
+  cat <<\_ACEOF
+
+Optional Features:
+  --disable-option-checking  ignore unrecognized --enable/--with options
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --disable-gtk-deprecated
+                          Disable deprecated GTK+ API
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  LIBS        libraries to pass to the linker, e.g. -l<library>
+  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+              you have headers in a nonstandard directory <include dir>
+  CPP         C preprocessor
+  OPT_CFLAGS  Additional C compiler flags used for optimizing critical areas
+              of the code (default: -O3 if using GCC)
+  AR          Static library archiver
+  AR_FLAGS    Flags to pass to ar to build a static library
+  RANLIB      Program to make a static library linkable
+  PKG_CONFIG  path to pkg-config utility
+  PKG_CONFIG_PATH
+              directories to add to pkg-config's search path
+  PKG_CONFIG_LIBDIR
+              path overriding pkg-config's built-in search path
+  GTK_CFLAGS  C compiler flags for GTK, overriding pkg-config
+  GTK_LIBS    linker flags for GTK, overriding pkg-config
+  TICALCS_CFLAGS
+              C compiler flags for TICALCS, overriding pkg-config
+  TICALCS_LIBS
+              linker flags for TICALCS, overriding pkg-config
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <tilem-devel@lists.sourceforge.net>.
+TilEm home page: <http://tilem.sourceforge.net/>.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d "$ac_dir" ||
+      { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+      continue
+    ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+    cd "$ac_dir" || { ac_status=$?; continue; }
+    # Check for guested configure.
+    if test -f "$ac_srcdir/configure.gnu"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+    elif test -f "$ac_srcdir/configure"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure" --help=recursive
+    else
+      $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi || ac_status=$?
+    cd "$ac_pwd" || { ac_status=$?; break; }
+  done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+  cat <<\_ACEOF
+TilEm configure 2.0
+generated by GNU Autoconf 2.68
+
+Copyright (C) 2010 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+	 test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext conftest$ac_exeext
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+	 test "$cross_compiling" = yes ||
+	 $as_test_x conftest$ac_exeext
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+  # interfere with the next link command; also delete a directory that is
+  # left behind by Apple's compiler.  We do this before executing the actions.
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $2 (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main ()
+{
+return $2 ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: program exited with status $ac_status" >&5
+       $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=$ac_status
+fi
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
+# ac_fn_c_check_type LINENO TYPE VAR INCLUDES
+# -------------------------------------------
+# Tests whether TYPE exists after having included INCLUDES, setting cache
+# variable VAR accordingly.
+ac_fn_c_check_type ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  eval "$3=no"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+if (sizeof ($2))
+	 return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+if (sizeof (($2)))
+	    return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  eval "$3=yes"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_type
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by TilEm $as_me 2.0, which was
+generated by GNU Autoconf 2.68.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo      = `(/usr/bin/hostinfo) 2>/dev/null      || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    $as_echo "PATH: $as_dir"
+  done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *\'*)
+      ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+    2)
+      as_fn_append ac_configure_args1 " '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+	ac_must_keep_next=false # Got value, back to normal.
+      else
+	case $ac_arg in
+	  *=* | --config-cache | -C | -disable-* | --disable-* \
+	  | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+	  | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+	  | -with-* | --with-* | -without-* | --without-* | --x)
+	    case "$ac_configure_args0 " in
+	      "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+	    esac
+	    ;;
+	  -* ) ac_must_keep_next=true ;;
+	esac
+      fi
+      as_fn_append ac_configure_args " '$ac_arg'"
+      ;;
+    esac
+  done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+(
+  for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+  (set) 2>&1 |
+    case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      sed -n \
+	"s/'\''/'\''\\\\'\'''\''/g;
+	  s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+      ;; #(
+    *)
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+)
+    echo
+
+    $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=\$$ac_var
+      case $ac_val in
+      *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      esac
+      $as_echo "$ac_var='\''$ac_val'\''"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+      echo
+      for ac_var in $ac_subst_files
+      do
+	eval ac_val=\$$ac_var
+	case $ac_val in
+	*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+	esac
+	$as_echo "$ac_var='\''$ac_val'\''"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+      echo
+      cat confdefs.h
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      $as_echo "$as_me: caught signal $ac_signal"
+    $as_echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core core.conftest.* &&
+    rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+  # We do not want a PATH search for config.site.
+  case $CONFIG_SITE in #((
+    -*)  ac_site_file1=./$CONFIG_SITE;;
+    */*) ac_site_file1=$CONFIG_SITE;;
+    *)   ac_site_file1=./$CONFIG_SITE;;
+  esac
+elif test "x$prefix" != xNONE; then
+  ac_site_file1=$prefix/share/config.site
+  ac_site_file2=$prefix/etc/config.site
+else
+  ac_site_file1=$ac_default_prefix/share/config.site
+  ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+  test "x$ac_site_file" = xNONE && continue
+  if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file" \
+      || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special files
+  # actually), so we avoid doing that.  DJGPP emulates it as a regular file.
+  if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . "$cache_file";;
+      *)                      . "./$cache_file";;
+    esac
+  fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+	# differences in whitespace do not lead to failure.
+	ac_old_val_w=`echo x $ac_old_val`
+	ac_new_val_w=`echo x $ac_new_val`
+	if test "$ac_old_val_w" != "$ac_new_val_w"; then
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+	  ac_cache_corrupted=:
+	else
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+	  eval $ac_var=\$ac_old_val
+	fi
+	{ $as_echo "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
+$as_echo "$as_me:   former value:  \`$ac_old_val'" >&2;}
+	{ $as_echo "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
+$as_echo "$as_me:   current value: \`$ac_new_val'" >&2;}
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+# Checks for programs
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+  esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link_default") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile.  We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+	;;
+    [ab].out )
+	# We found the default executable, but exeext='' is most
+	# certainly right.
+	break;;
+    *.* )
+	if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+	then :; else
+	   ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	fi
+	# We set ac_cv_exeext here because the later test for it is not
+	# safe: cross compilers may not add the suffix if given an `-o'
+	# argument, so we may need to know it at that point already.
+	# Even if this section looks crufty: it has the advantage of
+	# actually working.
+	break;;
+    * )
+	break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+  ac_file=''
+fi
+if test -z "$ac_file"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	  break;;
+    * ) break;;
+  esac
+done
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+  { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+  if { ac_try='./conftest$ac_cv_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+	cross_compiling=yes
+    else
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+    fi
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+else
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  ac_c_werror_flag=$ac_save_c_werror_flag
+	 CFLAGS="-g"
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+	-Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if ${ac_cv_prog_CPP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+      # Double quotes because CPP needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+  break
+fi
+
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+if test "x$GCC" = "xyes" ; then
+  CFLAGS="$CFLAGS -W -Wall -Wwrite-strings"
+  if test "x$OPT_CFLAGS" = "x" ; then
+    OPT_CFLAGS="-O3"
+  fi
+fi
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ar; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AR+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$AR"; then
+  ac_cv_prog_AR="$AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_AR="${ac_tool_prefix}ar"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+AR=$ac_cv_prog_AR
+if test -n "$AR"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5
+$as_echo "$AR" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_AR"; then
+  ac_ct_AR=$AR
+  # Extract the first word of "ar", so it can be a program name with args.
+set dummy ar; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_AR+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_AR"; then
+  ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_AR="ar"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_AR=$ac_cv_prog_ac_ct_AR
+if test -n "$ac_ct_AR"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5
+$as_echo "$ac_ct_AR" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_AR" = x; then
+    AR="false"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    AR=$ac_ct_AR
+  fi
+else
+  AR="$ac_cv_prog_AR"
+fi
+
+
+
+if test "x$AR_FLAGS" = "x" ; then
+  AR_FLAGS=cru
+fi
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$RANLIB"; then
+  ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+RANLIB=$ac_cv_prog_RANLIB
+if test -n "$RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5
+$as_echo "$RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_RANLIB"; then
+  ac_ct_RANLIB=$RANLIB
+  # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_RANLIB"; then
+  ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_RANLIB="ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB
+if test -n "$ac_ct_RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5
+$as_echo "$ac_ct_RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_RANLIB" = x; then
+    RANLIB=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    RANLIB=$ac_ct_RANLIB
+  fi
+else
+  RANLIB="$ac_cv_prog_RANLIB"
+fi
+
+
+
+ac_aux_dir=
+for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
+  if test -f "$ac_dir/install-sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f "$ac_dir/install.sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  elif test -f "$ac_dir/shtool"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/shtool install -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"  # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"  # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
+
+
+# Find a good install program.  We prefer a C program (faster),
+# so one script is as good as another.  But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+$as_echo_n "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if ${ac_cv_path_install+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in #((
+  ./ | .// | /[cC]/* | \
+  /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+  ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+  /usr/ucb/* ) ;;
+  *)
+    # OSF1 and SCO ODT 3.0 have their own names for install.
+    # Don't use installbsd from OSF since it installs stuff as root
+    # by default.
+    for ac_prog in ginstall scoinst install; do
+      for ac_exec_ext in '' $ac_executable_extensions; do
+	if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then
+	  if test $ac_prog = install &&
+	    grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+	    # AIX install.  It has an incompatible calling convention.
+	    :
+	  elif test $ac_prog = install &&
+	    grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+	    # program-specific install script used by HP pwplus--don't use.
+	    :
+	  else
+	    rm -rf conftest.one conftest.two conftest.dir
+	    echo one > conftest.one
+	    echo two > conftest.two
+	    mkdir conftest.dir
+	    if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" &&
+	      test -s conftest.one && test -s conftest.two &&
+	      test -s conftest.dir/conftest.one &&
+	      test -s conftest.dir/conftest.two
+	    then
+	      ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+	      break 3
+	    fi
+	  fi
+	fi
+      done
+    done
+    ;;
+esac
+
+  done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+  if test "${ac_cv_path_install+set}" = set; then
+    INSTALL=$ac_cv_path_install
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for INSTALL within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    INSTALL=$ac_install_sh
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+$as_echo "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+	@echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+  *@@@%%%=?*=@@@%%%*)
+    eval ac_cv_prog_make_${ac_make}_set=yes;;
+  *)
+    eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+  SET_MAKE=
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+  SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+
+# Extract the first word of "update-desktop-database", so it can be a program name with args.
+set dummy update-desktop-database; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_UPDATE_DESKTOP_DATABASE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$UPDATE_DESKTOP_DATABASE"; then
+  ac_cv_prog_UPDATE_DESKTOP_DATABASE="$UPDATE_DESKTOP_DATABASE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_UPDATE_DESKTOP_DATABASE="update-desktop-database"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_prog_UPDATE_DESKTOP_DATABASE" && ac_cv_prog_UPDATE_DESKTOP_DATABASE=":"
+fi
+fi
+UPDATE_DESKTOP_DATABASE=$ac_cv_prog_UPDATE_DESKTOP_DATABASE
+if test -n "$UPDATE_DESKTOP_DATABASE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $UPDATE_DESKTOP_DATABASE" >&5
+$as_echo "$UPDATE_DESKTOP_DATABASE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+# Extract the first word of "update-mime-database", so it can be a program name with args.
+set dummy update-mime-database; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_UPDATE_MIME_DATABASE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$UPDATE_MIME_DATABASE"; then
+  ac_cv_prog_UPDATE_MIME_DATABASE="$UPDATE_MIME_DATABASE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_UPDATE_MIME_DATABASE="update-mime-database"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_prog_UPDATE_MIME_DATABASE" && ac_cv_prog_UPDATE_MIME_DATABASE=":"
+fi
+fi
+UPDATE_MIME_DATABASE=$ac_cv_prog_UPDATE_MIME_DATABASE
+if test -n "$UPDATE_MIME_DATABASE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $UPDATE_MIME_DATABASE" >&5
+$as_echo "$UPDATE_MIME_DATABASE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+# Checks for libraries
+
+
+
+# GLib and GTK+
+
+
+
+
+
+
+
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+	if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
+set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_path_PKG_CONFIG"; then
+  ac_pt_PKG_CONFIG=$PKG_CONFIG
+  # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $ac_pt_PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
+if test -n "$ac_pt_PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
+$as_echo "$ac_pt_PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_pt_PKG_CONFIG" = x; then
+    PKG_CONFIG=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    PKG_CONFIG=$ac_pt_PKG_CONFIG
+  fi
+else
+  PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
+fi
+
+fi
+if test -n "$PKG_CONFIG"; then
+	_pkg_min_version=0.9.0
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
+$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
+	if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	else
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+		PKG_CONFIG=""
+	fi
+fi
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for GTK" >&5
+$as_echo_n "checking for GTK... " >&6; }
+
+if test -n "$GTK_CFLAGS"; then
+    pkg_cv_GTK_CFLAGS="$GTK_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_GTK_CFLAGS=`$PKG_CONFIG --cflags "gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$GTK_LIBS"; then
+    pkg_cv_GTK_LIBS="$GTK_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_GTK_LIBS=`$PKG_CONFIG --libs "gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        GTK_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0" 2>&1`
+        else
+	        GTK_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$GTK_PKG_ERRORS" >&5
+
+	as_fn_error $? "Package requirements (gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0) were not met:
+
+$GTK_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables GTK_CFLAGS
+and GTK_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details." "$LINENO" 5
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables GTK_CFLAGS
+and GTK_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details" "$LINENO" 5; }
+else
+	GTK_CFLAGS=$pkg_cv_GTK_CFLAGS
+	GTK_LIBS=$pkg_cv_GTK_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+fi
+
+# Check whether --enable-gtk-deprecated was given.
+if test "${enable_gtk_deprecated+set}" = set; then :
+  enableval=$enable_gtk_deprecated;  enable_gtk_deprecated=$enableval
+else
+   enable_gtk_deprecated=yes
+fi
+
+if test "x$enable_gtk_deprecated" = "xno" ; then
+  GTK_CFLAGS="$GTK_CFLAGS -DG_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED -DGDK_DISABLE_DEPRECATED -DGSEAL_ENABLE"
+fi
+
+# If using the native Windows version of GTK+, be sure to use
+# -mms-bitfields for all compilation.  Also, use -mwindows for linking
+# GUI programs.
+
+# (If not using pkg-config, you're on your own)
+
+if test "x$PKG_CONFIG" != "x" ; then
+  gtk_target=`$PKG_CONFIG --variable=target gtk+-2.0`
+fi
+
+if test "x$gtk_target" = "xwin32" && test "x$GCC" = "xyes" ; then
+  CFLAGS="$CFLAGS -mms-bitfields"
+  GUI_LDFLAGS="-mwindows"
+  LIBS="-lcomdlg32 -lshell32 -lole32 $LIBS"
+  gui_extra_objects="tilem2rc.o"
+else
+  GUI_LDFLAGS=""
+  gui_extra_objects=""
+fi
+
+
+
+
+
+  save_cflags="$CFLAGS"
+  save_libs="$LIBS"
+  CFLAGS="$CFLAGS $GTK_CFLAGS"
+  LIBS="$LIBS $GTK_LIBS"
+
+ac_fn_c_check_func "$LINENO" "gtk_init" "ac_cv_func_gtk_init"
+if test "x$ac_cv_func_gtk_init" = xyes; then :
+   have_gtk=yes
+else
+   have_gtk=no
+fi
+
+  CFLAGS="$save_cflags"
+  LIBS="$save_libs"
+
+if test "x$have_gtk" != "xyes" ; then
+  as_fn_error $? "GTK+ 2.x libraries not found or not usable.
+You must install a recent version of GTK+ 2.x, including the
+-dev/-devel packages if appropriate." "$LINENO" 5
+fi
+
+# Libticalcs2 and related libraries
+
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for TICALCS" >&5
+$as_echo_n "checking for TICALCS... " >&6; }
+
+if test -n "$TICALCS_CFLAGS"; then
+    pkg_cv_TICALCS_CFLAGS="$TICALCS_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ticalcs2 ticables2 tifiles2 ticonv\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "ticalcs2 ticables2 tifiles2 ticonv") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_TICALCS_CFLAGS=`$PKG_CONFIG --cflags "ticalcs2 ticables2 tifiles2 ticonv" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$TICALCS_LIBS"; then
+    pkg_cv_TICALCS_LIBS="$TICALCS_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ticalcs2 ticables2 tifiles2 ticonv\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "ticalcs2 ticables2 tifiles2 ticonv") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_TICALCS_LIBS=`$PKG_CONFIG --libs "ticalcs2 ticables2 tifiles2 ticonv" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        TICALCS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ticalcs2 ticables2 tifiles2 ticonv" 2>&1`
+        else
+	        TICALCS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ticalcs2 ticables2 tifiles2 ticonv" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$TICALCS_PKG_ERRORS" >&5
+
+	 have_ticalcs=no
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+	 have_ticalcs=no
+else
+	TICALCS_CFLAGS=$pkg_cv_TICALCS_CFLAGS
+	TICALCS_LIBS=$pkg_cv_TICALCS_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	 have_ticalcs=maybe
+fi
+
+if test "x$have_ticalcs" = "xmaybe" ; then
+
+  save_cflags="$CFLAGS"
+  save_libs="$LIBS"
+  CFLAGS="$CFLAGS $TICALCS_CFLAGS"
+  LIBS="$LIBS $TICALCS_LIBS"
+   ac_fn_c_check_func "$LINENO" "ticalcs_library_init" "ac_cv_func_ticalcs_library_init"
+if test "x$ac_cv_func_ticalcs_library_init" = xyes; then :
+   have_ticalcs=yes
+else
+   have_ticalcs=no
+fi
+
+  CFLAGS="$save_cflags"
+  LIBS="$save_libs"
+
+fi
+
+if test "x$have_ticalcs" != "xyes" ; then
+  as_fn_error $? "libticalcs2 not found or not usable.
+
+$TICALCS_PKG_ERRORS
+
+You must install libticalcs2, libticables2, libtifiles2, and libticonv
+(including the -dev/-devel packages if appropriate.)  These libraries
+are available from <http://lpg.ticalc.org/prj_tilp/>.
+
+If you have installed the libraries in a non-standard location (or if
+you're cross-compiling), you will need to add the location of
+ticalcs2.pc to your PKG_CONFIG_PATH environment variable, or set the
+TICALCS_CFLAGS and TICALCS_LIBS environment variables by hand." "$LINENO" 5
+fi
+
+# Tools used for building the Windows installer
+
+if test "x$gtk_target" = "xwin32" ; then
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$STRIP"; then
+  ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+  ac_ct_STRIP=$STRIP
+  # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_STRIP"; then
+  ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_STRIP="strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_STRIP" = x; then
+    STRIP=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    STRIP=$ac_ct_STRIP
+  fi
+else
+  STRIP="$ac_cv_prog_STRIP"
+fi
+
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args.
+set dummy ${ac_tool_prefix}objdump; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OBJDUMP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$OBJDUMP"; then
+  ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+OBJDUMP=$ac_cv_prog_OBJDUMP
+if test -n "$OBJDUMP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5
+$as_echo "$OBJDUMP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OBJDUMP"; then
+  ac_ct_OBJDUMP=$OBJDUMP
+  # Extract the first word of "objdump", so it can be a program name with args.
+set dummy objdump; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_OBJDUMP"; then
+  ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_OBJDUMP="objdump"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP
+if test -n "$ac_ct_OBJDUMP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5
+$as_echo "$ac_ct_OBJDUMP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_OBJDUMP" = x; then
+    OBJDUMP="objdump"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    OBJDUMP=$ac_ct_OBJDUMP
+  fi
+else
+  OBJDUMP="$ac_cv_prog_OBJDUMP"
+fi
+
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}windres", so it can be a program name with args.
+set dummy ${ac_tool_prefix}windres; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_WINDRES+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$WINDRES"; then
+  ac_cv_prog_WINDRES="$WINDRES" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_WINDRES="${ac_tool_prefix}windres"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+WINDRES=$ac_cv_prog_WINDRES
+if test -n "$WINDRES"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $WINDRES" >&5
+$as_echo "$WINDRES" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_WINDRES"; then
+  ac_ct_WINDRES=$WINDRES
+  # Extract the first word of "windres", so it can be a program name with args.
+set dummy windres; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_WINDRES+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_WINDRES"; then
+  ac_cv_prog_ac_ct_WINDRES="$ac_ct_WINDRES" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_WINDRES="windres"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_WINDRES=$ac_cv_prog_ac_ct_WINDRES
+if test -n "$ac_ct_WINDRES"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_WINDRES" >&5
+$as_echo "$ac_ct_WINDRES" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_WINDRES" = x; then
+    WINDRES="windres"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    WINDRES=$ac_ct_WINDRES
+  fi
+else
+  WINDRES="$ac_cv_prog_WINDRES"
+fi
+
+  # Extract the first word of "makensis", so it can be a program name with args.
+set dummy makensis; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_MAKENSIS+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$MAKENSIS"; then
+  ac_cv_prog_MAKENSIS="$MAKENSIS" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_MAKENSIS="makensis"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+MAKENSIS=$ac_cv_prog_MAKENSIS
+if test -n "$MAKENSIS"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAKENSIS" >&5
+$as_echo "$MAKENSIS" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5
+$as_echo_n "checking whether ln -s works... " >&6; }
+LN_S=$as_ln_s
+if test "$LN_S" = "ln -s"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5
+$as_echo "no, using $LN_S" >&6; }
+fi
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to find GTK+ runtime libraries" >&5
+$as_echo_n "checking where to find GTK+ runtime libraries... " >&6; }
+  if test "x$GTK_BINDIR" = "x" ; then
+    prefix=`$PKG_CONFIG --variable=exec_prefix gtk+-2.0`
+    test "x$prefix" != "x" && GTK_BINDIR="$prefix/bin"
+  fi
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GTK_BINDIR" >&5
+$as_echo "$GTK_BINDIR" >&6; }
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to find ticalcs2 runtime libraries" >&5
+$as_echo_n "checking where to find ticalcs2 runtime libraries... " >&6; }
+  if test "x$TICALCS_BINDIR" = "x" ; then
+    prefix=`$PKG_CONFIG --variable=exec_prefix ticalcs2`
+    test "x$prefix" != "x" && TICALCS_BINDIR="$prefix/bin"
+  fi
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TICALCS_BINDIR" >&5
+$as_echo "$TICALCS_BINDIR" >&6; }
+
+  if test "x$DLLPATH" = "x" ; then
+    DLLPATH='${GTK_BINDIR}'$PATH_SEPARATOR'${TICALCS_BINDIR}'
+  fi
+
+
+
+fi
+
+# Checks for header files
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$GREP"; then
+  ac_path_GREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in grep ggrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+      { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+  # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'GREP' >> "conftest.nl"
+    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_GREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_GREP="$ac_path_GREP"
+      ac_path_GREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_GREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_GREP"; then
+    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+   then ac_cv_path_EGREP="$GREP -E"
+   else
+     if test -z "$EGREP"; then
+  ac_path_EGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in egrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+      { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+  # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'EGREP' >> "conftest.nl"
+    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_EGREP="$ac_path_EGREP"
+      ac_path_EGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_EGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_EGREP"; then
+    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_EGREP=$EGREP
+fi
+
+   fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_header_stdc=yes
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "free" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+  if test "$cross_compiling" = yes; then :
+  :
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+		   (('a' <= (c) && (c) <= 'i') \
+		     || ('j' <= (c) && (c) <= 'r') \
+		     || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+  int i;
+  for (i = 0; i < 256; i++)
+    if (XOR (islower (i), ISLOWER (i))
+	|| toupper (i) != TOUPPER (i))
+      return 2;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+
+# Checks for system and compiler characteristics
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+		  inttypes.h stdint.h unistd.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5
+$as_echo_n "checking whether byte ordering is bigendian... " >&6; }
+if ${ac_cv_c_bigendian+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_c_bigendian=unknown
+    # See if we're dealing with a universal compiler.
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifndef __APPLE_CC__
+	       not a universal capable compiler
+	     #endif
+	     typedef int dummy;
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+	# Check for potential -arch flags.  It is not universal unless
+	# there are at least two -arch flags with different values.
+	ac_arch=
+	ac_prev=
+	for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do
+	 if test -n "$ac_prev"; then
+	   case $ac_word in
+	     i?86 | x86_64 | ppc | ppc64)
+	       if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then
+		 ac_arch=$ac_word
+	       else
+		 ac_cv_c_bigendian=universal
+		 break
+	       fi
+	       ;;
+	   esac
+	   ac_prev=
+	 elif test "x$ac_word" = "x-arch"; then
+	   ac_prev=arch
+	 fi
+       done
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    if test $ac_cv_c_bigendian = unknown; then
+      # See if sys/param.h defines the BYTE_ORDER macro.
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <sys/types.h>
+	     #include <sys/param.h>
+
+int
+main ()
+{
+#if ! (defined BYTE_ORDER && defined BIG_ENDIAN \
+		     && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \
+		     && LITTLE_ENDIAN)
+	      bogus endian macros
+	     #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  # It does; now see whether it defined to BIG_ENDIAN or not.
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <sys/types.h>
+		#include <sys/param.h>
+
+int
+main ()
+{
+#if BYTE_ORDER != BIG_ENDIAN
+		 not big endian
+		#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_c_bigendian=yes
+else
+  ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    fi
+    if test $ac_cv_c_bigendian = unknown; then
+      # See if <limits.h> defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris).
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <limits.h>
+
+int
+main ()
+{
+#if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN)
+	      bogus endian macros
+	     #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  # It does; now see whether it defined to _BIG_ENDIAN or not.
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <limits.h>
+
+int
+main ()
+{
+#ifndef _BIG_ENDIAN
+		 not big endian
+		#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_c_bigendian=yes
+else
+  ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    fi
+    if test $ac_cv_c_bigendian = unknown; then
+      # Compile a test program.
+      if test "$cross_compiling" = yes; then :
+  # Try to guess by grepping values from an object file.
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+short int ascii_mm[] =
+		  { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 };
+		short int ascii_ii[] =
+		  { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 };
+		int use_ascii (int i) {
+		  return ascii_mm[i] + ascii_ii[i];
+		}
+		short int ebcdic_ii[] =
+		  { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
+		short int ebcdic_mm[] =
+		  { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 };
+		int use_ebcdic (int i) {
+		  return ebcdic_mm[i] + ebcdic_ii[i];
+		}
+		extern int foo;
+
+int
+main ()
+{
+return use_ascii (foo) == use_ebcdic (foo);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then
+	      ac_cv_c_bigendian=yes
+	    fi
+	    if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then
+	      if test "$ac_cv_c_bigendian" = unknown; then
+		ac_cv_c_bigendian=no
+	      else
+		# finding both strings is unlikely to happen, but who knows?
+		ac_cv_c_bigendian=unknown
+	      fi
+	    fi
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+
+	     /* Are we little or big endian?  From Harbison&Steele.  */
+	     union
+	     {
+	       long int l;
+	       char c[sizeof (long int)];
+	     } u;
+	     u.l = 1;
+	     return u.c[sizeof (long int) - 1] == 1;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+  ac_cv_c_bigendian=no
+else
+  ac_cv_c_bigendian=yes
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+    fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5
+$as_echo "$ac_cv_c_bigendian" >&6; }
+ case $ac_cv_c_bigendian in #(
+   yes)
+     $as_echo "#define WORDS_BIGENDIAN 1" >>confdefs.h
+;; #(
+   no)
+      ;; #(
+   universal)
+
+$as_echo "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h
+
+     ;; #(
+   *)
+     as_fn_error $? "unknown endianness
+ presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;;
+ esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for inline" >&5
+$as_echo_n "checking for inline... " >&6; }
+if ${ac_cv_c_inline+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_c_inline=no
+for ac_kw in inline __inline__ __inline; do
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifndef __cplusplus
+typedef int foo_t;
+static $ac_kw foo_t static_foo () {return 0; }
+$ac_kw foo_t foo () {return 0; }
+#endif
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_c_inline=$ac_kw
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  test "$ac_cv_c_inline" != no && break
+done
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_inline" >&5
+$as_echo "$ac_cv_c_inline" >&6; }
+
+case $ac_cv_c_inline in
+  inline | yes) ;;
+  *)
+    case $ac_cv_c_inline in
+      no) ac_val=;;
+      *) ac_val=$ac_cv_c_inline;;
+    esac
+    cat >>confdefs.h <<_ACEOF
+#ifndef __cplusplus
+#define inline $ac_val
+#endif
+_ACEOF
+    ;;
+esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C/C++ restrict keyword" >&5
+$as_echo_n "checking for C/C++ restrict keyword... " >&6; }
+if ${ac_cv_c_restrict+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_c_restrict=no
+   # The order here caters to the fact that C++ does not require restrict.
+   for ac_kw in __restrict __restrict__ _Restrict restrict; do
+     cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+typedef int * int_ptr;
+	int foo (int_ptr $ac_kw ip) {
+	return ip[0];
+       }
+int
+main ()
+{
+int s[1];
+	int * $ac_kw t = s;
+	t[0] = 0;
+	return foo(t)
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_c_restrict=$ac_kw
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+     test "$ac_cv_c_restrict" != no && break
+   done
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_restrict" >&5
+$as_echo "$ac_cv_c_restrict" >&6; }
+
+ case $ac_cv_c_restrict in
+   restrict) ;;
+   no) $as_echo "#define restrict /**/" >>confdefs.h
+ ;;
+   *)  cat >>confdefs.h <<_ACEOF
+#define restrict $ac_cv_c_restrict
+_ACEOF
+ ;;
+ esac
+
+
+  ac_fn_c_check_type "$LINENO" "uintptr_t" "ac_cv_type_uintptr_t" "$ac_includes_default"
+if test "x$ac_cv_type_uintptr_t" = xyes; then :
+
+$as_echo "#define HAVE_UINTPTR_T 1" >>confdefs.h
+
+else
+  for ac_type in 'unsigned int' 'unsigned long int' \
+	'unsigned long long int'; do
+       cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+static int test_array [1 - 2 * !(sizeof (void *) <= sizeof ($ac_type))];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+cat >>confdefs.h <<_ACEOF
+#define uintptr_t $ac_type
+_ACEOF
+
+	  ac_type=
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+       test -z "$ac_type" && break
+     done
+fi
+
+
+
+# Output
+
+ac_config_headers="$ac_config_headers config.h"
+
+ac_config_files="$ac_config_files Makefile emu/Makefile db/Makefile data/Makefile gui/Makefile gui/tilem2.rc installer/win32/Makefile installer/win32/installer.nsi"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes: double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \.
+      sed -n \
+	"s/'/'\\\\''/g;
+	  s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    if test "x$cache_file" != "x/dev/null"; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+      if test ! -f "$cache_file" || test -h "$cache_file"; then
+	cat confcache >"$cache_file"
+      else
+        case $cache_file in #(
+        */* | ?:*)
+	  mv -f confcache "$cache_file"$$ &&
+	  mv -f "$cache_file"$$ "$cache_file" ;; #(
+        *)
+	  mv -f confcache "$cache_file" ;;
+	esac
+      fi
+    fi
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+	expr "X$arg" : "X\\(.*\\)$as_nl";
+	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+	 X"$0" : 'X\(//\)$' \| \
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='	';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -p'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -p'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -p'
+  fi
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_dir" : 'X\(//\)[^/]' \| \
+	 X"$as_dir" : 'X\(//\)$' \| \
+	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+	test -d "$1/.";
+      else
+	case $1 in #(
+	-*)set "./$1";;
+	esac;
+	case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #((
+	???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by TilEm $as_me 2.0, which was
+generated by GNU Autoconf 2.68.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+case $ac_config_headers in *"
+"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
+esac
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration.  Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+      --config     print configuration, then exit
+  -q, --quiet, --silent
+                   do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+      --file=FILE[:TEMPLATE]
+                   instantiate the configuration file FILE
+      --header=FILE[:TEMPLATE]
+                   instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Report bugs to <tilem-devel@lists.sourceforge.net>.
+TilEm home page: <http://tilem.sourceforge.net/>."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+TilEm config.status 2.0
+configured by $0, generated by GNU Autoconf 2.68,
+  with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2010 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=?*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  --*=)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    $as_echo "$ac_cs_version"; exit ;;
+  --config | --confi | --conf | --con | --co | --c )
+    $as_echo "$ac_cs_config"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    '') as_fn_error $? "missing file argument" ;;
+    esac
+    as_fn_append CONFIG_FILES " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --header | --heade | --head | --hea )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    as_fn_append CONFIG_HEADERS " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --he | --h)
+    # Conflict between --help and --header
+    as_fn_error $? "ambiguous option: \`$1'
+Try \`$0 --help' for more information.";;
+  --help | --hel | -h )
+    $as_echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+  *) as_fn_append ac_config_targets " $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+  set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  shift
+  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  CONFIG_SHELL='$SHELL'
+  export CONFIG_SHELL
+  exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
+    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+    "emu/Makefile") CONFIG_FILES="$CONFIG_FILES emu/Makefile" ;;
+    "db/Makefile") CONFIG_FILES="$CONFIG_FILES db/Makefile" ;;
+    "data/Makefile") CONFIG_FILES="$CONFIG_FILES data/Makefile" ;;
+    "gui/Makefile") CONFIG_FILES="$CONFIG_FILES gui/Makefile" ;;
+    "gui/tilem2.rc") CONFIG_FILES="$CONFIG_FILES gui/tilem2.rc" ;;
+    "installer/win32/Makefile") CONFIG_FILES="$CONFIG_FILES installer/win32/Makefile" ;;
+    "installer/win32/installer.nsi") CONFIG_FILES="$CONFIG_FILES installer/win32/installer.nsi" ;;
+
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+  test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp= ac_tmp=
+  trap 'exit_status=$?
+  : "${ac_tmp:=$tmp}"
+  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+  trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+  eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+  ac_cs_awk_cr='\\r'
+else
+  ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+  echo "cat >conf$$subs.awk <<_ACEOF" &&
+  echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+  echo "_ACEOF"
+} >conf$$subs.sh ||
+  as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  . ./conf$$subs.sh ||
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+  ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+  if test $ac_delim_n = $ac_delim_num; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+  N
+  s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+  for (key in S) S_is_set[key] = 1
+  FS = ""
+
+}
+{
+  line = $ 0
+  nfields = split(line, field, "@")
+  substed = 0
+  len = length(field[1])
+  for (i = 2; i < nfields; i++) {
+    key = field[i]
+    keylen = length(key)
+    if (S_is_set[key]) {
+      value = S[key]
+      line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+      len += length(value) + length(field[++i])
+      substed = 1
+    } else
+      len += 1 + keylen
+  }
+
+  print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+  sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+  cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+  || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[	 ]*VPATH[	 ]*=[	 ]*/{
+h
+s///
+s/^/:/
+s/[	 ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[	 ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[	 ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+# Set up the scripts for CONFIG_HEADERS section.
+# No need to generate them if there are no CONFIG_HEADERS.
+# This happens for instance with `./config.status Makefile'.
+if test -n "$CONFIG_HEADERS"; then
+cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
+BEGIN {
+_ACEOF
+
+# Transform confdefs.h into an awk script `defines.awk', embedded as
+# here-document in config.status, that substitutes the proper values into
+# config.h.in to produce config.h.
+
+# Create a delimiter string that does not exist in confdefs.h, to ease
+# handling of long lines.
+ac_delim='%!_!# '
+for ac_last_try in false false :; do
+  ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
+  if test -z "$ac_tt"; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+
+# For the awk script, D is an array of macro values keyed by name,
+# likewise P contains macro parameters if any.  Preserve backslash
+# newline sequences.
+
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+sed -n '
+s/.\{148\}/&'"$ac_delim"'/g
+t rset
+:rset
+s/^[	 ]*#[	 ]*define[	 ][	 ]*/ /
+t def
+d
+:def
+s/\\$//
+t bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[	 ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3"/p
+s/^ \('"$ac_word_re"'\)[	 ]*\(.*\)/D["\1"]=" \2"/p
+d
+:bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[	 ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3\\\\\\n"\\/p
+t cont
+s/^ \('"$ac_word_re"'\)[	 ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
+t cont
+d
+:cont
+n
+s/.\{148\}/&'"$ac_delim"'/g
+t clear
+:clear
+s/\\$//
+t bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/"/p
+d
+:bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
+b cont
+' <confdefs.h | sed '
+s/'"$ac_delim"'/"\\\
+"/g' >>$CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  for (key in D) D_is_set[key] = 1
+  FS = ""
+}
+/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
+  line = \$ 0
+  split(line, arg, " ")
+  if (arg[1] == "#") {
+    defundef = arg[2]
+    mac1 = arg[3]
+  } else {
+    defundef = substr(arg[1], 2)
+    mac1 = arg[2]
+  }
+  split(mac1, mac2, "(") #)
+  macro = mac2[1]
+  prefix = substr(line, 1, index(line, defundef) - 1)
+  if (D_is_set[macro]) {
+    # Preserve the white space surrounding the "#".
+    print prefix "define", macro P[macro] D[macro]
+    next
+  } else {
+    # Replace #undef with comments.  This is necessary, for example,
+    # in the case of _POSIX_SOURCE, which is predefined and required
+    # on some systems where configure will not decide to define it.
+    if (defundef == "undef") {
+      print "/*", prefix defundef, macro, "*/"
+      next
+    }
+  }
+}
+{ print }
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+  as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
+fi # test -n "$CONFIG_HEADERS"
+
+
+eval set X "  :F $CONFIG_FILES  :H $CONFIG_HEADERS    "
+shift
+for ac_tag
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$ac_tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+	 # (if the path is not absolute).  The absolute path cannot be DOS-style,
+	 # because $ac_f cannot contain `:'.
+	 test -f "$ac_f" ||
+	   case $ac_f in
+	   [\\/$]*) false;;
+	   *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+	   esac ||
+	   as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+      esac
+      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      as_fn_append ac_file_inputs " '$ac_f'"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input='Generated from '`
+	  $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+	`' by configure.'
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+    fi
+    # Neutralize special characters interpreted by sed in replacement strings.
+    case $configure_input in #(
+    *\&* | *\|* | *\\* )
+       ac_sed_conf_input=`$as_echo "$configure_input" |
+       sed 's/[\\\\&|]/\\\\&/g'`;; #(
+    *) ac_sed_conf_input=$configure_input;;
+    esac
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$ac_tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$ac_file" : 'X\(//\)[^/]' \| \
+	 X"$ac_file" : 'X\(//\)$' \| \
+	 X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+  as_dir="$ac_dir"; as_fn_mkdir_p
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+  case $INSTALL in
+  [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+  *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+  esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+  s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[	 ]*datarootdir[	 ]*:*=/p' \
+      "$ac_tmp/out"`; test -z "$ac_out"; } &&
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&2;}
+
+  rm -f "$ac_tmp/stdin"
+  case $ac_file in
+  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+  esac \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+  :H)
+  #
+  # CONFIG_HEADER
+  #
+  if test x"$ac_file" != x-; then
+    {
+      $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
+    } >"$ac_tmp/config.h" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+$as_echo "$as_me: $ac_file is unchanged" >&6;}
+    else
+      rm -f "$ac_file"
+      mv "$ac_tmp/config.h" "$ac_file" \
+	|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    fi
+  else
+    $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
+      || as_fn_error $? "could not create -" "$LINENO" 5
+  fi
+ ;;
+
+
+  esac
+
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+  as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
diff --git a/tool/tilem-src/configure.ac b/tool/tilem-src/configure.ac
new file mode 100644
index 0000000..88e2306
--- /dev/null
+++ b/tool/tilem-src/configure.ac
@@ -0,0 +1,172 @@
+AC_PREREQ(2.67)
+AC_INIT([TilEm], [2.0], [tilem-devel@lists.sourceforge.net],
+  [tilem], [http://tilem.sourceforge.net/])
+AC_CONFIG_SRCDIR([emu/tilem.h])
+
+# Checks for programs
+
+AC_PROG_CC
+AC_PROG_CPP
+AC_ARG_VAR(OPT_CFLAGS,
+  [Additional C compiler flags used for optimizing critical areas of
+  the code (default: -O3 if using GCC)])
+if test "x$GCC" = "xyes" ; then
+  CFLAGS="$CFLAGS -W -Wall -Wwrite-strings"
+  if test "x$OPT_CFLAGS" = "x" ; then
+    OPT_CFLAGS="-O3"
+  fi
+fi
+
+AC_CHECK_TOOL(AR, [ar], [false])
+AC_ARG_VAR(AR, [Static library archiver])
+AC_ARG_VAR(AR_FLAGS, [Flags to pass to ar to build a static library])
+if test "x$AR_FLAGS" = "x" ; then
+  AR_FLAGS=cru
+fi
+
+AC_PROG_RANLIB
+AC_ARG_VAR(RANLIB, [Program to make a static library linkable])
+
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+AC_CHECK_PROG([UPDATE_DESKTOP_DATABASE],
+              [update-desktop-database], [update-desktop-database], [:])
+AC_CHECK_PROG([UPDATE_MIME_DATABASE],
+              [update-mime-database], [update-mime-database], [:])
+
+# Checks for libraries
+
+m4_define(with_flags, [
+  save_cflags="$CFLAGS"
+  save_libs="$LIBS"
+  CFLAGS="$CFLAGS $$1_CFLAGS"
+  LIBS="$LIBS $$1_LIBS"
+  $2
+  CFLAGS="$save_cflags"
+  LIBS="$save_libs"
+])
+
+# GLib and GTK+
+
+PKG_CHECK_MODULES(GTK, gtk+-2.0 >= 2.6.0
+                       glib-2.0 >= 2.12.0
+                       gthread-2.0)
+
+AC_ARG_ENABLE([gtk-deprecated],
+  AS_HELP_STRING([--disable-gtk-deprecated], [Disable deprecated GTK+ API]),
+  [ enable_gtk_deprecated=$enableval ], [ enable_gtk_deprecated=yes ])
+if test "x$enable_gtk_deprecated" = "xno" ; then
+  GTK_CFLAGS="$GTK_CFLAGS -DG_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED -DGDK_DISABLE_DEPRECATED -DGSEAL_ENABLE"
+fi
+
+# If using the native Windows version of GTK+, be sure to use
+# -mms-bitfields for all compilation.  Also, use -mwindows for linking
+# GUI programs.
+
+# (If not using pkg-config, you're on your own)
+
+if test "x$PKG_CONFIG" != "x" ; then
+  gtk_target=`$PKG_CONFIG --variable=target gtk+-2.0`
+fi
+
+if test "x$gtk_target" = "xwin32" && test "x$GCC" = "xyes" ; then
+  CFLAGS="$CFLAGS -mms-bitfields"
+  GUI_LDFLAGS="-mwindows"
+  LIBS="-lcomdlg32 -lshell32 -lole32 $LIBS"
+  gui_extra_objects="tilem2rc.o"
+else
+  GUI_LDFLAGS=""
+  gui_extra_objects=""
+fi
+
+AC_SUBST(GUI_LDFLAGS)
+AC_SUBST(gui_extra_objects)
+
+with_flags(GTK,
+  [ AC_CHECK_FUNC(gtk_init, [ have_gtk=yes ], [ have_gtk=no ]) ])
+if test "x$have_gtk" != "xyes" ; then
+  AC_MSG_ERROR([GTK+ 2.x libraries not found or not usable.
+You must install a recent version of GTK+ 2.x, including the
+-dev/-devel packages if appropriate.])
+fi
+
+# Libticalcs2 and related libraries
+
+PKG_CHECK_MODULES(TICALCS, ticalcs2 ticables2 tifiles2 ticonv,
+                  [ have_ticalcs=maybe ], [ have_ticalcs=no ])
+
+if test "x$have_ticalcs" = "xmaybe" ; then
+  with_flags(TICALCS,
+    [ AC_CHECK_FUNC(ticalcs_library_init, [ have_ticalcs=yes ], [ have_ticalcs=no ]) ])
+fi
+
+if test "x$have_ticalcs" != "xyes" ; then
+  AC_MSG_ERROR([libticalcs2 not found or not usable.
+
+$TICALCS_PKG_ERRORS
+
+You must install libticalcs2, libticables2, libtifiles2, and libticonv
+(including the -dev/-devel packages if appropriate.)  These libraries
+are available from <http://lpg.ticalc.org/prj_tilp/>.
+
+If you have installed the libraries in a non-standard location (or if
+you're cross-compiling), you will need to add the location of
+ticalcs2.pc to your PKG_CONFIG_PATH environment variable, or set the
+TICALCS_CFLAGS and TICALCS_LIBS environment variables by hand.])
+fi
+
+# Tools used for building the Windows installer
+
+if test "x$gtk_target" = "xwin32" ; then
+  AC_CHECK_TOOL([STRIP], [strip], [:])
+  AC_CHECK_TOOL([OBJDUMP], [objdump], [objdump])
+  AC_CHECK_TOOL([WINDRES], [windres], [windres])
+  AC_CHECK_PROG([MAKENSIS], [makensis], [makensis])
+  AC_PROG_LN_S
+
+  AC_MSG_CHECKING([where to find GTK+ runtime libraries])
+  if test "x$GTK_BINDIR" = "x" ; then
+    prefix=`$PKG_CONFIG --variable=exec_prefix gtk+-2.0`
+    test "x$prefix" != "x" && GTK_BINDIR="$prefix/bin"
+  fi
+  AC_MSG_RESULT([$GTK_BINDIR])
+
+  AC_MSG_CHECKING([where to find ticalcs2 runtime libraries])
+  if test "x$TICALCS_BINDIR" = "x" ; then
+    prefix=`$PKG_CONFIG --variable=exec_prefix ticalcs2`
+    test "x$prefix" != "x" && TICALCS_BINDIR="$prefix/bin"
+  fi
+  AC_MSG_RESULT([$TICALCS_BINDIR])
+
+  if test "x$DLLPATH" = "x" ; then
+    DLLPATH='${GTK_BINDIR}'$PATH_SEPARATOR'${TICALCS_BINDIR}'
+  fi
+  AC_SUBST(GTK_BINDIR)
+  AC_SUBST(TICALCS_BINDIR)
+  AC_SUBST(DLLPATH)
+fi
+
+# Checks for header files
+
+AC_HEADER_STDC
+
+# Checks for system and compiler characteristics
+
+AC_C_BIGENDIAN
+AC_C_INLINE
+AC_C_RESTRICT
+AC_TYPE_UINTPTR_T
+
+# Output
+
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_FILES([Makefile
+                 emu/Makefile
+                 db/Makefile
+                 data/Makefile
+                 gui/Makefile
+                 gui/tilem2.rc
+                 installer/win32/Makefile
+                 installer/win32/installer.nsi])
+AC_OUTPUT
diff --git a/tool/tilem-src/data/Makefile.in b/tool/tilem-src/data/Makefile.in
new file mode 100644
index 0000000..c41e175
--- /dev/null
+++ b/tool/tilem-src/data/Makefile.in
@@ -0,0 +1,114 @@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+bindir = @bindir@
+datadir = @datadir@
+pkgdatadir = @datadir@/tilem2
+mandir = @mandir@
+icondir = @datadir@/icons
+applicationsdir = @datadir@/applications
+mimedir = @datadir@/mime
+
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+@SET_MAKE@
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+SHELL = @SHELL@
+UPDATE_DESKTOP_DATABASE = @UPDATE_DESKTOP_DATABASE@
+UPDATE_MIME_DATABASE = @UPDATE_MIME_DATABASE@
+
+# Custom action and status icons go in these directories.  These icons
+# are only used by TilEm, so they are installed in DATADIR/tilem2/icons.
+pkg_icondirs = hicolor/16x16/actions \
+	       hicolor/16x16/status \
+	       hicolor/24x24/actions
+
+# Application icons go in these directories; these icons will be
+# installed in DATADIR/icons so they are visible to other programs
+# (e.g. desktop application menus.)
+shared_icondirs = hicolor/16x16/apps \
+		  hicolor/22x22/apps \
+		  hicolor/24x24/apps \
+		  hicolor/32x32/apps \
+		  hicolor/48x48/apps
+
+all:
+	@echo 'Nothing to do'
+
+install:
+	$(INSTALL) -d -m 755 $(DESTDIR)$(pkgdatadir)
+	$(INSTALL_DATA) $(srcdir)/keybindings.ini $(DESTDIR)$(pkgdatadir)
+	$(INSTALL) -d -m 755 $(DESTDIR)$(pkgdatadir)/symbols
+	set -e ; for i in $(srcdir)/symbols/*.sym ; do \
+	 $(INSTALL_DATA) $$i $(DESTDIR)$(pkgdatadir)/symbols ; \
+	done
+	$(INSTALL) -d -m 755 $(DESTDIR)$(pkgdatadir)/skins
+	set -e ; for i in $(srcdir)/skins/*.skn ; do \
+	 $(INSTALL_DATA) $$i $(DESTDIR)$(pkgdatadir)/skins ; \
+	done
+	set -e ; for i in $(pkg_icondirs) ; do \
+	 $(INSTALL) -d -m 755 $(DESTDIR)$(pkgdatadir)/icons/$$i ; \
+	 for j in $(srcdir)/icons/$$i/*.png ; do \
+	  $(INSTALL_DATA) $$j $(DESTDIR)$(pkgdatadir)/icons/$$i ; \
+	 done ; \
+	done
+	set -e ; for i in $(shared_icondirs) ; do \
+	 $(INSTALL) -d -m 755 $(DESTDIR)$(icondir)/$$i ; \
+	 for j in $(srcdir)/icons/$$i/*.png ; do \
+	  $(INSTALL_DATA) $$j $(DESTDIR)$(icondir)/$$i ; \
+	 done ; \
+	done
+	$(INSTALL_DATA) $(srcdir)/icons/hicolor/index.theme $(DESTDIR)$(pkgdatadir)/icons/hicolor
+	$(INSTALL) -d -m 755 $(DESTDIR)$(applicationsdir)
+	$(INSTALL_DATA) $(srcdir)/desktop/tilem2.desktop $(DESTDIR)$(applicationsdir)
+	$(INSTALL) -d -m 755 $(DESTDIR)$(mimedir)/packages
+	$(INSTALL_DATA) $(srcdir)/desktop/tilem2.xml $(DESTDIR)$(mimedir)/packages
+	[ -n "$(DESTDIR)" ] || $(UPDATE_DESKTOP_DATABASE) $(applicationsdir)
+	[ -n "$(DESTDIR)" ] || $(UPDATE_MIME_DATABASE) $(mimedir)
+
+uninstall:
+	rm -f $(DESTDIR)$(pkgdatadir)/keybindings.ini
+	set -e ; for i in $(srcdir)/symbols/*.sym ; do \
+	 rm -f $(DESTDIR)$(pkgdatadir)/symbols/`basename $$i` ; \
+	done
+	set -e ; for i in $(srcdir)/skins/*.skn ; do \
+	 rm -f $(DESTDIR)$(pkgdatadir)/skins/`basename $$i` ; \
+	done
+	set -e ; for i in $(pkg_icondirs) ; do \
+	 for j in $(srcdir)/icons/$$i/*.png ; do \
+	  rm -f $(DESTDIR)$(pkgdatadir)/icons/$$i/`basename $$j` ; \
+	 done ; \
+	done
+	set -e ; for i in $(shared_icondirs) ; do \
+	 for j in $(srcdir)/icons/$$i/*.png ; do \
+	  rm -f $(DESTDIR)$(icondir)/$$i/`basename $$j` ; \
+	 done ; \
+	done
+	-for i in $(pkg_icondirs) ; do \
+	 rmdir $(DESTDIR)$(pkgdatadir)/icons/$$i ; \
+	done
+	-rmdir $(DESTDIR)$(pkgdatadir)/icons/hicolor/16x16
+	-rmdir $(DESTDIR)$(pkgdatadir)/icons/hicolor/24x24
+	rm -f $(DESTDIR)$(pkgdatadir)/icons/hicolor/index.theme
+	-rmdir $(DESTDIR)$(pkgdatadir)/icons/hicolor
+	-rmdir $(DESTDIR)$(pkgdatadir)/icons
+	-rmdir $(DESTDIR)$(pkgdatadir)/symbols
+	-rmdir $(DESTDIR)$(pkgdatadir)/skins
+	-rmdir $(DESTDIR)$(pkgdatadir)
+	rm -f $(DESTDIR)$(applicationsdir)/tilem2.desktop
+	rm -f $(DESTDIR)$(mimedir)/packages/tilem2.xml
+	[ -n "$(DESTDIR)" ] || $(UPDATE_DESKTOP_DATABASE) $(applicationsdir)
+	[ -n "$(DESTDIR)" ] || $(UPDATE_MIME_DATABASE) $(mimedir)
+
+Makefile: Makefile.in $(top_builddir)/config.status
+	cd $(top_builddir) && $(SHELL) ./config.status
+
+$(top_builddir)/config.status: $(top_srcdir)/configure
+	cd $(top_builddir) && $(SHELL) ./config.status --recheck
+
+.PRECIOUS: Makefile $(top_builddir)/config.status
+.PHONY: all install uninstall
diff --git a/tool/tilem-src/data/desktop/tilem2.desktop b/tool/tilem-src/data/desktop/tilem2.desktop
new file mode 100644
index 0000000..8118c97
--- /dev/null
+++ b/tool/tilem-src/data/desktop/tilem2.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=TilEm
+Comment=Graphing calculator emulator
+Exec=tilem2 %F
+Icon=tilem
+MimeType=application/x-tigroup;application/x-ti73-variables;application/x-ti73-program;application/x-ti73-backup;application/x-ti73-app;application/x-ti73-os;application/x-ti81-program;application/x-ti82-variables;application/x-ti82-program;application/x-ti82-backup;application/x-ti83-variables;application/x-ti83-program;application/x-ti83-backup;application/x-ti83plus-variables;application/x-ti83plus-program;application/x-ti83plus-app;application/x-ti83plus-os;application/x-ti85-variables;application/x-ti85-program;application/x-ti85-backup;application/x-ti86-variables;application/x-ti86-program;application/x-ti86-backup;
+StartupNotify=true
+Terminal=false
+Type=Application
+Categories=Education;Science;Math;Emulator;
diff --git a/tool/tilem-src/data/desktop/tilem2.xml b/tool/tilem-src/data/desktop/tilem2.xml
new file mode 100644
index 0000000..7dae83c
--- /dev/null
+++ b/tool/tilem-src/data/desktop/tilem2.xml
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+  <mime-type type="application/x-tigroup">
+    <sub-class-of type="application/zip" />
+    <comment>TI calculator group file</comment>
+    <glob pattern="*.tig" />
+  </mime-type>
+
+  <mime-type type="application/x-ti73-variables">
+    <comment>TI-73 variable</comment>
+    <glob pattern="*.73g" />
+    <glob pattern="*.73n" />
+    <glob pattern="*.73l" />
+    <glob pattern="*.73m" />
+    <glob pattern="*.73y" />
+    <glob pattern="*.73e" />
+    <glob pattern="*.73s" />
+    <glob pattern="*.73i" />
+    <glob pattern="*.73d" />
+    <glob pattern="*.73w" />
+    <glob pattern="*.73z" />
+    <glob pattern="*.73t" />
+    <glob pattern="*.73v" />
+    <magic>
+      <match type="string" offset="0" value="**TI73**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x000b" />
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti73-program">
+    <sub-class-of type="application/x-ti73-variables" />
+    <comment>TI-73 program</comment>
+    <glob pattern="*.73p" />
+    <magic>
+      <match type="string" offset="0" value="**TI73**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x000b">
+          <match type="byte" offset="59" value="0x05" />
+          <match type="byte" offset="59" value="0x06" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti73-backup">
+    <comment>TI-73 memory backup</comment>
+    <glob pattern="*.73b" />
+    <magic>
+      <match type="string" offset="0" value="**TI73**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x0009">
+          <match type="byte" offset="59" value="0x13" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti73-app">
+    <comment>TI-73 Flash application</comment>
+    <glob pattern="*.73k" />
+    <magic>
+      <match type="string" offset="0" value="**TIFL**">
+        <match type="big16" offset="48" value="0x7424" />
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti73-os">
+    <comment>TI-73 operating system</comment>
+    <glob pattern="*.73u" />
+    <magic>
+      <match type="string" offset="0" value="**TIFL**">
+        <match type="big16" offset="48" value="0x7423" />
+      </match>
+    </magic>
+  </mime-type>
+
+  <mime-type type="application/x-ti81-program">
+    <comment>TI-81 program</comment>
+    <glob pattern="*.prg" />
+    <magic>
+      <match type="string" offset="0" value="**TI81**\x00\x6e" />
+    </magic>
+  </mime-type>
+
+  <mime-type type="application/x-ti82-variables">
+    <comment>TI-82 variable</comment>
+    <glob pattern="*.82g" />
+    <glob pattern="*.82n" />
+    <glob pattern="*.82l" />
+    <glob pattern="*.82y" />
+    <glob pattern="*.82e" />
+    <glob pattern="*.82s" />
+    <glob pattern="*.82i" />
+    <glob pattern="*.82d" />
+    <glob pattern="*.82w" />
+    <glob pattern="*.82z" />
+    <glob pattern="*.82t" />
+    <magic>
+      <match type="string" offset="0" value="**TI82**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x000b" />
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti82-program">
+    <sub-class-of type="application/x-ti82-variables" />
+    <comment>TI-82 program</comment>
+    <glob pattern="*.82p" />
+    <magic>
+      <match type="string" offset="0" value="**TI82**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x000b">
+          <match type="byte" offset="59" value="0x05" />
+          <match type="byte" offset="59" value="0x06" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti82-backup">
+    <comment>TI-82 memory backup</comment>
+    <glob pattern="*.82b" />
+    <magic>
+      <match type="string" offset="0" value="**TI82**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x0009">
+          <match type="byte" offset="59" value="0x0f" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+
+  <mime-type type="application/x-ti83-variables">
+    <comment>TI-83 variable</comment>
+    <glob pattern="*.83g" />
+    <glob pattern="*.83n" />
+    <glob pattern="*.83l" />
+    <glob pattern="*.83m" />
+    <glob pattern="*.83y" />
+    <glob pattern="*.83e" />
+    <glob pattern="*.83s" />
+    <glob pattern="*.83i" />
+    <glob pattern="*.83d" />
+    <glob pattern="*.83c" />
+    <glob pattern="*.83w" />
+    <glob pattern="*.83z" />
+    <glob pattern="*.83t" />
+    <magic>
+      <match type="string" offset="0" value="**TI83**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x000b" />
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti83-program">
+    <sub-class-of type="application/x-ti83-variables" />
+    <comment>TI-83 program</comment>
+    <glob pattern="*.83p" />
+    <magic>
+      <match type="string" offset="0" value="**TI83**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x000b">
+          <match type="byte" offset="59" value="0x05" />
+          <match type="byte" offset="59" value="0x06" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti83-backup">
+    <comment>TI-83 memory backup</comment>
+    <glob pattern="*.83b" />
+    <magic>
+      <match type="string" offset="0" value="**TI83**\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x0009">
+          <match type="byte" offset="59" value="0x13" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+
+  <mime-type type="application/x-ti83plus-variables">
+    <comment>TI-83/84 Plus variable</comment>
+    <glob pattern="*.8xg" />
+    <glob pattern="*.8xn" />
+    <glob pattern="*.8xl" />
+    <glob pattern="*.8xm" />
+    <glob pattern="*.8xy" />
+    <glob pattern="*.8xe" />
+    <glob pattern="*.8xs" />
+    <glob pattern="*.8xi" />
+    <glob pattern="*.8xd" />
+    <glob pattern="*.8xc" />
+    <glob pattern="*.8xw" />
+    <glob pattern="*.8xz" />
+    <glob pattern="*.8xt" />
+    <glob pattern="*.8xv" />
+    <glob pattern="*.8xo" />
+    <glob pattern="*.8xgrp" />
+    <magic>
+      <match type="string" offset="0" value="**TI83F*\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x000d" />
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti83plus-program">
+    <sub-class-of type="application/x-ti83plus-variables" />
+    <comment>TI-83/84 Plus program</comment>
+    <glob pattern="*.8xp" />
+    <magic>
+      <match type="string" offset="0" value="**TI83F*\x1a\x0a\x00">
+        <match type="little16" offset="55" value="0x000d">
+          <match type="byte" offset="59" value="0x05" />
+          <match type="byte" offset="59" value="0x06" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti83plus-app">
+    <comment>TI-83/84 Plus Flash application</comment>
+    <glob pattern="*.8xk" />
+    <magic>
+      <match type="string" offset="0" value="**TIFL**">
+        <match type="big16" offset="48" value="0x7324" />
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti83plus-os">
+    <comment>TI-83/84 Plus operating system</comment>
+    <glob pattern="*.8xu" />
+    <magic>
+      <match type="string" offset="0" value="**TIFL**">
+        <match type="big16" offset="48" value="0x7323" />
+      </match>
+    </magic>
+  </mime-type>
+
+  <mime-type type="application/x-ti85-variables">
+    <comment>TI-85 variable</comment>
+    <glob pattern="*.85g" />
+    <glob pattern="*.85n" />
+    <glob pattern="*.85v" />
+    <glob pattern="*.85l" />
+    <glob pattern="*.85m" />
+    <glob pattern="*.85k" />
+    <glob pattern="*.85c" />
+    <glob pattern="*.85y" />
+    <glob pattern="*.85s" />
+    <glob pattern="*.85d" />
+    <glob pattern="*.85i" />
+    <glob pattern="*.85r" />
+    <glob pattern="*.85w" />
+    <glob pattern="*.85z" />
+    <magic>
+      <match type="string" offset="0" value="**TI85**\x1a\x0c\x00">
+        <match type="little16" offset="55" value="0x0004" mask="0xfffc" />
+        <match type="little16" offset="55" value="0x0008" mask="0xfff8" />
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti85-program">
+    <sub-class-of type="application/x-ti85-variables" />
+    <comment>TI-85 program</comment>
+    <glob pattern="*.85p" />
+    <magic>
+      <match type="string" offset="0" value="**TI85**\x1a\x0c\x00">
+        <match type="byte" offset="59" value="0x12">
+          <match type="little16" offset="55" value="0x0004" mask="0xfffc" />
+          <match type="little16" offset="55" value="0x0008" mask="0xfff8" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti85-backup">
+    <comment>TI-85 memory backup</comment>
+    <glob pattern="*.85b" />
+    <magic>
+      <match type="string" offset="0" value="**TI85**\x1a\x0c\x00">
+        <match type="little16" offset="55" value="0x0009">
+          <match type="byte" offset="59" value="0x1d" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+
+  <mime-type type="application/x-ti86-variables">
+    <comment>TI-86 variable</comment>
+    <glob pattern="*.86g" />
+    <glob pattern="*.86n" />
+    <glob pattern="*.86v" />
+    <glob pattern="*.86l" />
+    <glob pattern="*.86m" />
+    <glob pattern="*.86k" />
+    <glob pattern="*.86c" />
+    <glob pattern="*.86y" />
+    <glob pattern="*.86s" />
+    <glob pattern="*.86d" />
+    <glob pattern="*.86i" />
+    <glob pattern="*.86r" />
+    <glob pattern="*.86w" />
+    <glob pattern="*.86z" />
+    <magic>
+      <match type="string" offset="0" value="**TI86**\x1a\x0c\x00">
+        <match type="little16" offset="55" value="0x0004" mask="0xfffc" />
+        <match type="little16" offset="55" value="0x0008" mask="0xfff8" />
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti86-program">
+    <sub-class-of type="application/x-ti86-variables" />
+    <comment>TI-86 program</comment>
+    <glob pattern="*.86p" />
+    <magic>
+      <match type="string" offset="0" value="**TI86**\x1a\x0c\x00">
+        <match type="byte" offset="59" value="0x12">
+          <match type="little16" offset="55" value="0x0004" mask="0xfffc" />
+          <match type="little16" offset="55" value="0x0008" mask="0xfff8" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+  <mime-type type="application/x-ti86-backup">
+    <comment>TI-86 memory backup</comment>
+    <glob pattern="*.86b" />
+    <magic>
+      <match type="string" offset="0" value="**TI86**\x1a\x0c\x00">
+        <match type="little16" offset="55" value="0x0009">
+          <match type="byte" offset="59" value="0x1d" />
+        </match>
+      </match>
+    </magic>
+  </mime-type>
+</mime-info>
diff --git a/tool/tilem-src/data/icons-svg/breakpoint.svg b/tool/tilem-src/data/icons-svg/breakpoint.svg
new file mode 100644
index 0000000..e3c55fb
--- /dev/null
+++ b/tool/tilem-src/data/icons-svg/breakpoint.svg
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16px"
+   height="16px"
+   id="svg4802"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="breakpoint.svg">
+  <defs
+     id="defs4804">
+    <linearGradient
+       id="linearGradient5326">
+      <stop
+         style="stop-color:#ef2929;stop-opacity:1;"
+         offset="0"
+         id="stop5328" />
+      <stop
+         style="stop-color:#a40000;stop-opacity:1;"
+         offset="1"
+         id="stop5330" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5326"
+       id="radialGradient5352"
+       cx="5.0625"
+       cy="4.0624986"
+       fx="5.0625"
+       fy="4.0624986"
+       r="7"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.4693072e-8,1.2857142,-1.2857143,1.4693073e-8,11.223212,-2.508928)" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5326"
+       id="radialGradient6163"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.4693072e-8,1.2857142,-1.2857143,1.4693073e-8,11.723212,-3.008928)"
+       cx="5.0625"
+       cy="4.0624986"
+       fx="5.0625"
+       fy="4.0624986"
+       r="7" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.627417"
+     inkscape:cx="9.0315411"
+     inkscape:cy="7.1603107"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="888"
+     inkscape:window-height="993"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4812"
+       empspacing="2"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       spacingx="0.5px"
+       spacingy="0.5px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata4807">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       id="g6159"
+       transform="translate(-0.5,0.5)">
+      <path
+         d="M 14,8 A 6,6 0 1 1 2,8 6,6 0 1 1 14,8 z"
+         sodipodi:ry="6"
+         sodipodi:rx="6"
+         sodipodi:cy="8"
+         sodipodi:cx="8"
+         id="path4814"
+         style="fill:url(#radialGradient6163);fill-opacity:1;fill-rule:evenodd;stroke:#800000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         sodipodi:type="arc" />
+      <path
+         d="M 13,8 A 5,5 0 1 1 3,8 5,5 0 1 1 13,8 z"
+         sodipodi:ry="5"
+         sodipodi:rx="5"
+         sodipodi:cy="8"
+         sodipodi:cx="8"
+         id="path5336"
+         style="fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:0.24705882;stroke-dasharray:none;stroke-dashoffset:0"
+         sodipodi:type="arc" />
+    </g>
+  </g>
+</svg>
diff --git a/tool/tilem-src/data/icons-svg/pc-arrow.svg b/tool/tilem-src/data/icons-svg/pc-arrow.svg
new file mode 100644
index 0000000..13874e3
--- /dev/null
+++ b/tool/tilem-src/data/icons-svg/pc-arrow.svg
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16px"
+   height="16px"
+   id="svg5540"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="pc-arrow.svg">
+  <defs
+     id="defs5542">
+    <linearGradient
+       id="linearGradient6064">
+      <stop
+         style="stop-color:#fce94f;stop-opacity:1;"
+         offset="0"
+         id="stop6066" />
+      <stop
+         style="stop-color:#c4a000;stop-opacity:1;"
+         offset="1"
+         id="stop6068" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 8 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="16 : 8 : 1"
+       inkscape:persp3d-origin="8 : 5.3333333 : 1"
+       id="perspective5548" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6064"
+       id="linearGradient6070"
+       x1="9"
+       y1="6"
+       x2="8"
+       y2="11"
+       gradientUnits="userSpaceOnUse" />
+    <inkscape:perspective
+       id="perspective6082"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5326"
+       id="radialGradient5352"
+       cx="5.0625"
+       cy="4.0624986"
+       fx="5.0625"
+       fy="4.0624986"
+       r="7"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.4693072e-8,1.2857142,-1.2857143,1.4693073e-8,11.223212,-2.508928)" />
+    <linearGradient
+       id="linearGradient5326">
+      <stop
+         style="stop-color:#ef2929;stop-opacity:1;"
+         offset="0"
+         id="stop5328" />
+      <stop
+         style="stop-color:#a40000;stop-opacity:1;"
+         offset="1"
+         id="stop5330" />
+    </linearGradient>
+    <radialGradient
+       r="7"
+       fy="4.0624986"
+       fx="5.0625"
+       cy="4.0624986"
+       cx="5.0625"
+       gradientTransform="matrix(1.4693072e-8,1.2857142,-1.2857143,1.4693073e-8,10.723212,-3.0089279)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient6092"
+       xlink:href="#linearGradient5326"
+       inkscape:collect="always" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6064"
+       id="linearGradient6188"
+       gradientUnits="userSpaceOnUse"
+       x1="8"
+       y1="6"
+       x2="7"
+       y2="11"
+       gradientTransform="translate(1,0)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32"
+     inkscape:cx="8.1146157"
+     inkscape:cy="8.0354484"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="888"
+     inkscape:window-height="993"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid5550"
+       empspacing="2"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       spacingx="0.5px"
+       spacingy="0.5px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata5545">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       id="g6184"
+       transform="translate(-1,0)">
+      <path
+         sodipodi:nodetypes="cccccccc"
+         id="path5552"
+         d="m 13.5,8.5 -4,4 0,-2 -5,0 0,-4 5,0 0,-2 4,4 z"
+         style="fill:url(#linearGradient6188);fill-opacity:1;fill-rule:evenodd;stroke:#c4a000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         sodipodi:nodetypes="cccccccc"
+         id="path6072"
+         d="m 12,8.5 -1.5,1.5 0,-0.5 -5,0 0,-2 5,0 0,-0.5 1.5,1.5 z"
+         style="fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:0.36138617;stroke-dasharray:none;stroke-dashoffset:0" />
+    </g>
+  </g>
+</svg>
diff --git a/tool/tilem-src/data/icons-svg/stepicons-16.svg b/tool/tilem-src/data/icons-svg/stepicons-16.svg
new file mode 100644
index 0000000..c89f3e4
--- /dev/null
+++ b/tool/tilem-src/data/icons-svg/stepicons-16.svg
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16px"
+   height="16px"
+   id="svg17687"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="stepicons-16.svg">
+  <defs
+     id="defs17689">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 8 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="16 : 8 : 1"
+       inkscape:persp3d-origin="8 : 5.3333333 : 1"
+       id="perspective7144" />
+    <linearGradient
+       id="linearGradient3743-1">
+      <stop
+         style="stop-color:#d3d7cf;stop-opacity:1;"
+         offset="0"
+         id="stop3745-9" />
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="1"
+         id="stop3747-6" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient14417-2">
+      <stop
+         style="stop-color:#8ae234;stop-opacity:1;"
+         offset="0"
+         id="stop14419-0" />
+      <stop
+         style="stop-color:#4e9a06;stop-opacity:1;"
+         offset="1"
+         id="stop14421-6" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient14417-2-6-4">
+      <stop
+         style="stop-color:#729fcf;stop-opacity:1;"
+         offset="0"
+         id="stop14419-0-1-9" />
+      <stop
+         style="stop-color:#204a87;stop-opacity:1;"
+         offset="1"
+         id="stop14421-6-1-9" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient14417-2-6">
+      <stop
+         style="stop-color:#ad7fa8;stop-opacity:1;"
+         offset="0"
+         id="stop14419-0-1" />
+      <stop
+         style="stop-color:#5c3566;stop-opacity:1;"
+         offset="1"
+         id="stop14421-6-1" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient14417-2"
+       id="linearGradient4839"
+       x1="5.5"
+       y1="4.53125"
+       x2="3.3125"
+       y2="9.84375"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3743-1"
+       id="linearGradient4880"
+       x1="11"
+       y1="5"
+       x2="13"
+       y2="13"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient14417-2-6-4"
+       id="linearGradient4890"
+       gradientUnits="userSpaceOnUse"
+       x1="3.3910849"
+       y1="8.5"
+       x2="9"
+       y2="7.2027574" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient14417-2-6"
+       id="linearGradient4900"
+       gradientUnits="userSpaceOnUse"
+       x1="3.6299806"
+       y1="11.140816"
+       x2="11.09375"
+       y2="12.219177" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="10.91886"
+     inkscape:cy="6.6259422"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1278"
+     inkscape:window-height="993"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid17697"
+       empspacing="2"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       spacingx="0.5px"
+       spacingy="0.5px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata17692">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       id="boxes">
+      <rect
+         ry="1.5"
+         rx="1.5"
+         y="4.5"
+         x="8.5"
+         height="6"
+         width="7"
+         id="rect2895-6"
+         style="opacity:1;fill:url(#linearGradient4880);fill-opacity:1;stroke:#555753;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <rect
+         ry="0.5"
+         rx="0.5"
+         y="5.5"
+         x="9.5"
+         height="4"
+         width="5"
+         id="rect3713-8"
+         style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-opacity:0.23529412" />
+    </g>
+    <g
+       id="exit-arrow">
+      <path
+         sodipodi:nodetypes="ccccccccccc"
+         id="path13783-3"
+         d="m 9.5,5.5 0,4 -3,0 0,2 2,0 -4,4 -4,-4 2,0 0,-2 c 0,-2.5 1.5,-4 4,-4 l 3,0 z"
+         style="fill:url(#linearGradient4900);fill-opacity:1;fill-rule:evenodd;stroke:#5c3566;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         sodipodi:nodetypes="cccccccccccc"
+         id="path13783-5-7"
+         d="m 8.5,6.5 0,2 -2,0 c -0.5,0 -1,0.5 -1,1 l 0,3 0.5,0 L 4.5,14 3,12.5 l 0.5,0 0,-3 c 0,-2 1,-3 3,-3 l 2,0 z"
+         style="fill:none;stroke:#ffffff;stroke-opacity:0.18823529" />
+    </g>
+    <g
+       id="skip-arrow">
+      <path
+         sodipodi:nodetypes="cccccccc"
+         id="path13783-3-8"
+         d="m 2.5,1.5 4,0 0,10 2,0 -4,4 -4,-4 2,0 0,-10 z"
+         style="fill:url(#linearGradient4890);fill-opacity:1;fill-rule:evenodd;stroke:#204a87;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         sodipodi:nodetypes="cccccccc"
+         id="path13783-5-7-3"
+         d="m 3.5,2.5 2,0 0,10 0.5,0 L 4.5,14 3,12.5 l 0.5,0 0,-10 z"
+         style="fill:none;stroke:#ffffff;stroke-opacity:0.15686275" />
+    </g>
+    <g
+       id="enter-arrow">
+      <path
+         sodipodi:nodetypes="ccccccccccc"
+         id="path13783"
+         d="m 0.5,0.5 4,0 0,5 2,0 0,-2 4,4 -4,4 0,-2 -2,0 C 2,9.5 0.5,8 0.5,5.5 l 0,-5 z"
+         style="opacity:1;fill:url(#linearGradient4839);fill-opacity:1;fill-rule:evenodd;stroke:#4e9a06;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         sodipodi:nodetypes="cccccccccccc"
+         id="path13783-5"
+         d="m 1.5,1.5 2,0 0,4 c 0,0.5 0.5,1 1,1 l 3,0 L 7.5,6 9,7.5 7.5,9 l 0,-0.5 -3,0 c -2,0 -3,-1 -3,-3 l 0,-4 z"
+         style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-opacity:0.23529412000000000" />
+    </g>
+  </g>
+</svg>
diff --git a/tool/tilem-src/data/icons-svg/stepicons-24.svg b/tool/tilem-src/data/icons-svg/stepicons-24.svg
new file mode 100644
index 0000000..d4b9f4b
--- /dev/null
+++ b/tool/tilem-src/data/icons-svg/stepicons-24.svg
@@ -0,0 +1,389 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="24"
+   height="24"
+   id="svg16550"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="stepicons-24.svg"
+   inkscape:export-filename="/home/benjamin/programs/tilem2/misc/step-over-24.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs16552">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 12 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="24 : 12 : 1"
+       inkscape:persp3d-origin="12 : 8 : 1"
+       id="perspective60" />
+    <linearGradient
+       id="linearGradient19570">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop19572" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop19574" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5777">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="0"
+         id="stop5779" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0.49803922;"
+         offset="1"
+         id="stop5781" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient14417-2-6-4">
+      <stop
+         style="stop-color:#729fcf;stop-opacity:1;"
+         offset="0"
+         id="stop14419-0-1-9" />
+      <stop
+         style="stop-color:#204a87;stop-opacity:1;"
+         offset="1"
+         id="stop14421-6-1-9" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient14417-2-6">
+      <stop
+         style="stop-color:#ad7fa8;stop-opacity:1;"
+         offset="0"
+         id="stop14419-0-1" />
+      <stop
+         style="stop-color:#5c3566;stop-opacity:1;"
+         offset="1"
+         id="stop14421-6-1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient14417-2">
+      <stop
+         style="stop-color:#73d216;stop-opacity:1;"
+         offset="0"
+         id="stop14419-0" />
+      <stop
+         style="stop-color:#4e9a06;stop-opacity:1;"
+         offset="1"
+         id="stop14421-6" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3743-1-3-0">
+      <stop
+         style="stop-color:#d3d7cf;stop-opacity:1;"
+         offset="0"
+         id="stop3745-9-8-3" />
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="1"
+         id="stop3747-6-73-03" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3743-1-3-0"
+       id="linearGradient6950"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,-8)"
+       x1="17"
+       y1="2"
+       x2="19"
+       y2="10" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3743-1-3-0"
+       id="linearGradient6969"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,-8)"
+       x1="17"
+       y1="17"
+       x2="19"
+       y2="25" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient14417-2-6"
+       id="linearGradient4301"
+       gradientUnits="userSpaceOnUse"
+       x1="8.5"
+       y1="4.3125"
+       x2="16"
+       y2="11.0625" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient14417-2"
+       id="linearGradient5798"
+       gradientUnits="userSpaceOnUse"
+       x1="9"
+       y1="-2.375"
+       x2="3.625"
+       y2="8.5" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3743-1-3-0"
+       id="linearGradient19598"
+       x1="17"
+       y1="0"
+       x2="19"
+       y2="11"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3743-1-3-0"
+       id="linearGradient19608"
+       x1="18"
+       y1="13"
+       x2="18"
+       y2="10.5"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient14417-2-6-4"
+       id="linearGradient19628"
+       gradientUnits="userSpaceOnUse"
+       x1="5.125"
+       y1="5"
+       x2="12.5"
+       y2="6.3125" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient19570"
+       id="linearGradient19636"
+       x1="-1.5"
+       y1="4"
+       x2="9"
+       y2="4"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient19570"
+       id="linearGradient19638"
+       gradientUnits="userSpaceOnUse"
+       x1="-0.83650517"
+       y1="8.1351967"
+       x2="5.7805586"
+       y2="8.1351967" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5777"
+       id="radialGradient22298"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-0.85844211,-0.88068425,1.5254682,-1.4869414,-4.0810409,47.152195)"
+       cx="10.991496"
+       cy="15.354539"
+       fx="10.991496"
+       fy="15.354539"
+       r="5.25" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient19570"
+       id="linearGradient22300"
+       gradientUnits="userSpaceOnUse"
+       x1="9.5"
+       y1="5.5"
+       x2="9.5"
+       y2="14" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="6.6424946"
+     inkscape:cy="9.8308889"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:snap-global="true"
+     inkscape:window-width="1278"
+     inkscape:window-height="993"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid16560"
+       empspacing="2"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       spacingx="0.5px"
+       spacingy="0.5px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata16555">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,8)">
+    <g
+       id="enter-arrow">
+      <path
+         sodipodi:nodetypes="ccccccccccc"
+         id="path13783"
+         d="m 1.5,-5.5 6,0 0,7 3,0 0,-3 6,6 -6,6 0,-3 -4.5,0 C 3.5,7.5 1.5,5.5 1.5,3 l 0,-8.5 z"
+         style="fill:url(#linearGradient5798);fill-opacity:1;fill-rule:evenodd;stroke:#4e9a06;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         sodipodi:nodetypes="cccccccccccc"
+         id="path13783-5"
+         d="m 2.5,-4.5 4,0 0,6 c 0,0.5 0.5,1 1,1 l 4,0 L 11.5,1 15,4.5 11.5,8 11.5,6.5 6,6.5 C 4,6.5 2.5,5 2.5,3 l 0,-7.5 z"
+         style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:0.25098039000000000" />
+      <path
+         sodipodi:nodetypes="ccssssc"
+         transform="translate(0,-8)"
+         id="path19548"
+         d="M 2,11.5 C 1.9441214,10.997093 2.014329,4.7642029 2,4.5 2,4 2.3052366,3.5236342 2.7400388,3.427612 3.4340805,3.274339 3.9146482,3.7586271 4.1100582,4.0463305 5.1289687,5.5464784 4.7496137,9.1495023 4.5740968,10.741748 4.4778765,11.614635 4.2844847,12.982713 3.7786017,13.636466 3.0393768,14.591768 2.0558786,12.002907 2,11.5 z"
+         style="fill:url(#linearGradient19638);stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" />
+      <path
+         sodipodi:nodetypes="ccccc"
+         transform="translate(0,-8)"
+         id="path19568"
+         d="m 7.5,10 c -4.9264746,2.756282 2.955806,3.522097 7.5,2 l -4,-4 0,2 -3.5,0 z"
+         style="fill:url(#linearGradient22300);stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" />
+    </g>
+    <g
+       id="skip-arrow">
+      <path
+         sodipodi:nodetypes="cccccccc"
+         id="path13783-3-8"
+         d="m 4.5,-4.5 6,0 0,13 3,0 -6,6 -6,-6 3,0 0,-13 z"
+         style="fill:url(#linearGradient19628);fill-opacity:1;fill-rule:evenodd;stroke:#204a87;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         sodipodi:nodetypes="cccccccc"
+         id="path13783-5-7-3"
+         d="m 5.5,-3.5 4,0 0,13 1.5,0 L 7.5,13 4,9.5 l 1.5,0 0,-13 z"
+         style="fill:none;stroke:#ffffff;stroke-opacity:0.18823529" />
+      <path
+         style="fill:url(#linearGradient19636);fill-opacity:1;stroke:none"
+         d="M 5,-2.5 C 5,-3 5.2936905,-3.5149404 5.8232233,-3.4611676 6.9640388,-3.3453204 7.8692177,1.7617947 7.8696383,7.081299 7.8698453,9.6989932 7.6325825,10.823223 7,13 L 3,9 5,9 5,-2.5 z"
+         id="path19615"
+         sodipodi:nodetypes="csscccc" />
+    </g>
+    <g
+       id="boxes">
+      <g
+         id="g6971">
+        <rect
+           style="fill:url(#linearGradient6969);fill-opacity:1;stroke:#555753;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           id="rect2895-6-1-8"
+           width="9"
+           height="6"
+           x="13.5"
+           y="8.5"
+           rx="2"
+           ry="2" />
+        <rect
+           style="fill:none;stroke:#ffffff;stroke-opacity:0.23529412"
+           id="rect3713-8-0-0"
+           width="7"
+           height="4"
+           x="14.5"
+           y="9.5"
+           rx="1"
+           ry="1" />
+      </g>
+      <g
+         id="g19610">
+        <rect
+           style="fill:url(#linearGradient19598);stroke:#555753;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;fill-opacity:1"
+           id="rect2895-6-1-5"
+           width="9"
+           height="9"
+           x="13.5"
+           y="-0.5"
+           rx="2"
+           ry="2" />
+        <rect
+           style="fill:none;stroke:#ffffff;stroke-opacity:0.23529412"
+           id="rect3713-8-0-9"
+           width="7"
+           height="7"
+           x="14.5"
+           y="0.5"
+           rx="1"
+           ry="1" />
+        <rect
+           style="fill:url(#linearGradient19608);fill-opacity:1;fill-rule:evenodd;stroke:none"
+           id="rect19600"
+           width="5"
+           height="2"
+           x="15.5"
+           y="11"
+           transform="translate(0,-8)"
+           rx="1"
+           ry="0.984375" />
+      </g>
+      <g
+         id="g6986">
+        <rect
+           style="fill:url(#linearGradient6950);fill-opacity:1;stroke:#555753;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+           id="rect2895-6-1"
+           width="9"
+           height="6"
+           x="13.5"
+           y="-6.5"
+           rx="2"
+           ry="2" />
+        <rect
+           style="fill:none;stroke:#ffffff;stroke-opacity:0.23529412"
+           id="rect3713-8-0"
+           width="7"
+           height="4"
+           x="14.5"
+           y="-5.5"
+           rx="1"
+           ry="1" />
+      </g>
+    </g>
+    <g
+       id="exit-arrow">
+      <path
+         sodipodi:nodetypes="ccccccccccc"
+         id="path13783-3"
+         d="m 15.5,0.5 0,6 -5,0 0,2 3,0 -6,6 -6,-6 3,0 0,-3.5 C 4.5,2.5 6.5,0.5 9,0.5 l 6.5,0 z"
+         style="fill:url(#linearGradient4301);fill-opacity:1;fill-rule:evenodd;stroke:#5c3566;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+      <path
+         sodipodi:nodetypes="cccccccccccc"
+         id="path13783-5-7"
+         d="m 14.5,1.5 0,4 -4,0 c -0.5,0 -1,0.5 -1,1 l 0,3 1.5,0 L 7.5,13 4,9.5 5.5,9.5 5.5,5 C 5.5,3 7,1.5 9,1.5 l 5.5,0 z"
+         style="fill:none;stroke:#ffffff;stroke-opacity:0.19607843000000000;fill-opacity:1" />
+      <path
+         sodipodi:nodetypes="ccssssssscscc"
+         transform="translate(0,-8)"
+         id="path5775"
+         d="m 3,17 4,4 C 7.522097,19.367417 7.7711374,18.480908 7.7046955,17.621939 7.6368873,16.745307 7.4622004,15.840071 7.4033784,15.188039 7.292893,13.963314 7.2859183,13.413837 7.5138635,12.69064 7.7252404,12.02001 8.4401649,11.452716 9.2209703,11.226601 10.029108,10.992571 11.281114,11.028841 12.265164,10.850951 13.243067,10.674173 14.2513,10.314991 14.229203,9.9254757 14.200884,9.4262783 13.287262,9.0441942 12.817591,9.0441942 11.817593,9.0441942 10,9 9,9 7,9 5,10.5 5,13 c 0,1 0,4 0,4 l -2,0 z"
+         style="fill:url(#radialGradient22298);stroke:none;fill-opacity:1" />
+    </g>
+  </g>
+</svg>
diff --git a/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-finish.png b/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-finish.png
new file mode 100644
index 0000000..bd51d31
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-finish.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-step-over.png b/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-step-over.png
new file mode 100644
index 0000000..19f79af
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-step-over.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-step.png b/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-step.png
new file mode 100644
index 0000000..4b8e1d7
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/16x16/actions/tilem-db-step.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/16x16/apps/tilem.png b/tool/tilem-src/data/icons/hicolor/16x16/apps/tilem.png
new file mode 100644
index 0000000..0023cd9
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/16x16/apps/tilem.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-break-pc.png b/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-break-pc.png
new file mode 100644
index 0000000..35aaa06
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-break-pc.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-break.png b/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-break.png
new file mode 100644
index 0000000..9e9dc33
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-break.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-pc.png b/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-pc.png
new file mode 100644
index 0000000..e6c5594
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/16x16/status/tilem-disasm-pc.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/22x22/apps/tilem.png b/tool/tilem-src/data/icons/hicolor/22x22/apps/tilem.png
new file mode 100644
index 0000000..59a0a9b
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/22x22/apps/tilem.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-finish.png b/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-finish.png
new file mode 100644
index 0000000..8eeb723
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-finish.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-step-over.png b/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-step-over.png
new file mode 100644
index 0000000..d78ffe4
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-step-over.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-step.png b/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-step.png
new file mode 100644
index 0000000..96b9f9d
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/24x24/actions/tilem-db-step.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/24x24/apps/tilem.png b/tool/tilem-src/data/icons/hicolor/24x24/apps/tilem.png
new file mode 100644
index 0000000..6e19b2a
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/24x24/apps/tilem.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/32x32/apps/tilem.png b/tool/tilem-src/data/icons/hicolor/32x32/apps/tilem.png
new file mode 100644
index 0000000..8014816
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/32x32/apps/tilem.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/48x48/apps/tilem.png b/tool/tilem-src/data/icons/hicolor/48x48/apps/tilem.png
new file mode 100644
index 0000000..852b196
Binary files /dev/null and b/tool/tilem-src/data/icons/hicolor/48x48/apps/tilem.png differ
diff --git a/tool/tilem-src/data/icons/hicolor/index.theme b/tool/tilem-src/data/icons/hicolor/index.theme
new file mode 100644
index 0000000..cbb495c
--- /dev/null
+++ b/tool/tilem-src/data/icons/hicolor/index.theme
@@ -0,0 +1,44 @@
+[Icon Theme]
+Name=hicolor
+Comment=Default Theme
+Directories=16x16/actions,16x16/apps,16x16/status,22x22/apps,24x24/actions,24x24/apps,32x32/apps,48x48/apps
+
+[16x16/actions]
+Size=16
+Context=Actions
+Type=Threshold
+
+[16x16/apps]
+Size=16
+Context=Applications
+Type=Threshold
+
+[16x16/status]
+Size=16
+Context=Status
+Type=Threshold
+
+[22x22/apps]
+Size=22
+Context=Applications
+Type=Threshold
+
+[24x24/actions]
+Size=24
+Context=Actions
+Type=Threshold
+
+[24x24/apps]
+Size=24
+Context=Applications
+Type=Threshold
+
+[32x32/apps]
+Size=32
+Context=Applications
+Type=Threshold
+
+[48x48/apps]
+Size=48
+Context=Applications
+Type=Threshold
diff --git a/tool/tilem-src/data/keybindings.ini b/tool/tilem-src/data/keybindings.ini
new file mode 100644
index 0000000..64f3583
--- /dev/null
+++ b/tool/tilem-src/data/keybindings.ini
@@ -0,0 +1,555 @@
+[common]
+Up                = Up
+KP_Up             = Up
+Down              = Down
+KP_Down           = Down
+Left              = Left
+KP_Left           = Left
+Right             = Right
+KP_Right          = Right
+Shift+Up          = 2nd, Up
+Shift+Down        = 2nd, Down
+Tab               = 2nd
+KP_Tab            = 2nd
+ISO_Left_Tab      = 2nd
+Delete            = Del
+KP_Delete         = Del
+BackSpace         = Left, Del
+Ctrl+BackSpace    = Clear
+Ctrl+Delete       = Clear
+Ctrl+KP_Delete    = Clear
+0                 = 0
+KP_0              = 0
+1                 = 1
+KP_1              = 1
+2                 = 2
+KP_2              = 2
+3                 = 3
+KP_3              = 3
+4                 = 4
+KP_4              = 4
+5                 = 5
+KP_5              = 5
+6                 = 6
+KP_6              = 6
+7                 = 7
+KP_7              = 7
+8                 = 8
+KP_8              = 8
+9                 = 9
+KP_9              = 9
+period            = DecPnt
+KP_Decimal        = DecPnt
+asciitilde        = Chs
+dead_tilde        = Chs
+plusminus         = Chs
+Shift+KP_Subtract = Chs
+dollar            = 2nd, Chs
+plus              = Add
+KP_Add            = Add
+minus             = Sub
+KP_Subtract       = Sub
+asterisk          = Mul
+KP_Multiply       = Mul
+slash             = Div
+KP_Divide         = Div
+asciicircum       = Power
+dead_circumflex   = Power
+twosuperior       = Square
+Ctrl+2            = Square
+parenleft         = LParen
+parenright        = RParen
+greater           = Store
+less              = 2nd, Store
+F12               = On
+Shift+F12         = 2nd, On
+Return            = Enter
+KP_Enter          = Enter
+ISO_Enter         = Enter
+Shift+Return      = 2nd, Enter
+Shift+KP_Enter    = 2nd, Enter
+Shift+ISO_Enter   = 2nd, Enter
+
+[ti73]
+INHERIT = common
+F1                = YEqu
+F2                = Window
+F3                = Zoom
+F4                = Trace
+F5                = Graph
+Shift+F1          = 2nd, YEqu
+Shift+F2          = 2nd, Window
+Shift+F3          = 2nd, Zoom
+Shift+F4          = 2nd, Trace
+Shift+F5          = 2nd, Graph
+Home              = 2nd, Left
+End               = 2nd, Right
+F11               = Mode
+Shift+Escape      = 2nd, Mode
+Insert            = 2nd, Del
+KP_Insert         = 2nd, Del
+m                 = Math
+Menu              = 2nd, Math
+apostrophe        = 2nd, Math
+d                 = Draw
+l                 = List
+ampersand         = 2nd, Power
+EuroSign          = 2nd, Power
+F8                = Prgm
+p                 = Prgm
+Ctrl+Tab          = 2nd, Prgm
+Ctrl+KP_Tab       = 2nd, Prgm
+Ctrl+ISO_Left_Tab = 2nd, Prgm
+F7                = Apps
+Escape            = Clear
+underscore        = Unit
+bar               = FracSlash
+brokenbar         = FracSlash
+onehalf           = FracSlash
+backslash         = 2nd, FracSlash
+onesuperior       = 2nd, FracSlash
+Ctrl+1            = 2nd, FracSlash
+f                 = FracDec
+numbersign        = 2nd, FracDec
+a                 = MixSimp
+c                 = Const
+s                 = Simp
+percent           = Percent
+x                 = VarX
+comma             = Comma
+
+[ti76]
+INHERIT = common
+F1                = YEqu
+F2                = Window
+F3                = Zoom
+F4                = Trace
+F5                = Graph
+Shift+F1          = 2nd, YEqu
+Shift+F2          = 2nd, Window
+Shift+F3          = 2nd, Zoom
+Shift+F4          = 2nd, Trace
+Shift+F5          = 2nd, Graph
+Home              = 2nd, Left
+KP_Home           = 2nd, Left
+End               = 2nd, Right
+KP_End            = 2nd, Right
+F11               = Mode
+Shift+Escape      = 2nd, Mode
+Insert            = 2nd, Del
+KP_Insert         = 2nd, Del
+Menu              = Alpha
+apostrophe        = Alpha
+x                 = Graphvar
+F10               = Stat
+F6                = Math
+F7                = Matrix
+F8                = Prgm
+F9                = Vars
+Escape            = Clear
+backslash         = Recip
+onesuperior       = Recip
+Ctrl+1            = Recip
+s                 = Sin
+c                 = Cos
+t                 = Tan
+numbersign        = 2nd, Power
+comma             = Comma
+ampersand         = 2nd, Comma
+EuroSign          = 2nd, Comma
+braceleft         = 2nd, LParen
+braceright        = 2nd, RParen
+e                 = 2nd, Div
+o                 = Log
+u                 = 2nd, 7
+v                 = 2nd, 8
+w                 = 2nd, 9
+l                 = Ln
+p                 = Prgm
+Ctrl+Tab          = 2nd, 0
+Ctrl+KP_Tab       = 2nd, 0
+Ctrl+ISO_Left_Tab = 2nd, 0
+
+[ti81]
+INHERIT = common
+F1           = YEqu
+F2           = Range
+F3           = Zoom
+F4           = Trace
+F5           = Graph
+Shift+F1     = 2nd, YEqu
+Shift+F2     = 2nd, Range
+Shift+F3     = 2nd, Zoom
+Shift+F4     = 2nd, Trace
+Shift+F5     = 2nd, Graph
+Insert       = Ins
+KP_Insert    = Ins
+Menu         = Alpha
+apostrophe   = Alpha
+x            = Graphvar
+F11          = Mode
+F6           = Math
+F7           = Matrix
+F8           = Prgm
+F9           = Vars
+Escape       = Clear
+Shift+Escape = 2nd, Clear
+backslash    = Recip
+onesuperior  = Recip
+Ctrl+1       = Recip
+bar          = 2nd, Recip
+brokenbar    = 2nd, Recip
+s            = Sin
+c            = Cos
+t            = Tan
+p            = Prgm
+numbersign   = 2nd, Power
+ampersand    = EE
+EuroSign     = EE
+o            = Log
+l            = Ln
+A            = Alpha, Math
+B            = Alpha, Matrix
+C            = Alpha, Prgm
+D            = Alpha, Recip
+E            = Alpha, Sin
+F            = Alpha, Cos
+G            = Alpha, Tan
+H            = Alpha, Power
+I            = Alpha, Square
+J            = Alpha, EE
+K            = Alpha, LParen
+L            = Alpha, RParen
+M            = Alpha, Div
+N            = Alpha, Log
+O            = Alpha, 7
+P            = Alpha, 8
+Q            = Alpha, 9
+R            = Alpha, Mul
+S            = Alpha, Ln
+T            = Alpha, 4
+U            = Alpha, 5
+V            = Alpha, 6
+W            = Alpha, Sub
+X            = Alpha, Store
+Y            = Alpha, 1
+Z            = Alpha, 2
+at           = Alpha, 3
+quotedbl     = Alpha, Add
+space        = Alpha, 0
+comma        = Alpha, DecPnt
+question     = Alpha, Chs
+
+[ti82]
+INHERIT = common
+F1           = YEqu
+F2           = Window
+F3           = Zoom
+F4           = Trace
+F5           = Graph
+Shift+F1     = 2nd, YEqu
+Shift+F2     = 2nd, Window
+Shift+F3     = 2nd, Zoom
+Shift+F4     = 2nd, Trace
+Shift+F5     = 2nd, Graph
+Home         = 2nd, Left
+KP_Home      = 2nd, Left
+End          = 2nd, Right
+KP_End       = 2nd, Right
+F11          = Mode
+Shift+Escape = 2nd, Mode
+Insert       = 2nd, Del
+KP_Insert    = 2nd, Del
+Menu         = Alpha
+apostrophe   = Alpha
+x            = Graphvar
+F10          = Stat
+F6           = Math
+F7           = Matrix
+F8           = Prgm
+F9           = Vars
+Escape       = Clear
+backslash    = Recip
+onesuperior  = Recip
+Ctrl+1       = Recip
+bar          = 2nd, Recip
+brokenbar    = 2nd, Recip
+s            = Sin
+c            = Cos
+t            = Tan
+numbersign   = 2nd, Power
+comma        = Comma
+ampersand    = 2nd, Comma
+EuroSign     = 2nd, Comma
+braceleft    = 2nd, LParen
+braceright   = 2nd, RParen
+o            = Log
+u            = 2nd, 7
+v            = 2nd, 8
+n            = 2nd, 9
+bracketleft  = 2nd, Mul
+l            = Ln
+p            = Prgm
+bracketright = 2nd, Sub
+A            = Alpha, Math
+B            = Alpha, Matrix
+C            = Alpha, Prgm
+D            = Alpha, Recip
+E            = Alpha, Sin
+F            = Alpha, Cos
+G            = Alpha, Tan
+H            = Alpha, Power
+I            = Alpha, Square
+J            = Alpha, Comma
+K            = Alpha, LParen
+L            = Alpha, RParen
+M            = Alpha, Div
+N            = Alpha, Log
+O            = Alpha, 7
+P            = Alpha, 8
+Q            = Alpha, 9
+R            = Alpha, Mul
+S            = Alpha, Ln
+T            = Alpha, 4
+U            = Alpha, 5
+V            = Alpha, 6
+W            = Alpha, Sub
+X            = Alpha, Store
+Y            = Alpha, 1
+Z            = Alpha, 2
+at           = Alpha, 3
+quotedbl     = Alpha, Add
+space        = Alpha, 0
+colon        = Alpha, DecPnt
+question     = Alpha, Chs
+
+[ti83]
+INHERIT = common
+F1                = YEqu
+F2                = Window
+F3                = Zoom
+F4                = Trace
+F5                = Graph
+Shift+F1          = 2nd, YEqu
+Shift+F2          = 2nd, Window
+Shift+F3          = 2nd, Zoom
+Shift+F4          = 2nd, Trace
+Shift+F5          = 2nd, Graph
+Home              = 2nd, Left
+KP_Home           = 2nd, Left
+End               = 2nd, Right
+KP_End            = 2nd, Right
+Page_Up           = Alpha, Up
+KP_Page_Up        = Alpha, Up
+Page_Down         = Alpha, Down
+KP_Page_Down      = Alpha, Down
+F11               = Mode
+Shift+Escape      = 2nd, Mode
+Insert            = 2nd, Del
+KP_Insert         = 2nd, Del
+Menu              = Alpha
+apostrophe        = Alpha
+x                 = Graphvar
+F10               = Stat
+F6                = Math
+F7                = Matrix
+F8                = Prgm
+F9                = Vars
+Escape            = Clear
+backslash         = Recip
+onesuperior       = Recip
+Ctrl+1            = Recip
+s                 = Sin
+c                 = Cos
+t                 = Tan
+numbersign        = 2nd, Power
+comma             = Comma
+ampersand         = 2nd, Comma
+EuroSign          = 2nd, Comma
+braceleft         = 2nd, LParen
+braceright        = 2nd, RParen
+e                 = 2nd, Div
+o                 = Log
+u                 = 2nd, 7
+v                 = 2nd, 8
+w                 = 2nd, 9
+p                 = Prgm
+bracketleft       = 2nd, Mul
+l                 = Ln
+bracketright      = 2nd, Sub
+Ctrl+Tab          = 2nd, 0
+Ctrl+KP_Tab       = 2nd, 0
+Ctrl+ISO_Left_Tab = 2nd, 0
+i                 = 2nd, DecPnt
+A                 = Alpha, Math
+B                 = Alpha, Matrix
+C                 = Alpha, Prgm
+D                 = Alpha, Recip
+E                 = Alpha, Sin
+F                 = Alpha, Cos
+G                 = Alpha, Tan
+H                 = Alpha, Power
+I                 = Alpha, Square
+J                 = Alpha, Comma
+K                 = Alpha, LParen
+L                 = Alpha, RParen
+M                 = Alpha, Div
+N                 = Alpha, Log
+O                 = Alpha, 7
+P                 = Alpha, 8
+Q                 = Alpha, 9
+R                 = Alpha, Mul
+S                 = Alpha, Ln
+T                 = Alpha, 4
+U                 = Alpha, 5
+V                 = Alpha, 6
+W                 = Alpha, Sub
+X                 = Alpha, Store
+Y                 = Alpha, 1
+Z                 = Alpha, 2
+at                = Alpha, 3
+quotedbl          = Alpha, Add
+space             = Alpha, 0
+colon             = Alpha, DecPnt
+question          = Alpha, Chs
+
+[ti83p]
+INHERIT = ti83
+CapsLock+a = Alpha, Alpha, Math
+CapsLock+b = Alpha, Alpha, Apps
+CapsLock+c = Alpha, Alpha, Prgm
+CapsLock+d = Alpha, Alpha, Recip
+CapsLock+e = Alpha, Alpha, Sin
+CapsLock+f = Alpha, Alpha, Cos
+CapsLock+g = Alpha, Alpha, Tan
+CapsLock+h = Alpha, Alpha, Power
+CapsLock+i = Alpha, Alpha, Square
+CapsLock+j = Alpha, Alpha, Comma
+CapsLock+k = Alpha, Alpha, LParen
+CapsLock+l = Alpha, Alpha, RParen
+CapsLock+m = Alpha, Alpha, Div
+CapsLock+n = Alpha, Alpha, Log
+CapsLock+o = Alpha, Alpha, 7
+CapsLock+p = Alpha, Alpha, 8
+CapsLock+q = Alpha, Alpha, 9
+CapsLock+r = Alpha, Alpha, Mul
+CapsLock+s = Alpha, Alpha, Ln
+CapsLock+t = Alpha, Alpha, 4
+CapsLock+u = Alpha, Alpha, 5
+CapsLock+v = Alpha, Alpha, 6
+CapsLock+w = Alpha, Alpha, Sub
+CapsLock+x = Alpha, Alpha, Store
+CapsLock+y = Alpha, Alpha, 1
+CapsLock+z = Alpha, Alpha, 2
+
+[ti83pse]
+INHERIT = ti83p
+
+[ti84p]
+INHERIT = ti83p
+
+[ti84pse]
+INHERIT = ti83p
+
+[ti84pns]
+INHERIT = ti83p
+
+[ti85]
+INHERIT = common
+F1                = F1
+F2                = F2
+F3                = F3
+F4                = F4
+F5                = F5
+Shift+F1          = 2nd, F1
+Shift+F2          = 2nd, F2
+Shift+F3          = 2nd, F3
+Shift+F4          = 2nd, F4
+Shift+F5          = 2nd, F5
+Home              = 2nd, Left
+KP_Home           = 2nd, Left
+End               = 2nd, Right
+KP_End            = 2nd, Right
+Escape            = Exit
+Shift+Escape      = 2nd, Exit
+Page_Down         = More
+KP_Page_Down      = More
+F11               = 2nd, More
+Menu              = Alpha
+apostrophe        = Alpha
+Insert            = 2nd, Del
+KP_Insert         = 2nd, Del
+F6                = Graph
+F7                = Stat
+F8                = Prgm
+F9                = Custom
+Ctrl+Tab          = 2nd, Custom
+Ctrl+KP_Tab       = 2nd, Custom
+Ctrl+ISO_Left_Tab = 2nd, Custom
+numbersign        = 2nd, Power
+ampersand         = EE
+EuroSign          = EE
+backslash         = 2nd, EE
+onesuperior       = 2nd, EE
+Ctrl+1            = 2nd, EE
+bracketleft       = 2nd, LParen
+bracketright      = 2nd, RParen
+comma             = Comma
+colon             = 2nd, DecPnt
+A                 = Alpha, Log
+B                 = Alpha, Sin
+C                 = Alpha, Cos
+D                 = Alpha, Tan
+E                 = Alpha, Power
+F                 = Alpha, Ln
+G                 = Alpha, EE
+H                 = Alpha, LParen
+I                 = Alpha, RParen
+J                 = Alpha, Div
+K                 = Alpha, Square
+L                 = Alpha, 7
+M                 = Alpha, 8
+N                 = Alpha, 9
+O                 = Alpha, Mul
+P                 = Alpha, Comma
+Q                 = Alpha, 4
+R                 = Alpha, 5
+S                 = Alpha, 6
+T                 = Alpha, Sub
+U                 = Alpha, 1
+V                 = Alpha, 2
+W                 = Alpha, 3
+X                 = Alpha, Add
+Y                 = Alpha, 0
+Z                 = Alpha, DecPnt
+space             = Alpha, Chs
+equal             = Alpha, Store
+a                 = 2nd, Alpha, Log
+b                 = 2nd, Alpha, Sin
+c                 = 2nd, Alpha, Cos
+d                 = 2nd, Alpha, Tan
+e                 = 2nd, Alpha, Power
+f                 = 2nd, Alpha, Ln
+g                 = 2nd, Alpha, EE
+h                 = 2nd, Alpha, LParen
+i                 = 2nd, Alpha, RParen
+j                 = 2nd, Alpha, Div
+k                 = 2nd, Alpha, Square
+l                 = 2nd, Alpha, 7
+m                 = 2nd, Alpha, 8
+n                 = 2nd, Alpha, 9
+o                 = 2nd, Alpha, Mul
+p                 = 2nd, Alpha, Comma
+q                 = 2nd, Alpha, 4
+r                 = 2nd, Alpha, 5
+s                 = 2nd, Alpha, 6
+t                 = 2nd, Alpha, Sub
+u                 = 2nd, Alpha, 1
+v                 = 2nd, Alpha, 2
+w                 = 2nd, Alpha, 3
+x                 = 2nd, Alpha, Add
+y                 = 2nd, Alpha, 0
+z                 = 2nd, Alpha, DecPnt
+
+[ti86]
+INHERIT = ti85
diff --git a/tool/tilem-src/data/skins/README b/tool/tilem-src/data/skins/README
new file mode 100644
index 0000000..4b4028d
--- /dev/null
+++ b/tool/tilem-src/data/skins/README
@@ -0,0 +1,45 @@
+** About the format : **
+
+When I started to work on tilem 2, I firstly decided to use the old tilem format (4 pixs around a lcd).
+But that's not a good idea, and the new skin creation is too hard like this ... :(
+
+Finally, we (with Benjamin) have choosen the Tiemu skin format:
+This format consists in a binary file containing skin information (author, name, model, size etc...) and in the second part :the image data.
+With this format, it becomes really simple and fun to create a skin.
+Tiemu Skinedit works perfectly for tilem 2 (how do you think I 've created these "officials skins :P).
+I hope this new format will pleased to you.
+
+Thank you Julien Blache for his help and his authorization to use Tiemu skin format and skinops.c/skinops.h files.
+Thank you Romain Lievin too.
+
+
+
+** About the image source : **
+
+To find free files to generate skins is not so easy :|
+A part was found on wikimedia (god bless wikimedia)
+Some other are created and donated by tilem2 users :)
+Some other are just my own calc.
+
+For the source of the pictures :
+ti81.skn = GFDL wikimedia.commons
+ti82.skn = GFDL wikimedia.commons
+TI82stats.skn = GFDL wikimedia.commons
+ti83.skn = My TI83 !!!
+ti83p.skn = GFDL wikimedia.commons
+ti83pfr.skn = GPL v3+ given by Claude Clerc (aka claudiux). It's his calc. Thanks to him !!!
+ti84p.skn = My TI84plus !!!
+ti84p2.skn = Mine.
+ti86.skn = LGPL v2.1. Picture given by Danilo Segan. I've resized with the gimp and I've done the skin (the skin given by Danilo was perfect but too big).Thanks to him !!!
+...
+...
+Your contribution ...
+Your contribution ...
+Your contribution ...
+...
+...
+...
+
+Have fun with tilem 2 ;)
+
+Thibault Duponchelle (aka contra-sh)
diff --git a/tool/tilem-src/data/skins/ti76.skn b/tool/tilem-src/data/skins/ti76.skn
new file mode 100644
index 0000000..2f5958b
Binary files /dev/null and b/tool/tilem-src/data/skins/ti76.skn differ
diff --git a/tool/tilem-src/data/skins/ti81.skn b/tool/tilem-src/data/skins/ti81.skn
new file mode 100644
index 0000000..6c1ab92
Binary files /dev/null and b/tool/tilem-src/data/skins/ti81.skn differ
diff --git a/tool/tilem-src/data/skins/ti82.skn b/tool/tilem-src/data/skins/ti82.skn
new file mode 100644
index 0000000..6d3a080
Binary files /dev/null and b/tool/tilem-src/data/skins/ti82.skn differ
diff --git a/tool/tilem-src/data/skins/ti82stats.skn b/tool/tilem-src/data/skins/ti82stats.skn
new file mode 100644
index 0000000..e89827b
Binary files /dev/null and b/tool/tilem-src/data/skins/ti82stats.skn differ
diff --git a/tool/tilem-src/data/skins/ti83.skn b/tool/tilem-src/data/skins/ti83.skn
new file mode 100644
index 0000000..6cbbe6b
Binary files /dev/null and b/tool/tilem-src/data/skins/ti83.skn differ
diff --git a/tool/tilem-src/data/skins/ti83p.skn b/tool/tilem-src/data/skins/ti83p.skn
new file mode 100644
index 0000000..5c97529
Binary files /dev/null and b/tool/tilem-src/data/skins/ti83p.skn differ
diff --git a/tool/tilem-src/data/skins/ti83pfr.skn b/tool/tilem-src/data/skins/ti83pfr.skn
new file mode 100644
index 0000000..0866be2
Binary files /dev/null and b/tool/tilem-src/data/skins/ti83pfr.skn differ
diff --git a/tool/tilem-src/data/skins/ti84p.skn b/tool/tilem-src/data/skins/ti84p.skn
new file mode 100644
index 0000000..c58d544
Binary files /dev/null and b/tool/tilem-src/data/skins/ti84p.skn differ
diff --git a/tool/tilem-src/data/skins/ti84p2.skn b/tool/tilem-src/data/skins/ti84p2.skn
new file mode 100644
index 0000000..5913805
Binary files /dev/null and b/tool/tilem-src/data/skins/ti84p2.skn differ
diff --git a/tool/tilem-src/data/skins/ti86.skn b/tool/tilem-src/data/skins/ti86.skn
new file mode 100644
index 0000000..3377aeb
Binary files /dev/null and b/tool/tilem-src/data/skins/ti86.skn differ
diff --git a/tool/tilem-src/data/symbols/ti82.sym b/tool/tilem-src/data/symbols/ti82.sym
new file mode 100644
index 0000000..4c8afd0
--- /dev/null
+++ b/tool/tilem-src/data/symbols/ti82.sym
@@ -0,0 +1,939 @@
+[macros]
+0CD748D ROM_CALL~%j
+
+[labels]
+0008 rOP1TOOP2
+0010 rFINDSYM
+0018 rPUSHREALO1
+0020 rMOV9TOOP1
+0028 rFPMULT
+0030 rFPADD
+0033 _LdHLind
+004F _SetTblGraphDraw
+0095 _CpHLDE
+00A1 _DivHLBy10
+00A3 _DivHLByA
+
+012B _ApdSetup
+0131 _KbdScan
+01B8 _KEY_READ
+01C7 _STORE_KEY
+01D4 _GetCSC
+01E8 _CanAlphIns
+020D _coorMon
+0213 _Mon
+0220 _MonForceKey
+02B5 _sendKPress
+033C _JForceCmdNoChar
+033D _JForceCmd
+0355 _resetStacks
+0368 _sysErrHandler
+0374 _newContext
+0385 _newContext0
+0462 _PPutAwayPrompt
+046D _PPutAway
+0473 _PutAway
+04B4 _Redisp
+04B9 _SizeWind
+04CA _ErrorEP
+04DC _callMain
+04ED _monErrHand
+
+10C2 _SinCosRad
+10C6 _Sin
+10CA _Cos
+10CE _Tan
+14AA _SinHCosH
+14AE _TanH
+14B2 _CosH
+14B6 _SinH
+154D _ACosRad
+1553 _ATanRad
+1556 _ATan2Rad
+1558 _ATan2Rad_83
+155E _ASinRad
+1563 _ACos
+156D _ATan
+15CD _ATan2_83
+1570 _ATan2
+1575 _ASin
+1787 _ATanH
+17D4 _ASinH
+17E2 _ACosH
+18E7 _HLTimes9
+18F1 _CkOP1Real
+18F7 _Angle
+18FF _CpOP4OP3
+1907 _Mov9OP2Cp
+190C _AbsO1O2Cp
+1912 _CpOP1OP2
+1955 _OP3ToOP4
+195E _OP1ToOP4
+1967 _OP2ToOP4
+1970 _OP4ToOP2
+1979 _OP3ToOP2
+1981 _OP1ToOP3
+1989 _OP5ToOP2
+1991 _OP5ToOP6
+1999 _OP5ToOP4
+19A1 _OP1ToOP2
+19A9 _OP6ToOP2
+19B1 _OP6ToOP1
+19B6 _OP4ToOP1
+19BB _OP5ToOP1
+19C0 _OP3ToOP1
+19C5 _OP4ToOP5
+19CD _OP3ToOP5
+19D5 _OP2ToOP5
+19DD _OP2ToOP6
+19E5 _OP1ToOP6
+19ED _OP1ToOP5
+19F5 _OP2ToOP1
+19FB _Mov11B
+19FD _Mov10B
+19FF _Mov9B
+1A01 _Mov8B
+1A03 _Mov7B
+1A05 _Mov6B
+1A07 _Mov5B
+1A09 _Mov4B
+1A0B _Mov3B
+1A0D _Mov2B
+1A54 _OP2ToOP3
+1A5C _OP4ToOP3
+1A61 _OP5ToOP3
+1A66 _OP4ToOP6
+1A6E _Mov9ToOP1
+1A73 _Mov9OP1OP2
+1A74 _Mov9ToOP2
+1A79 _MovFrOP1
+1A7E _OP4Set1
+1A83 _OP3Set1
+1A88 _OP2Set8
+1A8F _OP2Set5
+1A96 _OP2Set4
+1A9D _OP2Set3
+1AA5 _OP1Set1
+1AAA _OP1Set4
+1AB1 _OP1Set3
+1AB8 _OP3Set2
+1ABD _OP1Set2
+1AC2 _OP2Set2
+1AC5 _SetNum2
+1AC9 _SetMant1
+1ACD _OP2Set1
+1AD0 _SetNum1
+1AD2 _SetNum
+1AD7 _SetNumA
+1AD8 _SetMant
+1ADC _Zero16D
+1ADD _Set16A
+1ADE _Set14A
+1ADF _Set14D
+1AED _OP4Set0
+1AF2 _OP3Set0
+1AF7 _OP2Set0
+1AFC _OP1Set0
+1AFF _SetNum0
+1B07 _ZeroOP1
+1B0C _ZeroOP2
+1B11 _ZeroOP3
+1B14 _ZeroOP
+1B1C _ClrLp
+1B23 _ShRAcc
+1B2B _ShLAcc
+1B39 _ShR18
+1B3A _ShR18A
+1B42 _ShR16
+1B43 _ShR16A
+1B46 _ShR14
+1B76 _ShL16
+1B7A _ShL14
+1C2D _Add16D
+1C33 _Add14D
+1CA2 _Sub16D
+1CA8 _Sub14D
+1CEB _OP2ExOP6
+1CF0 _OP5ExOP6
+1CF8 _OP1ExOP5
+1CFD _OP1ExOP6
+1D02 _OP2ExOP4
+1D07 _OP2ExOP5
+1D0F _OP1ExOP3
+1D14 _OP1ExOP4
+1D19 _OP1ExOP2
+1D21 _ExLp
+1D2B _CkOP1FP0
+1D30 _CkOP2FP0
+1D39 _PosNo0Int
+1D3F _CkPosInt
+1D48 _CkInt
+1D4F _CkOdd
+1DD5 _GetCon1
+1DDA _GetCon
+1E48 _ExpToHex
+1E52 _OP1ExpToDec
+1E9A _CkOP2Pos
+1EA0 _CkOP1Pos
+1EA6 _ClrOP2S
+1EAB _ClrOP1S
+1EB2 _FDiv100
+1EB5 _FDiv10
+1EC0 _DecO1Exp
+1EC8 _IncO1Exp
+1ECB _IncExp
+1ED0 _CkValidNum
+1EF2 _GetExp
+1EFE _HTimesL
+1F0F _EOP1NotReal
+1F16 _PrgmBangName
+1F1B _PrgmPoundName
+1F3E _ThetaName
+1F42 _RName
+1F46 _RegEqName
+1F4C _RecurNName
+1F57 _XName
+1F5B _YName
+1F5F _TName
+1F61 _RealName
+1F6A _SetEStoFPS
+1F71 _ChkTempDirt
+1F94 _OP1MOP2Exp
+1F9E _OP1ExpMDE
+1FB2 _ChkErrBreak
+1FCC _IsA2ByteTok
+1FE3 _GetLastEntry
+1FFE _GetLastEntryPtr
+2014 _REGRCLRCHNG
+2057 _CheckSplitFlag
+2083 _ResetWinTop
+209C _SetYUp
+20A6 _SetXUp
+20AB _MemChk
+20BD _CmpPrgNamLen1
+20C0 _CmpPrgNamLen
+20D1 _FindProgSym
+2124 _ChkFindSym
+2129 _FindSym
+2258 _InsertMem
+2262 _InsertMemA
+227D _EnoughMem
+22AE _CmpMemNeed
+22C3 _CreatePVar4
+22E2 _CreatePVar3
+230F _CreateVar3
+2333 _CreateReal
+2339 _CreateTRList
+233F _CreateRList
+235B _CreateTRMat
+2361 _CreateRMat
+2369 _CreateTStrng
+236F _CreateStrng
+237D _Create0Equ
+2382 _CreateTEqu
+2388 _CreateEqu
+238C _CreatePict
+2393 _CreateGDB
+2397 _CreateProg
+239B _ChkDel
+23A4 _ChkDelA
+23DF _AdjParser
+2400 _AdjMath
+2455 _AdjM7
+2531 _DelMemA
+2547 _DelVar
+2548 _DelVarIO
+258D _DelMem
+2590 _DelVar3D
+2593 _DelVar3C
+25BB _DelVar3DC
+2612 _AdjSymPtrs
+2695 _DataSizeA
+2699 _DataSize
+26C1 _PopRealO6
+26C6 _PopRealO5
+26CB _PopRealO4
+26D0 _PopRealO3
+26D5 _PopRealO2
+26DA _PopRealO1
+26DD _PopReal
+26E3 _FPopCplx
+26E6 _FPopReal
+26E9 _FPopFPS
+26F1 _DeallocFPS
+26F4 _DeallocFPS1
+26FF _AllocFPS
+2702 _AllocFPS1
+270B _PushRealO6
+2710 _PushRealO5
+2715 _PushRealO4
+271A _PushRealO3
+271F _PushRealO2
+2724 _PushRealO1
+2727 _PushReal
+273B _CpyTo1FPS11
+273E _CpyFPS11
+2745 _CpyTo1FPS5
+2748 _CpyFPS5
+274F _CpyTo1FPS6
+2752 _CpyFPS6
+2759 _CpyTo2FPS4
+275C _CpyFPS4
+2763 _CpyTo6FPS3
+2768 _CpyTo2FPS3
+276D _CpyTo1FPS3
+2770 _CpyFPS3
+277C _CpyTo3FPS2
+2783 _CpyTo5FPST
+2788 _CpyTo6FPST
+278D _CpyTo4FPST
+2792 _CpyTo3FPST
+2797 _CpyTo2FPST
+279C _CpyTo1FPST
+279F _CpyFPST
+27A4 _CpyStack
+27AB _CpyTo3FPS1
+27B0 _CpyTo2FPS1
+27B5 _CpyTo1FPS1
+27B8 _CpyFPS1
+27BF _CpyTo2FPS2
+27C4 _CpyTo1FPS2
+27C7 _CpyFPS2
+27CE _CpyO3ToFPST
+27D3 _CpyO2ToFPST
+27D8 _CpyO6ToFPST
+27DD _CpyO1ToFPST
+27E0 _CpyToFPST
+27E5 _CpyToStack
+27ED _CpyO5ToFPS1
+27F2 _CpyO1ToFPS1
+27F5 _CpyToFPS1
+27FC _CpyO2ToFPS2
+2801 _CpyO3ToFPS2
+2806 _CpyO6ToFPS2
+280B _CpyO1ToFPS2
+280E _CpyToFPS2
+2815 _CpyO5ToFPS3
+281A _CpyO1ToFPS3
+281D _CpyToFPS3
+2824 _CpyO1ToFPS4
+2827 _CpyToFPS4
+282E _ErrNotEnoughMem
+283D _FPSMinus9
+2840 _HLMinus9
+2845 _ErrOverflow
+2849 _ErrDivBy0
+284D _ErrSingularMat
+2851 _ErrDomain
+2855 _ErrIncrement
+2859 _ErrSyntax
+285D _ErrMode
+2861 _ErrDataType
+2865 _ErrReserved
+2869 _ErrArgument
+286D _ErrDimMismatch
+2871 _ErrDimension
+2875 _ErrUndefined
+2879 _ErrMemory
+287D _ErrMemoryNE
+2881 _ErrInvalid
+2885 _ErrIllegalNest
+2889 _ErrBound
+288D _ErrGraphRange
+2891 _ErrZoom
+2895 _ErrBreak
+2899 _ErrStat
+289D _ErrSolver
+28A1 _ErrIterations
+28A5 _ErrBadGuess
+28A9 _ErrTolTooSmall
+28AD _ErrStatPlot
+28B1 _ErrLink
+28B3 _JError
+28B6 _JErrorNo
+28DC _noErrorEntry
+28DD _pushErrorHandler
+2903 _popErrorHandler
+2913 _StrLength
+2926 _StrCopy
+292E _StrCat
+295B _IsInSet
+
+2E46 ___bank_call
+2E6D ___bank_ret
+2E75 ___bank_jump
+
+2E86 _PutTokString
+# 2E8C _DispYPrompt2 ?
+2E92 _SetupDispEq
+2E98 _BufPeek2
+2EAA _CloseEquField
+2EB0 _BufToBtm
+2EB6 _CursorLeft
+2EBC _SetEmptyEditEqu
+# 2ECE DOREFFLAGS02 ?
+2EEC _PrevEq
+2EF2 _DarkLine
+2F1C _CursorDown
+2F22 _JPromptCursor
+2F34 _GrphPars
+2F3A _PlotPars
+2F40 _ParseInp
+2F46 _InitPFlgs
+2F4C _OP2Set60
+2F58 _StoTheta
+2F5E _StoR
+2F64 _StoY
+2F6A _StoT
+2F70 _StoX
+2F7C _RclAns
+2F82 _RclY
+2F88 _StMatEl
+2F8E _StLstVecEl
+2F94 _GetDEPtr
+2FA0 _WScrollRight
+2FA6 _WScrollLeft
+2FAC _WScrollUp
+2FB8 _RclToQueue
+2FBE _FORSEQINIT
+2FC4 _PDspGrph
+2FCA _GRDECODA
+2FDC _XYCent
+2FE2 _ZmInt
+2FE8 _CopyRng
+2FEE _ZooDefault
+2FF4 _ZmTrig
+# 2FFA _ZmFit ?
+# 3000 _ZmPrev ?
+# 3006 _ZmDecml ?
+# 300C _ZmUsr ?
+# 3012 _SetUZm ?
+# 3024 _DrawCmd ?
+302A _InvCmd
+3030 _TraceOff
+304E _FNDDB
+3054 _NextEq
+305A _SetFuncM
+3060 _SetPolM
+3066 _SetParM
+306C _SetSeqM
+307E _TanLnF
+# 3084 _ShadeCmd ?
+309C _PointCmd
+30A2 _PixelCmd
+30A8 _ChkTextCurs
+30AE _FormToTok
+30B4 _GDispToken
+30BA _UnLineCmd
+30C0 _LineCmd
+30CC _VertCmd
+30D2 _HorizCmd
+30DE _FormERealTok
+30F0 _RclSysTok
+30F6 _FormEReal
+30FC _OP1toEdit
+310E _IsAtTop
+3114 _ToggleIns
+311A _BufReplace
+3120 _StoSysTok
+3126 _SetupEditEqu
+3132 _RecallEd
+313E _SetupBuffer
+3144 _CreateNumEditBuf
+314A _CallCommon
+3150 _CommonKeys
+3156 _Leftmore
+315C _fDel
+3162 _fClear
+3168 _fInsDisp02
+3180 _CloseEditBufNoR
+3186 _ReleaseBuffer
+31AA _numError02
+31B0 _SetupEditCmd
+31B6 _CursorToOffset
+31BC _RstGFlags
+31C2 _RclVarToEdit
+31C8 _BufInsert
+31CE _BufToTop
+31DA _IsEditEmpty
+31E6 _DisarmScroll
+31EC _SetEmptyEditPtr
+31F2 _FormDisp
+3216 _JCursorRight
+321C _JCursorUp
+3228 _JCursorFirst
+322E _JCursorLast
+323A _BufDelete
+3258 _RclEntryToEdit
+325E _InsDisp
+3270 _RclVarToEditPtr
+3276 _FormScrollUp
+327C _FormMatrix
+3294 _FormReal
+32D4 _DispTail
+3312 _RclX
+3318 _SaveParse
+331E _GetParse
+3330 _CpyTo2ES1
+3336 _CpyTo6ES1
+333C _CpyTo1ES1
+3342 _CpyTo3ES1
+3348 _CpyTo3ES2
+334E _CpyTo2ES2
+3354 _CpyTo1ES2
+335A _CpyTo2ES3
+3360 _CpyTo1ES3
+3366 _CpyTo3ES4
+336C _CpyTo6ES4
+3372 _CpyTo2ES4
+3378 _CpyTo1ES4
+337E _CpyTo2ES5
+3384 _CpyTo1ES5
+338A _CpyTo4EST
+3390 _CpyTo2EST
+3396 _CpyTo1EST
+339C _CpyTo2ES6
+33A2 _CpyTo1ES6
+33A8 _CpyTo2ES7
+33AE _CpyTo1ES7
+33B4 _CpyTo2ES8
+33BA _CpyTo1ES8
+33C0 _CpyTo1ES9
+33C6 _CpyTo2ES9
+33CC _CpyTo2ES10
+33D2 _CpyTo1ES10
+33D8 _CpyES10
+33DE _CpyTo2ES11
+33E4 _CpyTo1ES11
+33EA _CpyTo2ES12
+33F0 _CpyTo1ES12
+33F6 _CpyTo2ES13
+33FC _CpyTo1ES13
+3402 _CpyTo1ES14
+3408 _CpyTo1ES16
+340E _CpyTo1ES17
+3414 _CpyTo1ES18
+341A _CpyTo1ES15
+3420 _CpyTo2ES15
+3426 _CpyO1ToEST
+342C _CpyO1ToES1
+3432 _CpyO6ToES1
+3438 _CpyO6ToES4
+343E _CpyO1ToES2
+3444 _CpyO2ToES2
+344A _CpyO1ToES3
+3450 _CpyO1ToES4
+3456 _CpyO1ToES5
+345C _CpyO1ToES6
+3462 _CpyO1ToES7
+3468 _CpyO2ToES1
+346E _CpyO2ToES4
+3474 _CpyO2ToES7
+347A _CpyO1ToES8
+3480 _CpyO1ToES9
+3486 _CpyO1ToES10
+348C _CpyO1ToES11
+3492 _CpyO1ToES12
+3498 _CpyO1ToES13
+349E _CpyO1ToES14
+34A4 _CpyO1ToES15
+34AA _PixelTest
+# 34BC _DFMIN2 ?
+3504 _RestoreErrNo
+3516 _StoN
+351C _ParseOff
+3522 _RclN
+354C _Random
+3552 _StoRand
+3558 _GetL3TOP1A
+355E _GetL1ToOP1
+3564 _GetL1TOP1A
+356A _GetLToOP1
+3570 _GetLToOP2
+3576 _GetL2TOP1A
+3582 _PutToLA1
+3588 _PutToL
+35A0 _ConvOP1
+35AC _GetM1ToOP1A
+35B2 _GetMToOP1
+35B8 _PutToM1A
+35BE _PutToMA1
+35C4 _PutToMat
+35D0 _AdrMEle
+35D6 _AdrLEle
+35DC _RclVarSym
+35E2 _StoOther
+35E8 _RedimMat
+35EE _IncLstSize
+35F4 _InsertList
+35FA _DelListEl
+3600 _EditProg
+3606 _CloseProg
+360C _ClrGraphRef
+3612 _FixTempCnt
+3618 _SaveData
+361E _RestoreData
+3624 _CleanAll
+362A _MoveToNextSym
+3630 _DispErrorScreen
+3660 _GrRedisp
+3690 _FindAlphaUp
+3696 _FindAlphaDn
+369C _RestoreDisp
+36A2 _LoadMenuNum
+36A8 _LoadMenuNumL
+36AE _RstrSmallText
+36B4 _ConvKeyToTok
+36BA _GrLabels
+36CC _NBCursor
+36D2 _IsEditFull
+36D8 _IsAtBtm
+36DE _ShowCursor
+36E4 _BufLeft
+36EA _BufPeek
+36F0 _BufRight
+36F6 _FDispBOL
+36FC _FDispBOL1
+3702 _SetWinAbove
+3708 _GetPrevTok
+370E _StringWidth
+371A _DispEOW
+3720 _BufPeek1
+372C _BufPeek3
+3732 _ReadDisp2
+3738 _PutMap
+373E _PutPS
+3744 _WPutPS
+374A _PutBuf
+3750 _PutBuf1
+3756 _WPutC
+375C _WPutS
+3762 _WPutSEOL
+3768 _WHomeUp
+376E _SetNumWindow
+377A _NewLine
+3780 _MoveDown
+3786 _ScrollUp
+3792 _ShrinkWindow
+3798 _MoveUp
+379E _ScrollDown
+37A4 _ClrScrnFull
+37AA _ClrTxtShd
+37B6 _EraseEOL
+37C8 _GetCurLoc
+37CE _VPutMap
+37D4 _VPutS
+37DA _VPutSNG
+37E0 _SaveCmdShadow
+37EC _SaveShadow
+37F8 _RstrShadow
+37FE _RstrCurRow
+3804 _RstrUnderMenu
+380A _RstrBotRow
+3810 _SaveTR
+3816 _RestoreTR
+381C _GetTokLen
+3822 _GetTokString
+3828 _PUTBPATBUF2
+382E _PUTBPATBUF
+3834 _putbPAT
+383A _putcCheckScroll
+3840 _DispEOL
+3846 _FDispEOL
+384C _TblScreenDn
+3852 _TblScreenUp
+3858 _TOTOSTRP
+385E _SetVarName
+386A _DispDone
+3870 _FinishOutput
+3876 _ClrLCD
+387C _DispHL
+3894 _CreateTemp
+389A _ClrLCDFull
+38A0 _LoadNoEEntry
+38AC _GrBufCpy
+38BE _AbortPrgmode
+38CA _ClrScrn
+38E2 _StoAns
+38E8 _GrReset
+38F4 _RandInit
+38FA _PutS
+3900 _HomeUp
+3912 _CoorDisp
+3918 _RunIndicOff
+391E _CursorOn
+3924 _GetKey
+392A _BlinkGCur
+3930 _RunIndicOn
+# 3936 _PullDownChk ?
+393C _TokToKey
+3942 _GrPutAway
+3948 _PopCx
+39C6 _SaveOScreen
+39CC _CurBlink
+39D2 _PutC
+
+## note: following ROM calls are not correctly handled by
+## SNG on ROM 19.006
+
+31E0 _CloseEditEqu
+3246 _DispHead
+324C _BufClear
+32BE _Send1BErr
+# 32C4
+# 32CA
+# 32D0
+# 32D6
+32DC _KeyScnLnk
+37B0 _ClrWindow
+37C2 _EraseEOW
+38B8 _RstrPartial
+38C4 _HideCursor
+38D6 _LCD_DriverOn
+38DC _RstrOScreen
+390C _CursorOff
+
+## note: following ROM calls are not correctly handled by
+## SNG on ROM 19.0
+
+050C _AppInit
+0684 _initialize
+07F3 _LCD_BUSY
+0801 _Min
+080A _Max
+0818 _AbsO1PAbsO2
+0820 _Intgr
+0836 _Trunc
+083A _InvSub
+083F _Times2
+0842 _Plus1
+0847 _Minus1
+084A _FPSub
+0851 _FPAdd
+0905 _DToR
+090E _RToD
+0917 _Cube
+091C _TimesPt5
+0924 _FPSquare
+0925 _FPMult
+
+## note: following ROM calls are not correctly handled by
+## any current shells on ROM 19.0
+
+0A1B _LJRnd
+0A5D _InvOP1S
+0A6D _InvOP2S
+0A83 _Frac
+0AD0 _FPRecip
+0AD4 _FPDiv
+0BA1 _SqRoot
+0C69 _Int
+0C6B _Round
+0C4F _RndGuard
+# 0CDF _Factorial ?
+0D27 _LnX
+0D2B _LogX
+0E63 _LJNoRnd
+0F03 _EToX
+0F0D _TenX
+
+8000 kbdScanCode
+8001 kbdLGSC
+8002 kbdPSC
+8003 kbdWUR
+8004 kbdDebncCnt
+8005 kbdKey
+8006 kbdGetKy
+8007 keyExtend
+8008 contrast
+8009 apdSubTimer
+800A apdTimer
+800B curTime
+800C curRow
+800D curCol
+800E curUnder
+800F curY
+# 8010 curType ?
+8011 curXRow
+# 801A tokVarPtr ?
+# 801C tokLen ?
+801E indicMem
+8026 indicCounter
+8027 indicBusy
+8028 OP1
+8033 OP2
+803E OP3
+8049 OP4
+8054 OP5
+805F OP6
+806A progToEdit
+8072 nameBuff
+807D iMathPtr1
+807F iMathPtr2
+8081 iMathPtr3
+8083 iMathPtr4
+8085 iMathPtr5
+8087 chkDelPtr1
+8089 chkDelPtr2
+808B insDelPtr
+808D upDownPtr
+808F textShadow
+810F textShadCur
+8111 textShadTop
+8112 textShadAlph
+8113 textShadIns
+8114 cxMain
+8116 cxPPutAway
+8118 cxPutAway
+811A cxRedisp
+811C cxErrorEP
+811E cxSizeWind
+8120 cxPage
+8121 cxCurApp
+8122 cxPrev
+8131 monQH
+8132 monQT
+# 8133 monQueue ?
+8143 onSP
+8145 onCheckSum
+8161 menuActive
+8163 menuCurrent
+81F0 ioFlag
+81F2 sndRecState
+81F3 ioErrState
+81F4 header
+81FD ioData
+8209 bakHeader
+8215 penCol
+8216 penRow
+8217 rclQueue
+8219 errNo
+821A errSP
+821C errOffset
+8228 saveSScreen
+8528 flags
+8549 curGStyle
+854A curGY
+854B curGX
+854C curGY2
+854D curGX2
+# 8551 XOffset ?
+# 8552 YOffset ?
+8553 lcdTallP
+8554 pixWideP
+8557 lastEntryStk
+85D7 numLastEntries
+85D8 currLastEntry
+# 85E1 ORGXMIN ?
+8691 Xmin
+86AC Ymin
+874E deltaX
+8757 deltaY
+8760 shortX
+8769 shortY
+8774 XOutDat
+8778 YOutDat
+# 877A inputSym ?
+877C inputDat
+88AE ES
+88B8 plotSScreen
+8BB8 seed1
+8BC1 seed2
+8BCA parseVar
+8BD3 begPC
+8BD5 curPC
+8BD7 endPC
+8BDF cmdShadow
+8C5F cmdShadCur
+8C65 editTop
+8C67 editCursor
+8C69 editTail
+8C6B editBtm
+8C71 editSym
+# 8C73 editDat ?
+8C8D modePtr
+8C8F winTop
+8C90 winBtm
+8C91 winLeftEdge
+8C92 winLeft
+8C94 winAbove
+8C96 winRow
+8C98 winCol
+8C9A fmtDigits
+8CF2 fmtMatSym
+8CF4 fmtMatMem
+# 8CF6 EQS ?
+8CFF delAdjAmt
+8D06 chkDelPtr3
+8D08 chkDelPtr4
+8D0A tempMem
+8D0C fpBase
+8D0E FPS
+8D10 OPBase
+8D12 OPS
+8D14 pTempCnt
+8D16 cleanTmp
+8D18 pTemp
+8D24 userMem
+FE6E symTable
+
+[flags]
+00    kbdFlags
+2,00  trigDeg,trigFlags
+3,00  kbdSCR,kbdFlags
+4,00  kbdKeyPress,kbdFlags
+01    editFlags
+2,01  editOpen,editFlags
+02    plotFlags
+1,02  plotLoc,plotFlags
+2,02  plotDisp,plotFlags
+4,02  grfFuncM,grfModeFlags
+5,02  grfPolarM,grfModeFlags
+6,02  grfParamM,grfModeFlags
+7,02  grfRecurM,grfModeFlags
+03    graphFlags
+0,03  graphDraw,graphFlags
+2,03  graphCursor,graphFlags
+04    grfDBFlags
+5,04  grfNoAxis,grfDBFlags
+5,04  grfLabel,grfDBFlags
+05    textFlags
+1,05  textEraseBelow,textFlags
+2,05  textScrolled,textFlags
+3,05  textInverse,textFlags
+4,05  textInsMode,textFlags
+08    apdFlags
+2,08  apdAble,apdFlags
+3,08  apdRunning,apdFlags
+09    onFlags
+3,09  onRunning,onFlags
+4,09  onInterrupt,onFlags
+0C    curFlags
+2,0C  curAble,curFlags
+3,0C  curOn,curFlags
+4,0C  curLock,curFlags
+0D    appFlags
+1,0D  appTextSave,appFlags
+2,0D  appAutoScroll,appFlags
+5,0D  appCurGraphic,appFlags
+6,0D  appCurWord,appFlags
+11    promptFlags
+0,11  promptEdit,promptFlags
+12    indicFlags
+0,12  indicRun,indicFlags
+1,12  indicInUse,indicFlags
+2,12  indicOnly,indicFlags
+3,12  shift2nd,shiftFlags
+4,12  shiftAlpha,shiftFlags
+5,12  shiftLwrAlph,shiftFlags
+6,12  shiftALock,shiftFlags
+7,12  shiftKeepAlph,shiftFlags
+13    tblFlags
+6,13  reTable,tblFlags
+14    sGrFlags
+0,14  grfSplit,sGrFlags
+1,14  grfSChanged,sGrFlags
+2,14  grfSplitOverride,sGrFlags
+7,14  textWrite,sGrFlags
+15    newIndicFlags
+1,15  saIndic,newIndicFlags
diff --git a/tool/tilem-src/data/symbols/ti83.sym b/tool/tilem-src/data/symbols/ti83.sym
new file mode 100644
index 0000000..70df089
--- /dev/null
+++ b/tool/tilem-src/data/symbols/ti83.sym
@@ -0,0 +1,1307 @@
+[macros]
+# Nothing currently 
+
+[labels]
+0008 rOP1TOOP2
+0010 rFINDSYM
+0018 rPUSHREALO1
+0020 rMOV9TOOP1
+0028 rFPMULT
+0030 rFPADD
+
+8000 kbdScanCode
+8001 kbdLGSC
+8002 kbdPSC
+8003 kbdWUR
+8004 kbdDebncCnt
+8005 kbdKey
+8006 kbdGetKy
+8007 keyExtend
+8008 contrast
+8009 apdSubTimer
+800a apdTimer
+800b curTime
+800c curRow 
+800d curCol 
+800e curOffset
+800f curUnder
+8010 curY
+8012 curXRow
+801a lFont_record
+8022 sFont_record
+802f indicMem
+8037 indicCounter
+8038 indicBusy
+8039 OP1
+8044 OP2
+804f OP3
+805a OP4
+8065 OP5
+8070 OP6
+8080 progToEdit
+8088 nameBuff
+8094 iMathPtr1
+8096 iMathPtr2
+8098 iMathPtr3
+809a iMathPtr4
+809c iMathPtr5
+809e chkDelPtr1
+80a0 chkDelPtr2
+80a2 insDelPtr
+80a4 upDownPtr
+80ac asm_data_ptr1
+80ae asm_data_ptr2
+80c8 asm_ind_call
+80c9 textShadow
+8149 textShadCur
+814b textShadTop
+814c textShadAlph
+814d textShadIns
+814e cxMain
+8150 cxPPutAway
+8152 cxPutAway
+8154 cxRedisp
+8156 cxErrorEP
+8158 cxSizeWind
+815a cxPage
+815b cxCurApp
+815c cxPrev
+817d onSP
+817f onCheckSum
+819d menuActive
+819f menuCurrent
+822d ioFlag
+822f sndRecState
+8230 ioErrState
+8231 header
+823a ioData
+8246 bakHeader
+8252 penCol
+8253 penRow
+8254 rclQueue
+8256 errNo
+8257 errSP
+8259 errOffset
+8265 saveSScreen
+8565 usermem_offset
+8567 flags
+858f statVars
+886c curGStyle
+886d curGY
+886e curGX
+886f curGY2
+8870 curGX2
+8871 freeSaveY
+8872 freeSaveX
+88F6 XOffset
+88F7 YOffset
+88F8 lcdTallP
+88F9 pixWideP
+88FA pixWide_m_1
+88FB pixWide_m_2
+88FE lastEntryStk
+897E numLastEntries
+897F currLastEntry
+8AA5 Xmin
+8AB7 Xscl
+8AC0 Ymin
+8AD2 Yscl
+8C3B deltaX
+8C44 deltaY
+8C4D shortX
+8C56 shortY
+8C77 XOutDat
+8C7B YOutDat
+8C7F inputDat
+8DEB ES
+8e29 plotSScreen
+913B parseVar
+9144 begPC
+9146 curPC
+9148 endPC
+9157 cmdShadow
+91D7 cmdShadCur
+91DB cmdCursor
+91DD editTop
+91DF editCursor
+91E1 editTail
+91E3 editBtm
+91EF editSym
+91F1 editDat
+928C modePtr
+928E winTop
+928F winBtm
+9290 winLeftEdge
+9291 winLeft
+9293 winAbove
+9295 winRow
+9297 winCol
+92F1 fmtMatSym
+92F3 fmtMatMem
+92F5 EQS
+92FE delAdjAmt
+9305 chkDelPtr3
+9307 chkDelPtr4
+9309 tempMem
+930B fpBase
+930D FPS
+930F OPBase
+9311 OPS
+9313 pTempCnt
+9315 cleanTmp
+9317 pTemp
+9319 progPtr
+9327 userMem
+0fe6e symTable
+
+# System routines (page C)
+4000 _ldHLind
+4004 _cphlde
+4008 _divHLBy10
+400c _divHLbyA
+4010 _kbdScan
+4014 _getcsc
+4018 _coorMon
+401b _mon
+401e _monForceKey
+4021 _sendKPress
+4024 _JforceCmdNoChaR
+4027 _JforceCmd
+402a _sysErrHandler
+402e _newContext
+4032 _newContext0
+4036 _A2PointHLind
+403a _PointHLind
+403e _PPutAwayPrompt
+4042 _PPutAway
+4046 _PutAway
+404a _SizeWind
+404e _ErrorEP
+4052 _callMain
+4056 _monErrHand
+405a _appInit
+405e _appInitIfDec
+4062 _Initialize
+4066 _lcd_busy
+406a _Min
+406e _Max
+4072 _AbsO1PAbsO2
+4076 _Intgr
+407a _Trunc
+407e _InvSub
+4082 _Times2
+4086 _Plus1
+408a _Minus1
+408e _FPSub
+4092 _FPAdd
+4096 _DToR
+409a _RToD
+409e _Cube
+40a2 _TimesPt5
+40a6 _FPSquare
+40aa _FPMult
+40ae _LJRnd
+40b2 _InvOP1SC
+40b6 _InvOP1S
+40ba _InvOP2S
+40be _Frac
+40c2 _FPRecip
+40c6 _FPDiv
+40ca _SqRoot
+40ce _RndGuard
+40d2 _RnFx
+40d6 _Int
+40da _Round
+40de _LnX
+40e2 _LogX
+40e6 _LJNoRnd
+40ea _EToX
+40ee _TenX
+40f2 _SinCosRad
+40f6 _Sin
+40fa _Cos
+40fe _Tan
+4102 _SinHCosH
+4106 _TanH
+410a _CosH
+410e _SinH
+4112 _ACosRad
+4116 _ATanRad
+411a _ATan2Rad
+411e _ASinRad
+4122 _ACos
+4126 _ATan
+412a _ASin
+412e _ATan2
+4132 _ATanH
+4136 _ASinH
+413a _ACosH
+413e _PToR
+4142 _RToP
+4146 _HLTimes9
+414a _CkOP1Cplx
+414e _CkOP1Real
+4152 _Angle
+4156 _COP1Set0
+415a _CpOP4OP3
+415e _Mov9OP2Cp
+4162 _AbsO1O2Cp
+4166 _CpOP1OP2
+416a _OP3ToOP4
+416e _OP1ToOP4
+4172 _OP2ToOP4
+4176 _OP4ToOP2
+417a _OP3ToOP2
+417e _OP1ToOP3
+4182 _OP5ToOP2
+4186 _OP5ToOP6
+418a _OP5ToOP4
+418e _OP1ToOP2
+4192 _OP6ToOP2
+4196 _OP6ToOP1
+419a _OP4ToOP1
+419e _OP5ToOP1
+41a2 _OP3ToOP1
+41a6 _OP6ToOP5
+41aa _OP4ToOP5
+41ae _OP3ToOP5
+41b2 _OP2ToOP5
+41b6 _OP2ToOP6
+41ba _OP1ToOP6
+41be _OP1ToOP5
+41c2 _OP2ToOP1
+41c6 _MovREG
+41ca _Mov10B
+41ce _Mov9B
+41d2 _Mov18
+41d6 _Mov8B
+41da _Mov7B
+41de _Mov14
+41e2 _Mov6B
+41e6 _Mov5B
+41ea _Mov4B
+41ee _Mov3B
+41f2 _Mov2B
+41f6 _OP2ToOP3
+41fa _OP4ToOP3
+41fe _OP5ToOP3
+4202 _OP4ToOP6
+4206 _Mov9ToOP1
+420a _Mov9OP1OP2
+420e _Mov9ToOP2
+4212 _MovFrOP1
+4216 _OP4Set1
+421a _OP3Set1
+421e _OP2Set8
+4222 _OP2Set5
+4226 _OP2SetA
+422a _OP2Set4
+422e _OP2Set3
+4232 _OP1Set1
+4236 _OP1Set4
+423a _OP1Set3
+423e _OP3Set2
+4242 _OP1Set2
+4246 _OP2Set2
+424a _SetNum2
+424e _SetMant1
+4252 _OP2Set1
+4256 _SetNum1
+425a _SetNum
+425e _SetNumA
+4262 _SetMant
+4266 _Zero16D
+426a _Set16A
+426e _Set14A
+4272 _Set14D
+4276 _OP5Set0
+427a _OP4Set0
+427e _OP3Set0
+4282 _OP2Set0
+4286 _OP1Set0
+428a _SetNum0
+428e _ZeroOP1
+4292 _ZeroOP2
+4296 _ZeroOP3
+429a _ZeroOP
+429e _ClrLp
+42a2 _ShRAcc
+42a6 _ShLAcc
+42aa _ShR18
+42ae _ShR18A
+42b2 _ShR16
+42b6 _ShR14
+42ba _ShL16
+42be _ShL14
+42c2 _SRDO1
+42c6 _SHRDRND
+42ca _MANTPA
+42ce _ADDPROP
+42d2 _ADDPROPLP
+42d6 _Add16D
+42da _Add14D
+42de _Sub16D
+42e2 _Sub14D
+42e6 _OP2ExOP6
+42ea _OP5ExOP6
+42ee _OP1ExOP5
+42f2 _OP1ExOP6
+42f6 _OP2ExOP4
+42fa _OP2ExOP5
+42fe _OP1ExOP3
+4302 _OP1ExOP4
+4306 _OP1ExOP2
+430a _ExLp
+430e _CkOP1C0
+4312 _CkOP1FP0
+4316 _CkOP2FP0
+431a _PosNo0Int
+431e _CkPosInt
+4322 _CkInt
+4326 _CkOdd
+432a _CkOP1M
+432e _GetCon1
+4332 _GetCon
+4336 _PIDIV2
+433a _PIDIV4
+433e _TWOPI
+4342 _PICON
+4346 _PIDIV4A
+434a _PIDIV2A
+434e _ExpToHex
+4352 _OP1ExpToDec
+4356 _CkOP2Pos
+435a _CkOP1Pos
+435e _ClrOP2S
+4362 _ClrOP1S
+4366 _FDiv100
+436a _FDiv10
+436e _DecO1Exp
+4372 _IncO1Exp
+4376 _IncExp
+437a _CkValidNum
+437e _GetExp
+4382 _HTimesL
+4386 _EOP1NotReal
+438a _ThetaName
+438e _RName
+4392 _RegEqName
+4396 _RecurNName
+439a _XName
+439e _YName
+43a2 _TName
+43a6 _RealName
+43aa _SETesTOfps
+43ae _ChkTempDirt
+43b2 _OP1MOP2Exp
+43b6 _OP1EXPMDE
+43ba _ChkErrBreak
+43be _IsA2ByteTok
+43c2 _GetLastEntry
+43c6 _GetLastEntryPtr
+43ca _REGRCLRCHNG
+43ce _ResetWinTop
+43d2 _SetYUp
+43d6 _SetXUp
+43da _ISO1NONTLSTorPROG
+43de _ISO1NONTEMPLST
+43e2 _IS_A_LSTorCLST
+43e6 _Chk_HL_999
+43ea _EQU_or_NEWEQU
+43ee _GET_PLUS1_SAVE
+43f2 _ErrD_OP1NotPos
+43f6 _ErrD_OP1Not_R
+43fa _ErrD_OP1NotPosInt
+43fe _ErrD_OP1_LE_0
+4402 _ErrD_OP1_0
+4406 _Findsym_Get_Size
+440a _Sto_StatVar
+440e _Rcl_StatVar
+4412 _CkOP2Real
+4416 _Get_X_Indirect
+441a _MemChk
+441e _CmpPrgNamLen1
+4422 _CmpPrgNamLen
+4426 _FindProgSym
+442a _ChkFindSym
+442e _FindSym
+4432 _InsertMem
+4436 _InsertMemA
+443a _EnoughMem
+443e _CmpMemNeed
+4442 _CREATEPVAR4
+4446 _CREATEPVAR3
+444a _CREATEVAR3
+444e _CreateCplx
+4452 _CreateReal
+4456 _CreateTRList
+445a _CreateRList
+445e _CreateTCList
+4462 _CreateCList
+4466 _CreateTRMat
+446a _CreateRMat
+446e _CreateTStrng
+4472 _CreateStrng
+4476 _Create0Equ
+447a _CreateTEqu
+447e _CreateEqu
+4482 _CreatePict
+4486 _CreateGDB
+448a _CreateProg
+448e _ChkDel
+4492 _ChkDelA
+4496 _AdjParser
+449a _AdjMath
+449e _AdjM7
+44a2 _DelMemA
+44a6 _Get_Form_Num
+44aa _DelVar
+44ae _DelVarIO
+44b2 _DelMem
+44b6 _DelVar3D
+44ba _DelVar3C
+44be _DelVar3DC
+44c2 _Sym_Prog_Non_T_Lst
+44c6 _AdjSymPtrs
+44ca _DataSizeA
+44ce _DataSize
+44d2 _PopMCplxO1
+44d6 _PopMCplx
+44da _MovCplx
+44de _PopOP5
+44e2 _PopOP3
+44e6 _PopOP1
+44ea _PopRealO6
+44ee _PopRealO5
+44f2 _PopRealO4
+44f6 _PopRealO3
+44fa _PopRealO2
+44fe _PopRealO1
+4502 _PopReal
+4506 _FPopCplx
+450a _FPopReal
+450e _FPopFPS
+4512 _DeallocFPS
+4516 _DeallocFPS1
+451a _AllocFPS
+451e _AllocFPS1
+4522 _PushRealO6
+4526 _PushRealO5
+452a _PushRealO4
+452e _PushRealO3
+4532 _PushRealO2
+4536 _PushRealO1
+453a _PushReal
+453e _PushOP5
+4542 _PushOP3
+4546 _PushMCplxO3
+454a _PushOP1
+454e _PushMCplxO1
+4552 _PushMCplx
+4556 _ExMCplxO1
+455a _Exch9
+455e _CpyTo1FPS11
+4562 _CpyTo2FPS5
+4566 _CpyTo1FPS5
+456a _CpyTo2FPS6
+456e _CpyTo1FPS6
+4572 _CpyTo2FPS7
+4576 _CpyTo1FPS7
+457a _CpyTo1FPS8
+457e _CpyTo2FPS8
+4582 _CpyTo1FPS10
+4586 _CpyTo1FPS9
+458a _CpyTo2FPS4
+458e _CpyTo6FPS3
+4592 _CpyTo6FPS2
+4596 _CpyTo2FPS3
+459a _CpyCTo1FPS3
+459e _CpyTo1FPS3
+45a2 _CpyFPS3
+45a6 _CpyTo1FPS4
+45aa _CpyTo3FPS2
+45ae _CpyTo5FPST
+45b2 _CpyTo6FPST
+45b6 _CpyTo4FPST
+45ba _CpyTo3FPST
+45be _CpyTo2FPST
+45c2 _CpyTo1FPST
+45c6 _CpyFPST
+45ca _CpySTACK
+45ce _CpyTo3FPS1
+45d2 _CpyTo2FPS1
+45d6 _CpyTo1FPS1
+45da _CpyFPS1
+45de _CpyTo2FPS2
+45e2 _CpyTo1FPS2
+45e6 _CpyFPS2
+45ea _CpyO3ToFPST
+45ee _CpyO2ToFPST
+45f2 _CpyO6ToFPST
+45f6 _CpyO1ToFPST
+45fa _CpyToFPST
+45fe _CpyToSTACK
+4602 _CpyO3ToFPS1
+4606 _CpyO5ToFPS1
+460a _CpyO2ToFPS1
+460e _CpyO1ToFPS1
+4612 _CpyToFPS1
+4616 _CpyO2ToFPS2
+461a _CpyO3ToFPS2
+461e _CpyO6ToFPS2
+4622 _CpyO1ToFPS2
+4626 _CpyToFPS2
+462a _CpyO5ToFPS3
+462e _CpyO2ToFPS3
+4632 _CpyO1ToFPS3
+4636 _CpyToFPS3
+463a _CpyO1ToFPS6
+463e _CpyO1ToFPS7
+4642 _CpyO1ToFPS5
+4646 _CpyO2ToFPS4
+464a _CpyO1ToFPS4
+464e _ErrNotEnoughMem
+4652 _FPSMinus9
+4656 _HLMinus9
+465a _ErrOverflow
+465d _ErrDivBy0
+4660 _ErrSingularMat
+4663 _ErrDomain
+4666 _ErrIncrement
+4669 _ErrNon_Real
+466c _ErrSyntax
+466f _ErrDataType
+4672 _ErrArgument
+4675 _ErrDimMismatch
+4678 _ErrDimension
+467b _ErrUndefined
+467e _ErrMemory
+4681 _ErrInvalid
+4684 _ErrBreak
+4687 _ErrStat
+468a _ErrSignChange
+468d _ErrIterations
+4690 _ErrBadGuess
+4693 _ErrTolTooSmall
+4696 _ErrStatPlot
+4699 _ErrLinkXmit
+469c _JError
+469f _JErrorNo
+46a2 _noErrorEntry
+46a5 _pushErrorHandlER
+46a9 _popErrorHandleR
+46ad _strCopy
+46b1 _strCat
+46b5 _isInSet
+46b9 _sDone
+46bd _serrort
+46c1 _sNameEq
+46c5 _sUnderScr
+46c9 _sFAIL
+46cd _sName
+46d1 _sOK
+46d5 _seqn
+46d9 _Sselect
+46dd _STransmit
+46e1 _SRECURN
+46e5 _GEQNAMEA
+46e9 _RECNAME
+46ed ___bank_call
+46f1 ___bank_ret
+46f5 ___bank_jump
+46f9 ___bank_entry
+46fd _ReadDisp2
+4701 _putmap
+4705 _putc
+4709 _dispHL
+470d _puts
+4711 _putpsb
+4715 _putps
+4719 _wputps
+471d _putbuf
+4721 _putbuf1
+4725 _wputc
+4729 _wputs
+472d _wputsEOL
+4731 _wdispEOL
+4735 _whomeUp
+4739 _setNumWindow
+473d _newline
+4741 _moveDown
+4745 _scrollUp
+4749 _shrinkWindow
+474d _moveUp
+4751 _scrollDown
+4755 _clrLCDFull
+4759 _clrLCD
+475d _clrScrnFull
+4761 _clrScrn
+4765 _clrTxtShd
+4769 _clrWindow
+476d _eraseEOL
+4771 _eraseEOW
+4775 _homeUp
+4779 _getcurloc
+477d _vputmap
+4781 _vputs
+4785 _vputsn
+4789 _vputsnG
+478d _vputsnT
+4791 _runIndicOn
+4795 _runIndicOff
+4799 _saveCmdShadow
+479d _saveShadow
+47a1 _rstrShadow
+47a5 _rstrpartial
+47a9 _rstrCurRow
+47ad _rstrUnderMenu
+47b1 _rstrbotrow
+47b5 _saveTR
+47b9 _restoreTR
+47bd _GetKeyPress
+47c1 _GetTokLen
+47c5 _Get_Tok_Strng
+47c9 _GetTokString
+47cd _PUTBPATBUF2
+47d1 _PUTBPATBUF
+47d5 _putbPAT
+47d9 _putcCheckScrolL
+47dd _dispEOL
+47e1 _fdispEOL
+47e5 _MAKEROWCMD
+47e9 _TOTOSTRP
+47ed _SETVARNAME
+47f1 _dispDone
+47f5 _finishoutput
+47f9 _curBlink
+47fd _cursorOff
+4801 _hideCursor
+4805 _cursorOn
+4809 _showCursor
+480d _KeyToString
+4811 _PullDownChk
+4815 _MenuCatCommon
+4819 _ZIfCatalog
+481d _ZIfMatrixMenu
+4821 _loadCurCat
+4825 _NCifprgmedmode
+4829 _LoadMenuNum
+482d _LoadMenuNumL
+4831 _MenuEdKey
+4835 _MenCatRet
+4839 _notalphnum
+483d _SaveSavedFlags
+4841 _SetMenuFlags
+4845 _RstrSomeFlags
+4849 _RstrSmallText
+484d _dispListName
+4851 _SeeIfErrorCx
+4855 _RstrOScreen
+4859 _SaveOscreen
+485d _DispLAlphaName
+4861 _AbortPrgmode
+4865 _Is_FullCntx
+4869 _AdrMRow
+486d _AdrMEle
+4871 _GetMatOP1A
+4875 _GetM1ToOP1
+4879 _GetM1TOP1A
+487d _GetMToOP1
+4881 _PutToM1A
+4885 _PutToMA1
+4889 _PutToMat
+488d _Mat_El_Div
+4891 _CMatFun
+4895 _RowEch_Poly
+4899 _RowEchelon
+489d _AdrLEle
+48a1 _GetL1ToOP1
+48a5 _GetL1TOP1A
+48a9 _GetLToOP1
+48ad _GetL1ToOP2
+48b1 _GetL1TOP2A
+48b5 _GetL2TOP1A
+48b9 _PutToLA1
+48bd _PutToL
+48c1 _MaxMinLst
+48c5 _LLow
+48c9 _LHigh
+48cd _LSum
+48d1 _CumSum
+48d5 _ToFrac
+48d9 _SeqSet
+48dd _SeqSolve
+48e1 _Cmp_Num_Init
+48e5 _BinOpExec
+48e9 _EXMEAN1
+48ed _SET2MVLPTRS
+48f1 _SETMAT1
+48f5 _CreateTList
+48f9 _UnOpExec
+48fd _ThreeExec
+4901 _RestoreErrNo
+4904 _FourExec
+4908 _FiveExec
+490c _CpyTo2ES1
+4910 _CpyTo6ES1
+4914 _CpyTo1ES1
+4918 _CpyTo3ES1
+491c _CpyTo3ES2
+4920 _CpyTo2ES2
+4924 _CpyTo1ES2
+4928 _CpyTo2ES3
+492c _CpyTo1ES3
+4930 _CpyTo3ES4
+4934 _CpyTo6ES3
+4938 _CpyTo2ES4
+493c _CpyTo1ES4
+4940 _CpyTo2ES5
+4944 _CpyTo1ES5
+4948 _CpyTo4EST
+494c _CpyTo2EST
+4950 _CpyTo1EST
+4954 _CpyTo2ES6
+4958 _CpyTo1ES6
+495c _CpyTo2ES7
+4960 _CpyTo1ES7
+4964 _CpyTo2ES8
+4968 _CpyTo1ES8
+496c _CpyTo1ES9
+4970 _CpyTo2ES9
+4974 _CpyTo2ES10
+4978 _CpyTo1ES10
+497c _CpyTo2ES11
+4980 _CpyTo1ES11
+4984 _CpyTo2ES12
+4988 _CpyTo1ES12
+498c _CpyTo2ES13
+4990 _CpyTo1ES13
+4994 _CpyTo1ES14
+4998 _CpyTo1ES16
+499c _CpyTo1ES17
+49a0 _CpyTo1ES18
+49a4 _CpyTo1ES15
+49a8 _CpyTo2ES15
+49ac _CpyO1ToEST
+49b0 _CpyO1ToES1
+49b4 _CpyO6ToES1
+49b8 _CpyO6ToES3
+49bc _CpyO1ToES2
+49c0 _CpyO2ToES2
+49c4 _CpyO1ToES3
+49c8 _CpyO1ToES4
+49cc _CpyO1ToES5
+49d0 _CpyO1ToES6
+49d4 _CpyO1ToES7
+49d8 _CpyO2ToES4
+49dc _CpyO2ToES5
+49e0 _CpyO2ToES6
+49e4 _CpyO2ToES7
+49e8 _CpyO2ToES8
+49ec _CpyO2ToES9
+49f0 _CpyO1ToES8
+49f4 _CpyO1ToES9
+49f8 _CpyO1ToES10
+49fc _CpyO1ToES11
+4a00 _CpyO1ToES12
+4a04 _CpyO1ToES13
+4a08 _CpyO1ToES14
+4a0c _CpyO1ToES15
+4a10 _EVALF3A
+4a14 COMPLEX_EXEC
+4a18 _GetK
+4a1c _setTitlE
+4a20 _dispVarVal
+4a24 _RecallEd
+4a28 _setupBuffer
+4a2c _createNumEditBUF
+4a30 _CallCommon
+4a34 _CommonKeys
+4a37 _Leftmore
+4a3a _fDel
+4a3e _fClear
+4a42 _finsDisp
+4a45 _FinsDisp02
+4a49 _setIndicator
+4a4d _CloseEditBufNo
+4a51 _releaseBuffer
+4a55 _varnameToOP1hl
+4a59 _nameToOP1
+4a5d _numPPutAway
+4a61 _numRedisp
+4a65 _numError02
+4a68 _Load_SFont
+4a6c _SFont_Len
+4a70 _InitNumVec
+4a74 _SetXXOP1
+4a78 _SetXXOP2
+4a7c _SetXXXXOP2
+4a80 _UCLineS
+4a84 _CLine
+4a88 _CLineS
+4a8c _XRooty
+4a90 _YToX
+4a94 _ZmStats
+4a98 _POINT_STAT_HLP
+4a9c _DRAWSPLOT
+4aa0 _INITNEWTRACEP
+4aa4 _SPLOTCOORD
+4aa8 _SPLOTRIGHT
+4aac _SPLOTLEFT
+4ab0 _CMPBOXINFO
+4ab4 _NEXTPLOT
+4ab8 _PREVPLOT
+4abc _CLRPREVPLOT
+4ac0 _PUT_INDEX_LST
+4ac4 _GET_INDEX_LST
+4ac8 _HEAP_SORT
+4acc _STOGDB2
+4ad0 _RCLGDB2
+4ad4 _CircCmd
+4ad8 _GrphCirc
+4adc _Mov18B
+4ae0 _DarkLine
+4ae4 _ILine
+4ae8 _IPoint
+4aec _XYRndBoth
+4af0 _XYRnd
+4af4 _CheckTOP
+4af8 _CheckXY
+4afc _DarkPnt
+4b00 _CPointS
+4b04 _WtoV
+4b08 _VtoWHLDE
+4b0c _XItoF
+4b10 _YFtoI
+4b14 _XFtoI
+4b18 _TraceOff
+4b1c _GrRedisp
+4b20 _GDispToken
+4b24 _GRDECODA
+4b28 _LABCOOR
+4b2c _COORDISP
+4b30 _TMPEQUNOSRC
+4b34 _GrLabels
+4b38 _YPixSet
+4b3c _XPixSet
+4b40 _CopyRng
+4b44 _VALCUR
+4b48 _GrPutAway
+4b4c _RstGFlags
+4b50 _GRReset
+4b54 _XYCent
+4b58 _ZoomXYCmd
+4b5c _CPTDELY
+4b60 _CPTDELX
+4b64 _SetFuncM
+4b68 _SetSeqM
+4b6c _SetPolM
+4b70 _SetParM
+4b74 _ZmInt
+4b78 _ZmDecml
+4b7c _ZmPrev
+4b80 _ZmUsr
+4b84 _SetUZm
+4b88 _ZmFit
+4b8c _ZmSquare
+4b90 _ZmTrig
+4b94 _SetXMinMax
+4b98 _ZooDefault
+4b9c _GrBufCpy
+4ba0 _DrawSplitLine
+4ba4 _RestoreDisp
+4ba8 _FNDDB
+4bac _ALLEQ
+4bb0 _fndallseleq
+4bb4 _NEXTEQ
+4bb8 _PREVEQ
+4bbc _BlinkGCur
+4bc0 _NBCURSOR
+4bc4 _StatMark
+4bc8 _ChkTextCurs
+4bcc _Regraph
+4bd0 _DOREFFLAGS02
+4bd4 _InitNSeq
+4bd8 _YRES
+4bdc _PLOTPTXY2
+4be0 _Ceiling
+4be4 _PutXY
+4be7 _Putequno
+4beb _PDspGrph
+4bef _HorizCmd
+4bf3 _VertCmd
+4bf7 _LineCmd
+4bfb _UnLineCmd
+4bff _PointCmd
+4c03 _PixelTest
+4c07 _PixelCmd
+4c0b _TanLnF
+4c0f _DrawCmd_Init
+4c13 _DrawCmd
+4c17 _ShadeCmd
+4c1b _InvCmd
+4c1f _StatShade
+4c23 _dspmattable
+4c27 _dsplsts
+4c2b _closeEditBuf
+4c2f _parseEditBuf
+4c33 _putsm
+4c37 _DspCurTbl
+4c3b _DspGrTbl
+4c3f _ZeroTemplate
+4c43 _settblrefs
+4c47 _dispTblBot
+4c4b _DispTblTop
+4c4f _dispTblbody
+4c53 _VPutBlank
+4c57 _TblTrace
+4c5b _dispListNameY
+4c5f _CurNameLength
+4c63 _NameToBuf
+4c67 _jpromptcursor
+4c6a _bufLeft
+4c6e _bufRight
+4c72 _bufInsert
+4c76 _bufQueueChar
+4c7a _bufReplace
+4c7e _bufDelete
+4c82 _bufPeek
+4c86 _bufPeek1
+4c8a _bufPeek2
+4c8e _bufPeek3
+4c92 _BufToBtm
+4c96 _setupEditEqu
+4c9a _bufToTop
+4c9e _isEditFull
+4ca2 _isEditEmpty
+4ca6 _isAtTop
+4caa _isAtBtm
+4cae _bufClear
+4cb2 _JcursorFirst
+4cb5 _JcursorLast
+4cb8 _cursorLeft
+4cbc _JcursorRight
+4cbf _JcursorUp
+4cc2 _cursorDown
+4cc6 _cursorToOffset
+4cca _insDisp
+4cce _FDISPBOL1
+4cd2 _FDISPBOL
+4cd6 _dispEOW
+4cda _dispHead
+4cde _dispTail
+4ce2 _PutTokString
+4ce6 _setupEditCmd
+4cea _setEmptyEditEqU
+4cee _setEmptyEditPtR
+4cf2 _closeEditEqu
+4cf6 _toggleIns
+4cfa _GetPrevTok
+4cfe _getkey
+4d02 _canIndic
+4d06 _LCD_DriverOn
+4d0a _DFMIN2
+4d0e _formDisp
+4d12 _formMatrix
+4d16 _wscrollLeft
+4d1a _wscrollUp
+4d1e _wscrollDown
+4d22 _wscrollRight
+4d26 _formEReal
+4d2a _formERealTOK
+4d2e _formDCplx
+4d32 _formReal
+4d36 _formScrollUp
+4d3a _setwinabove
+4d3e _disarmScroll
+4d42 _OP1toEdit
+4d46 _MinToEdit
+4d4a _rclVarToEdit
+4d4e _rclVarToEditPtR
+4d52 _RclEntryToEdit
+4d56 _rclToQueue
+4d5a _FormToTok
+4d5e _Disp_Interval
+4d62 _DisplstName
+4d66 _dispSLstNameHL
+4d6a _EditEqu
+4d6e _closeEquField
+4d72 _AutoSelect
+4d76 _DISPYEOS
+4d7a _dispNumEOS
+4d7e _setupdispeq
+4d82 _dispForward
+4d86 _DispYPrompt2
+4d8a _stringwidth
+4d8e _dispErrorScreeN
+4d92 _PopCx
+4d96 _loadnoeentry
+4d9a _SaveScreen
+4d9e _RetScreen
+4da2 _RetScreenErr
+4da6 _SplitChange
+4daa _SolveRedisp
+4dae _SolveDisp
+4db2 _itemName
+4db6 _SetNorm_Vals
+4dba _SetVert_Vals
+4dbe _ConvKeyToTok
+4dc2 _KeyToTokNew2B
+4dc6 _KeyToTok2Byte
+4dca _TokToKey
+4dce _Load_LFont
+4dd2 _Send1BErr
+4dd6 _GetVarCmd
+4dda _SendVarCmd
+4dde _PrintScreen
+4de2 _KeyScnLnk
+4de6 _IOResetAll
+4dea _DelRes
+4dee _ConvLCtoLR
+4df2 _RedimMat
+4df6 _IncLstSize
+4dfa _insertlist
+4dfe _dellistel
+4e02 _EditProg
+4e06 _CloseProg
+4e0a _ClrGraphRef
+4e0e _FixTempCnt
+4e12 _SaveData
+4e16 _RestoreData
+4e1a _FindAlphaup
+4e1e _FindAlphadn
+4e22 _CmpSyms
+4e26 _CreateTemp
+4e2a _CleanAll
+4e2e _MoveToNextSym
+4e32 _ConvLRtoLC
+4e36 _TblScreenDn
+4e3a _TblScreenUp
+4e3e _ScreenUp
+4e42 _ScreenUpDown
+4e46 _ZifRclHandler
+4e4a _zifrclkapp
+4e4e _rclkeyRtn
+4e52 _RclKey
+4e55 _RclRegEq_Call
+4e59 _RclRegEq
+4e5c _initNamePrompt
+4e60 _NamePrompt2
+4e64 _CatalogChk
+4e68 _clrTR
+4e6c _Quad
+4e70 _GraphQuad
+4e74 _BC2NonReal
+4e78 _ErrNonReal
+4e7c _Write_Text
+4e80 _FORSEQINIT
+4e84 _GrphPars
+4e88 _PlotPars
+4e8c _PARSEinp
+4e90 _PARSEOFF
+4e94 _PARSESCAN
+4e98 _GetParse
+4e9c _SaveParse
+4ea0 _InitPFlgs
+4ea4 _CkEndLinErr
+4ea8 _OP2Set60
+4eac _GetStatPtr
+4eb0 _Cmp_StatPtr
+4eb4 _VarSysAdr
+4eb8 _StoSysTok
+4ebc _StoAns
+4ec0 _StoTheta
+4ec4 _StoR
+4ec8 _StoY
+4ecc _StoN
+4ed0 _StoT
+4ed4 _StoX
+4ed8 _StoOther
+4edc _RCLANS
+4ee0 _RclY
+4ee4 _RclN
+4ee8 _RclX
+4eec _RclVarSym
+4ef0 _RclSysTok
+4ef4 _StMatEl
+4ef8 _StLstVecEl
+4efc _ConvOP1
+4f00 _isaletter
+4f04 _Find_Parse_ForMULA
+4f08 _Parse_Formula
+4f0c _StrngEnt1
+4f15 _GetNumLet
+4f1e _Init_Prog_List
+4f27 _PRGRDLP
+4f30 _VarEnt
+4f39 _RclStat
+4f42 _ParseLoop
+4f4b _ParseOnC
+4f52 _ParseOn
+4f5a _ParseCmd
+4f61 _StoType
+4f6a _CreatePair
+4f73 _PushNum
+4f7c _IncCurPCErrEnd
+4f85 _ErrEnd
+4f8e _CommaErrF
+4f97 _CommaErr
+4fa0 _StEqArg2
+4fa9 _StEqArg
+4fb2 _InpArg
+4fbb _StEqArg3
+4fc4 _NxtFetch
+4fcd _CkFetchVar
+4fd6 _FetchVarA
+4fdf PARSER_EXEC
+4fe3 _FetchVar
+4fec _CkEndLin
+4ff5 _CkEndExp
+4ffe _CkParsEnd
+5007 _StoTypeArg
+5010 _ConvDim
+5019 _ConvDim00
+5022 _AheadEqual
+502b _ParsAheadS
+5034 _ParsAhead
+503d _AnsName
+5046 _StoCmpReals
+504f _GetDEPtr
+5058 _Push2BOper
+5061 _Pop2BOper
+506a _PushOper
+5073 _PopOper
+507c _Find_E_Undef
+5085 _StTmpEq
+508e _FindEOL
+5097 _BrkInc
+50a0 _IncFetch
+50a9 _CurFetch
+50b2 PGMIO_EXEC
+50b6 _Random
+50ba _StoRand
+50be _RandInit
+50c2 _resetStacks
+50c6 _Factorial
+50ca _YOnOff
+50ce _EqSelUnsel
+50d2 _ITSOLVER
+50d6 _GRITSOLVER
+50da _ITSOLVERB
+50de _ITSOLVERNB
+50e2 _ExTest_INT
+50e6 _Dist_Fun
+50ea _LogGamma
+50ee _OneVar
+50f2 _OneVarS_0
+50f6 _ORDSTAT
+50fa _INITSTATANS2
+50fe _ANOVA_SPEC
+5102 _EXEC_ASSEMBLY
+5106 _outputExpr
+510a _CentCursor
+5113 _Text
+511c _FINISHSPEC
+5125 _TRCYFUNC
+512e _Rcl_Seq_X
+5137 _RclSeq2
+5140 _GRPPutAway
+5149 _CkValDelX
+5152 _CkValDelta
+515b _GrBufClr
+5164 _GrBufCpy_V
+516d _FndSelEq
+5176 _ClrGraphXY
+517f _Next_Y_Style
+5188 GRAPH_EXEC
+518c _PLOTPT
+5195 _NEWINDEP
+519e _Axes
+51a7 _setPenX
+51b0 _setPenY
+51b9 _setPenT
+51c2 _Tan_Equ_Disp
+51cb _putAns
+51d4 _DispOP1A
+51dd _MathTanLn
+51e6 _EndDraw
+51ef IO_EXEC
+52e5 EXECUTE_Z80
+
+[flags]
+00    kbdFlags
+2,00  trigDeg,trigFlags
+3,00  kbdSCR,kbdFlags
+4,00  kbdKeyPress,kbdFlags
+01    editFlags
+2,01  editOpen,editFlags
+4,01  monAbandon,monFlags
+02    plotFlags
+1,02  plotLoc,plotFlags
+2,02  plotDisp,plotFlags
+4,02  grfFuncM,grfModeFlags
+5,02  grfPolarM,grfModeFlags
+6,02  grfParamM,grfModeFlags
+7,02  grfRecurM,grfModeFlags
+03    graphFlags
+0,03  graphDraw,graphFlags
+2,03  graphCursor,graphFlags
+04    grfDBFlags
+0,04  grfDot,grfDBFlags
+1,04  grfSimul,grfDBFlags
+2,04  grfGrid,grfDBFlags
+3,04  grfPolar,grfDBFlags
+4,04  grfNoCoord,grfDBFlags
+5,04  grfNoAxis,grfDBFlags
+6,04  grfLabel,grfDBFlags
+05    textFlags
+1,05  textEraseBelow,textFlags
+2,05  textScrolled,textFlags
+3,05  textInverse,textFlags
+08    apdFlags
+2,08  apdAble,apdFlags
+3,08  apdRunning,apdFlags
+09    onFlags
+3,09  onRunnings,onFlags
+4,09  onInterrupt,onFlags
+6,09  statsValid,statFlags
+10    fmtFlags
+0,0A  fmtExponent,fmtFlags
+1,0A  fmtEng,fmtFlags
+5,0A  fmtReal,fmtFlags
+6,0A  fmtRect,fmtFlags
+7,0A  fmtPolar,fmtFlags
+0c    curFlags
+2,0C  curAble,curFlags
+3,0C  curOn,curFlags
+4,0C  curLock,curFlags
+0d    appFlags
+1,0D  appTextSave,appFlags
+2,0D  appAutoScroll,appFlags
+11    plotFlag2
+3,11  expr_param,plotFlag2
+4,11  expr_writing,plotFlag2
+12    indicFlags
+0,12  indicRun,indicFlags
+1,12  indicInUse,indicFlags
+2,12  indicOnly,indicFlags
+3,12  shift2nd,shiftFlags
+4,12  shiftAlpha,shiftFlags
+6,12  shiftALock,shiftFlags
+13    tblFlags
+4,13  AutoFill,tblFlags
+5,13  AutoCalc,tblFlags
+14    sGrFlags
+0,14  grfSplit,sGrFlags
+1,14  VertSplit,sGrFlags
+2,14  grfSChanged,sGrFlags
+3,14  grfSplitOverride,sGrFlags
+4,14  write_on_graph,sGrFlags
+6,14  cmp_mod_box,sGrFlags
+7,14  textWrite,sGrFlags
+15    newIndicFlags
+1,15  saIndic,newIndicFlags
+0,1D  gkKeyRepeating,gkFlag
+21    asm_flag1
+22    asm_flag1
+23    asm_flag1
diff --git a/tool/tilem-src/data/symbols/ti83p.sym b/tool/tilem-src/data/symbols/ti83p.sym
new file mode 100644
index 0000000..086215a
--- /dev/null
+++ b/tool/tilem-src/data/symbols/ti83p.sym
@@ -0,0 +1,2327 @@
+[macros]
+0EF     B_CALL~%c
+0CD5000 B_JUMP~%c
+0EDED   DB~$ED,$ED,%b,%b
+
+[labels]
+0008 rOP1TOOP2
+000B LCD_BUSY_QUICK
+0010 rFINDSYM
+0018 rPUSHREALO1
+0020 rMOV9TOOP1
+0028 rBR_CALL
+0030 rFPADD
+0050 BRT_JUMP0
+0059 APP_PUSH_ERRORH
+005C APP_POP_ERRORH
+
+[romcalls]
+4000 JErrorNo
+4003 FontHook
+4006 LocalizeHook
+4009 LdHLind
+400c CpHLDE
+400f DivHLBy10
+4012 DivHLByA
+4015 KbdScan
+4018 GetCSC
+401b coorMon
+401e Mon
+4021 monForceKey
+4024 SendKPress
+4027 JForceCmdNoChar
+402a JForceCmd
+402d sysErrHandler
+4030 newContext
+4033 newContext0
+4036 PPutAwayPrompt
+4039 PPutAway
+403c PutAway
+403f SizeWind
+4042 ErrorEP
+4045 callMain
+4048 monErrHand
+404b AppInit
+404e initialize
+4051 LCD_BUSY
+4054 Min
+4057 Max
+405a AbsO1PAbsO2
+405d Intgr
+4060 Trunc
+4063 InvSub
+4066 Times2
+4069 Plus1
+406c Minus1
+406f FPSub
+4072 FPAdd
+4075 DToR
+4078 RToD
+407b Cube
+407e TimesPt5
+4081 FPSquare
+4084 FPMult
+4087 LJRnd
+408a InvOP1SC
+408d InvOP1S
+4090 InvOP2S
+4093 Frac
+4096 FPRecip
+4099 FPDiv
+409c SqRoot
+409f RndGuard
+40a2 RnFx
+40a5 Int
+40a8 Round
+40ab LnX
+40ae LogX
+40b1 LJNoRnd
+40b4 EToX
+40b7 TenX
+40ba SinCosRad
+40bd Sin
+40c0 Cos
+40c3 Tan
+40c6 SinHCosH
+40c9 TanH
+40cc CosH
+40cf SinH
+40d2 ACosRad
+40d5 ATanRad
+40d8 ATan2Rad
+40db ASinRad
+40de ACos
+40e1 ATan
+40e4 ASin
+40e7 ATan2
+40ea ATanH
+40ed ASinH
+40f0 ACosH
+40f3 PToR
+40f6 RToP
+40f9 HLTimes9
+40fc CkOP1Cplx
+40ff CkOP1Real
+4102 Angle
+4105 COP1Set0
+4108 CpOP4OP3
+410b Mov9OP2Cp
+410e AbsO1O2Cp
+4111 CpOP1OP2
+4114 OP3ToOP4
+4117 OP1ToOP4
+411a OP2ToOP4
+411d OP4ToOP2
+4120 OP3ToOP2
+4123 OP1ToOP3
+4126 OP5ToOP2
+4129 OP5ToOP6
+412c OP5ToOP4
+412f OP1ToOP2
+4132 OP6ToOP2
+4135 OP6ToOP1
+4138 OP4ToOP1
+413b OP5ToOP1
+413e OP3ToOP1
+4141 OP6ToOP5
+4144 OP4ToOP5
+4147 OP3ToOP5
+414a OP2ToOP5
+414d OP2ToOP6
+4150 OP1ToOP6
+4153 OP1ToOP5
+4156 OP2ToOP1
+4159 Mov11B
+415c Mov10B
+415f Mov9B
+4162 Mov18
+4165 Mov8B
+4168 Mov7B
+416b Mov14
+416e OP2ToOP3
+4171 OP4ToOP3
+4174 OP5ToOP3
+4177 OP4ToOP6
+417a Mov9ToOP1
+417d Mov9OP1OP2
+4180 Mov9ToOP2
+4183 MovFrOP1
+4186 OP4Set1
+4189 OP3Set1
+418c OP2Set8
+418f OP2Set5
+4192 OP2SetA
+4195 OP2Set4
+4198 OP2Set3
+419b OP1Set1
+419e OP1Set4
+41a1 OP1Set3
+41a4 OP3Set2
+41a7 OP1Set2
+41aa OP2Set2
+41ad OP2Set1
+41b0 Zero16D
+41b3 OP5Set0
+41b6 OP4Set0
+41b9 OP3Set0
+41bc OP2Set0
+41bf OP1Set0
+41c2 SetNum0
+41c5 ZeroOP1
+41c8 ZeroOP2
+41cb ZeroOP3
+41ce ZeroOP
+41d1 ClrLp
+41d4 ShRAcc
+41d7 ShLAcc
+41da ShR18
+41dd ShR18A
+41e0 ShR16
+41e3 ShR14
+41e6 ShL16
+41e9 ShL14
+41ec SRDO1
+41ef ShRDRnd
+41f2 MantPA
+41f5 ADDPROP
+41f8 ADDPROPLP
+41fb Add16D
+41fe Add14D
+4201 Sub16D
+4204 Sub14D
+4207 OP2ExOP6
+420a OP5ExOP6
+420d OP1ExOP5
+4210 OP1ExOP6
+4213 OP2ExOP4
+4216 OP2ExOP5
+4219 OP1ExOP3
+421c OP1ExOP4
+421f OP1ExOP2
+4222 ExLp
+4225 CkOP1C0
+4228 CkOP1FP0
+422b CkOP2FP0
+422e PosNo0Int
+4231 CkPosInt
+4234 CkInt
+4237 CkOdd
+423a CkOP1M
+423d GetConOP1
+4240 GetConOP2
+4243 PIDIV2
+4246 PIDIV4
+4249 TWOPI
+424c PICON
+424f ExpToHex
+4252 OP1ExpToDec
+4255 CkOP2Pos
+4258 CkOP1Pos
+425b ClrOP2S
+425e ClrOP1S
+4261 FDiv100
+4264 FDiv10
+4267 DecO1Exp
+426a IncO1Exp
+426d IncExp
+4270 CkValidNum
+4273 GetExp
+4276 HTimesL
+4279 EOP1NotReal
+427c ThetaName
+427f RName
+4282 RegEqName
+4285 RecurNName
+4288 XName
+428b YName
+428e TName
+4291 RealName
+4294 SetEStoFPS
+4297 ChkTempDirt
+429a OP1MOP2Exp
+429d OP1ExpMDE
+42a0 ChkErrBreak
+42a3 IsA2ByteTok
+42a6 GetLastEntry
+42a9 GetLastEntryPtr
+42ac RegrClrChng
+42af ResetWinTop
+42b2 SetYUp
+42b5 SetXUp
+42b8 IsO1NonTLstOrProg
+42bb IsO1NonTempLst
+42be Is_A_LstOrCLst
+42c1 Chk_HL_999
+42c4 Equ_or_NewEqu
+42c7 ErrD_OP1NotPos
+42ca ErrD_OP1Not_R
+42cd ErrD_OP1NotPosInt
+42d0 ErrD_OP1_LE_0
+42d3 ErrD_OP1_0
+42d6 FindSym_Get_Size
+42d9 Sto_StatVar
+42dc Rcl_StatVar
+42df CkOP2Real
+42e2 Get_X_Indirect
+42e5 MemChk
+42e8 CmpPrgNamLen1
+42eb CmpPrgNamLen
+42ee FindProgSym
+42f1 ChkFindSym
+42f4 FindSym
+42f7 InsertMem
+42fa InsertMemA
+42fd EnoughMem
+4300 CmpMemNeed
+4303 CreatePVar4
+4306 CreatePVar3
+4309 CreateVar3
+430c CreateCplx
+430f CreateReal
+4312 CreateTRList
+4315 CreateRList
+4318 CreateTCList
+431b CreateCList
+431e CreateTRMat
+4321 CreateRMat
+4324 CreateTStrng
+4327 CreateStrng
+432a Create0Equ
+432d CreateTEqu
+4330 CreateEqu
+4333 CreatePict
+4336 CreateGDB
+4339 CreateProg
+433c ChkDel
+433f ChkDelA
+4342 AdjParser
+4345 AdjMath
+4348 AdjM7
+434b DelMemA
+434e Get_Form_Num
+4351 DelVar
+4354 DelVarIO
+4357 DelMem
+435a DelVar3D
+435d DelVar3C
+4360 DelVar3DC
+4363 Sym_Prog_Non_T_Lst
+4366 AdjSymPtrs
+4369 DataSizeA
+436c DataSize
+436f PopMCplxO1
+4372 PopMCplx
+4375 MovCplx
+4378 PopOP5
+437b PopOP3
+437e PopOP1
+4381 PopRealO6
+4384 PopRealO5
+4387 PopRealO4
+438a PopRealO3
+438d PopRealO2
+4390 PopRealO1
+4393 PopReal
+4396 FPopCplx
+4399 FPopReal
+439c FPopFPS
+439f DeallocFPS
+43a2 DeallocFPS1
+43a5 AllocFPS
+43a8 AllocFPS1
+43ab PushRealO6
+43ae PushRealO5
+43b1 PushRealO4
+43b4 PushRealO3
+43b7 PushRealO2
+43ba PushRealO1
+43bd PushReal
+43c0 PushOP5
+43c3 PushOP3
+43c6 PushMCplxO3
+43c9 PushOP1
+43cc PushMCplxOP1
+43cf PushMCplx
+43d2 ExMCplxO1
+43d5 Exch9
+43d8 CpyTo1FPS11
+43db CpyTo2FPS5
+43de CpyTo1FPS5
+43e1 CpyTo2FPS6
+43e4 CpyTo1FPS6
+43e7 CpyTo2FPS7
+43ea CpyTo1FPS7
+43ed CpyTo1FPS8
+43f0 CpyTo2FPS8
+43f3 CpyTo1FPS10
+43f6 CpyTo1FPS9
+43f9 CpyTo2FPS4
+43fc CpyTo6FPS3
+43ff CpyTo6FPS2
+4402 CpyTo2FPS3
+4405 CpyCTo1FPS3
+4408 CpyTo1FPS3
+440b CpyFPS3
+440e CpyTo1FPS4
+4411 CpyTo3FPS2
+4414 CpyTo5FPST
+4417 CpyTo6FPST
+441a CpyTo4FPST
+441d CpyTo3FPST
+4420 CpyTo2FPST
+4423 CpyTo1FPST
+4426 CpyFPST
+4429 CpyStack
+442c CpyTo3FPS1
+442f CpyTo2FPS1
+4432 CpyTo1FPS1
+4435 CpyFPS1
+4438 CpyTo2FPS2
+443b CpyTo1FPS2
+443e CpyFPS2
+4441 CpyO3ToFPST
+4444 CpyO2ToFPST
+4447 CpyO6ToFPST
+444a CpyO1ToFPST
+444d CpyToFPST
+4450 CpyToStack
+4453 CpyO3ToFPS1
+4456 CpyO5ToFPS1
+4459 CpyO2ToFPS1
+445c CpyO1ToFPS1
+445f CpyToFPS1
+4462 CpyO2ToFPS2
+4465 CpyO3ToFPS2
+4468 CpyO6ToFPS2
+446b CpyO1ToFPS2
+446e CpyToFPS2
+4471 CpyO5ToFPS3
+4474 CpyO2ToFPS3
+4477 CpyO1ToFPS3
+447a CpyToFPS3
+447d CpyO1ToFPS6
+4480 CpyO1ToFPS7
+4483 CpyO1ToFPS5
+4486 CpyO2ToFPS4
+4489 CpyO1ToFPS4
+448c ErrNotEnoughMem
+448f FPSMinus9
+4492 HLMinus9
+4495 ErrOverflow
+4498 ErrDivBy0
+449b ErrSingularMat
+449e ErrDomain
+44a1 ErrIncrement
+44a4 ErrNon_Real
+44a7 ErrSyntax
+44aa ErrDataType
+44ad ErrArgument
+44b0 ErrDimMismatch
+44b3 ErrDimension
+44b6 ErrUndefined
+44b9 ErrMemory
+44bc ErrInvalid
+44bf ErrBreak
+44c2 ErrStat
+44c5 ErrSignChange
+44c8 ErrIterations
+44cb ErrBadGuess
+44ce ErrTolTooSmall
+44d1 ErrStatPlot
+44d4 ErrLinkXmit
+44d7 JError
+44da noErrorEntry
+44dd pushErrorHandler
+44e0 popErrorHandler
+44e3 StrCopy
+44e6 StrCat
+44e9 IsInSet
+44ec GEQNAMEA
+44ef RECNAME
+44f2 __bank_call
+44f5 __bank_ret
+44f8 __bank_jump
+44fb __bank_entry
+44fe ReadDisp2
+4501 PutMap
+4504 PutC
+4507 DispHL
+450a PutS
+450d PutPSB
+4510 PutPS
+4513 WPutPS
+4516 PutBuf
+4519 PutBuf1
+451c WPutC
+451f WPutS
+4522 WPutSEOL
+4525 WDispEOL
+4528 WHomeUp
+452b SetNumWindow
+452e NewLine
+4531 MoveDown
+4534 ScrollUp
+4537 ShrinkWindow
+453a MoveUp
+453d ScrollDown
+4540 ClrLCDFull
+4543 ClrLCD
+4546 ClrScrnFull
+4549 ClrScrn
+454c ClrTxtShd
+454f ClrWindow
+4552 EraseEOL
+4555 EraseEOW
+4558 HomeUp
+455b GetCurLoc
+455e VPutMap
+4561 VPutS
+4564 VPutSN
+4567 VPutSNG
+456a VPutSNT
+456d RunIndicOn
+4570 RunIndicOff
+4573 SaveCmdShadow
+4576 SaveShadow
+4579 RstrShadow
+457c RstrPartial
+457f RstrCurRow
+4582 RstrUnderMenu
+4585 RstrBotRow
+4588 SaveTR
+458b RestoreTR
+458e GetKeyPress
+4591 GetTokLen
+4594 Get_Tok_Strng
+4597 GetTokString
+459a PUTBPATBUF2
+459d PUTBPATBUF
+45a0 PUTBPAT
+45a3 putcCheckScroll
+45a6 DispEOL
+45a9 fdispEOL
+45ac MakeRowCmd
+45af TOTOSTRP
+45b2 SetVarName
+45b5 DispDone
+45b8 FinishOutput
+45bb CurBlink
+45be CursorOff
+45c1 HideCursor
+45c4 CursorOn
+45c7 ShowCursor
+45ca KeyToString
+45cd PullDownChk
+45d0 MenuCatCommon
+45d3 LoadCurCat
+45d6 NCifprgmedmode
+45d9 LoadMenuNum
+45dc LoadMenuNumL
+ 45df
+45e2 MenCatRet
+45e5 NotAlphNum
+45e8 SaveSavedFlags
+45eb SetMenuFlags
+45ee RstrSomeFlags
+45f1 RstrSmallText
+45f4 DispListName
+45f7 RstrOScreen
+45fa SaveOScreen
+45fd DispLAlphaName
+4600 AbortPrgmode
+4603 Is_FullCntx
+4606 AdrMRow
+4609 AdrMEle
+460c GetMatOP1A
+460f GetM1ToOP1
+4612 GetM1TOP1A
+4615 GetMToOP1
+4618 PutToM1A
+461b PutToMa1
+461e PutToMat
+4621 Mat_El_Div
+4624 CMATFUN
+4627 RowEch_Poly
+462a RowEchelon
+462d AdrLEle
+4630 GetL1ToOP1
+4633 GetL1TOP1A
+4636 GetLToOP1
+4639 GetL1TOOP2
+463c GetL1TOP2A
+463f GetL2TOP1A
+4642 PutToLA1
+4645 PutToL
+4648 MaxMinLst
+464b LLow
+464e LHigh
+4651 LSum
+4654 CumSum
+4657 ToFrac
+465a SeqSet
+465d SeqSolve
+4660 Cmp_Num_Init
+4663 BinOPExec
+4666 ExMean1
+4669 Set2MVLPtrs
+466c SetMat1
+466f CreateTList
+4672 UnOPExec
+4675 ThreeExec
+4678 RestoreErrNo
+467b FourExec
+467e FiveExec
+4681 CpyTo2ES1
+4684 CpyTo6ES1
+4687 CpyTo1ES1
+468a CpyTo3ES1
+468d CpyTo3ES2
+4690 CpyTo2ES2
+4693 CpyTo1ES2
+4696 CpyTo2ES3
+4699 CpyTo1ES3
+469c CpyTo3ES4
+469f CpyTo6ES3
+46a2 CpyTo2ES4
+46a5 CpyTo1ES4
+46a8 CpyTo2ES5
+46ab CpyTo1ES5
+46ae CpyTo4EST
+46b1 CpyTo2EST
+46b4 CpyTo1EST
+46b7 CpyTo2ES6
+46ba CpyTo1ES6
+46bd CpyTo2ES7
+46c0 CpyTo1ES7
+46c3 CpyTo2ES8
+46c6 CpyTo1ES8
+46c9 CpyTo1ES9
+46cc CpyTo2ES9
+46cf CpyTo2ES10
+46d2 CpyTo1ES10
+46d5 CpyTo2ES11
+46d8 CpyTo1ES11
+46db CpyTo2ES12
+46de CpyTo1ES12
+46e1 CpyTo2ES13
+46e4 CpyTo1ES13
+46e7 CpyTo1ES14
+46ea CpyTo1ES16
+46ed CpyTo1ES17
+46f0 CpyTo1ES18
+46f3 CpyTo1ES15
+46f6 CpyTo2ES15
+46f9 CpyO1ToEST
+46fc CpyO1ToES1
+46ff CpyO6ToES1
+4702 CpyO6ToES3
+4705 CpyO1ToES2
+4708 CpyO2ToES2
+470b CpyO1ToES3
+470e CpyO1ToES4
+4711 CpyO1ToES5
+4714 CpyO1ToES6
+4717 CpyO1ToES7
+471a CpyO2ToES4
+471d CpyO2ToES5
+4720 CpyO2ToES6
+4723 CpyO2ToES7
+4726 CpyO2ToES8
+4729 CpyO2ToES9
+472c CpyO1ToES8
+472f CpyO1ToES9
+4732 CpyO1ToES10
+4735 CpyO1ToES11
+4738 CpyO1ToES12
+473b CpyO1ToES13
+473e CpyO1ToES14
+4741 EvalF3A
+4744 GetK
+4747 SetTitle
+474a DispVarVal
+474d RecallEd
+4750 SetupBuffer
+4753 CreateNumEditBuf
+4756 CallCommon
+4759 CommonKeys
+475c Leftmore
+475f fDel
+4762 fClear
+4765 fInsDisp
+4768 fInsDisp02
+476b SetIndicator
+476e CloseEditBufNoR
+4771 ReleaseBuffer
+4774 VarNameToOP1HL
+4777 NameToOP1
+477a numPPutAway
+477d numRedisp
+4780 numError02
+4783 Load_SFont
+4786 SFont_Len
+4789 InitNumVec
+478c SetXXOP1
+478f SetXXOP2
+4792 SetXXXXOP2
+4795 UCLineS
+4798 CLine
+479b CLineS
+479e XRootY
+47a1 YToX
+47a4 ZmStats
+47a7 Point_Stat_Hlp
+47aa DrawSPlot
+47ad InitNewTraceP
+47b0 SPlotCoord
+47b3 SPlotRight
+47b6 SPlotLeft
+47b9 CmpBoxInfo
+47bc NextPlot
+47bf PrevPlot
+47c2 ClrPrevPlot
+47c5 Put_Index_Lst
+47c8 Get_Index_Lst
+47cb Heap_Sort
+47ce StoGDB2
+47d1 RclGDB2
+47d4 CircCmd
+47d7 GrphCirc
+47da Mov18B
+47dd DarkLine
+47e0 ILine
+47e3 IPoint
+47e6 XYRndBoth
+47e9 XYRnd
+47ec CheckTop
+47ef CheckXY
+47f2 DarkPnt
+47f5 CPointS
+47f8 WToV
+47fb VtoWHLDE
+47fe Xitof
+4801 YftoI
+4804 XftoI
+4807 TraceOff
+480a GrRedisp
+480d GDispToken
+4810 GRDECODA
+4813 LabCoor
+4816 CoorDisp
+4819 TmpEquNoSrc
+481c GrLabels
+481f YPixSet
+4822 XPixSet
+4825 CopyRng
+4828 ValCur
+482b GrPutAway
+482e RstGFlags
+4831 GrReset
+4834 XYCent
+4837 ZoomXYCmd
+483a CptDelY
+483d CptDelX
+4840 SetFuncM
+4843 SetSeqM
+4846 SetPolM
+4849 SetParM
+484c ZmInt
+484f ZmDecml
+4852 ZmPrev
+4855 ZmUsr
+4858 SetUZm
+485b ZmFit
+485e ZmSquare
+4861 ZmTrig
+4864 SetXMinMax
+4867 ZooDefault
+486a GrBufCpy
+486d DrawSplitLine
+4870 RestoreDisp
+4873 FNDDB
+4876 AllEq
+4879 FndAllSelEq
+487c NextEq
+487f PrevEq
+4882 BlinkGCur
+4885 NBCursor
+4888 StatMark
+488b ChkTextCurs
+488e Regraph
+4891 DoRefFlags02
+4894 YRes
+4897 PlotPtXY2
+489a Ceiling
+489d PutXY
+48a0 PutEquNo
+48a3 PDspGrph
+48a6 HorizCmd
+48a9 VertCmd
+48ac LineCmd
+48af UnLineCmd
+48b2 PointCmd
+48b5 PixelTest
+48b8 PixelCmd
+48bb TanLnF
+48be DrawCmd_Init
+48c1 DrawCmd
+48c4 ShadeCmd
+48c7 InvCmd
+48ca StatShade
+48cd DspMatTable
+48d0 DspLsts
+48d3 CloseEditBuf
+48d6 ParseEditBuf
+48d9 PutSM
+48dc DspCurTbl
+48df DspGrTbl
+48e2 ZeroTemplate
+48e5 SetTblRefs
+48e8 DispTblBot
+48eb DispTblTop
+48ee DispTblBody
+48f1 VPutBlank
+48f4 TblTrace
+48f7 DispListNameY
+48fa CurNameLength
+48fd NameToBuf
+4900 JPromptCursor
+4903 BufLeft
+4906 BufRight
+4909 BufInsert
+490c BufQueueChar
+490f BufReplace
+4912 BufDelete
+4915 BufPeek
+4918 BufPeek1
+491b BufPeek2
+491e BufPeek3
+4921 BufToBtm
+4924 SetupEditEqu
+4927 BufToTop
+492a IsEditFull
+492d IsEditEmpty
+4930 IsAtTop
+4933 IsAtBtm
+4936 BufClear
+4939 JCursorFirst
+493c JCursorLast
+493f CursorLeft
+4942 JCursorRight
+4945 JCursorUp
+4948 CursorDown
+494b CursorToOffset
+494e InsDisp
+4951 FDispBOL1
+4954 FDispBOL
+4957 DispEOW
+495a DispHead
+495d DispTail
+4960 PutTokString
+4963 SetupEditCmd
+4966 SetEmptyEditEqu
+4969 SetEmptyEditPtr
+496c CloseEditEqu
+496f GetPrevTok
+4972 GetKey
+4975 CanIndic
+4978 LCD_DRIVERON
+497b DFMIN2
+497e FormDisp
+4981 FormMatrix
+4984 WScrollLeft
+4987 WScrollUp
+498a WScrollDown
+498d WScrollRight
+4990 FormEReal
+4993 FormERealTok
+4996 FormDCplx
+4999 FormReal
+499c FormScrollUp
+499f SetWinAbove
+49a2 DisarmScroll
+49a5 OP1toEdit
+49a8 MinToEdit
+49ab RclVarToEdit
+49ae RclVarToEditPtr
+49b1 RclEntryToEdit
+49b4 RclToQueue
+49b7 FormToTok
+49ba Disp_Interval
+49bd DispLstName
+49c0 DispSLstNameHL
+49c3 EditEqu
+49c6 CloseEquField
+49c9 AutoSelect
+49cc DispYEOS
+49cf DispNumEOS
+49d2 SetupDispEq
+49d5 DispForward
+49d8 DispYPrompt2
+49db StringWidth
+49de DispErrorScreen
+49e1 PopCx
+49e4 LoadNoEEntry
+49e7 SaveScreen
+49ea RetScreen
+49ed RetScreenErr
+49f0 CheckSplitFlag
+49f3 SolveRedisp
+49f6 SolveDisp
+49f9 ItemName
+49fc SetNorm_Vals
+49ff SetVert_Vals
+4a02 ConvKeyToTok
+4a05 KeyToTokNew2B
+4a08 KeyToTok2Byte
+4a0b TokToKey
+4a0e Send1BErr
+4a11 GetVarCmd
+4a14 SendVarCmd
+4a17 PrintScreen
+4a1a KeyScnLnk
+4a1d IOResetAll
+4a20 DelRes
+4a23 ConvLcToLr
+4a26 RedimMat
+4a29 IncLstSize
+4a2c InsertList
+4a2f DelListEl
+4a32 EditProg
+4a35 CloseProg
+4a38 ClrGraphRef
+4a3b FixTempCnt
+4a3e SaveData
+4a41 RestoreData
+4a44 FindAlphaUp
+4a47 FindAlphaDn
+4a4a CmpSyms
+4a4d CreateTemp
+4a50 CleanAll
+4a53 MoveToNextSym
+4a56 ConvLrToLc
+4a59 TblScreenDn
+4a5c TblScreenUp
+4a5f ScreenUp
+4a62 ScreenUpDown
+4a65 ZIfRclHandler
+4a68 ZIfRclKApp
+4a6b RclKey
+4a6e RclKey2
+4a71 RclRegEq
+4a74 RclRegEq2
+4a77 InitNamePrompt
+4a7a NamePrompt2
+4a7d CatalogChk
+4a80 ClrTR
+4a83 Quad
+4a86 GraphQuad
+4a89 BC2NonReal
+4a8c ErrNonReal
+4a8f Write_Text
+4a92 ForSeqInit
+4a95 GrphPars
+4a98 PlotPars
+4a9b ParseInp
+4a9e ParseOff
+4aa1 ParseScan
+4aa4 GetParse
+4aa7 SaveParse
+4aaa InitPFlgs
+4aad CkEndLinErr
+4ab0 OP2Set60
+4ab3 GetStatPtr
+4ab6 Cmp_StatPtr
+4ab9 VarSysAdr
+4abc StoSysTok
+4abf StoAns
+4ac2 StoTheta
+4ac5 StoR
+4ac8 StoY
+4acb StoN
+4ace StoT
+4ad1 StoX
+4ad4 StoOther
+4ad7 RclAns
+4ada RclY
+4add RclN
+4ae0 RclX
+4ae3 RclVarSym
+4ae6 RclSysTok
+4ae9 StMatEl
+4aec StLstVecEl
+4aef ConvOP1
+4af2 Find_Parse_Formula
+4af5 Parse_Formula
+4af8 StrngEnt1
+4afb PrgRdLp
+4afe VarEnt
+4b01 ParseOnC
+4b04 ParseOn
+4b07 ParseCmd
+4b0a StoType
+4b0d CreatePair
+4b10 PushNum
+4b13 IncCurPCErrEnd
+4b16 ErrEnd
+4b19 CommaErrF
+4b1c CommaErr
+4b1f StEqArg2
+4b22 StEqArg
+4b25 InpArg
+4b28 StEqArg3
+4b2b NxtFetch
+4b2e CkFetchVar
+4b31 FetchVarA
+4b34 FetchVar
+4b37 CkEndLin
+4b3a CkEndExp
+4b3d CkParsEnd
+4b40 StoTypeArg
+4b43 ConvDim
+4b46 ConvDim00
+4b49 AheadEqual
+4b4c ParsAheadS
+4b4f ParsAhead
+4b52 AnsName
+4b55 StoCmpReals
+4b58 GetDEPtr
+4b5b Push2BOper
+4b5e Pop2BOper
+4b61 PushOper
+4b64 PopOper
+4b67 Find_E_UndefOrArchived
+4b6a StTmpEq
+4b6d FindEOL
+4b70 BrkInc
+4b73 IncFetch
+4b76 CurFetch
+4b79 Random
+4b7c StoRand
+4b7f RandInit
+4b82 ResetStacks
+4b85 Factorial
+4b88 YOnOff
+4b8b EqSelUnsel
+4b8e ITSOLVER
+4b91 GRITSOLVER
+4b94 ITSOLVERB
+4b97 ITSOLVERNB
+4b9a ExTest_INT
+4b9d Dist_Fun
+4ba0 LogGamma
+4ba3 OneVar
+4ba6 OneVarS_0
+4ba9 OrdStat
+4bac InitStatAns2
+4baf ANOVA_Spec
+4bb2 OutputExpr
+4bb5 CentCursor
+4bb8 Text
+4bbb FinishSpec
+4bbe TRCYFUNC
+4bc1 Rcl_Seq_X
+4bc4 RclSeq2
+4bc7 GrPPutAway
+4bca CkValDelX
+4bcd CkValDelta
+4bd0 GrBufClr
+4bd3 GrBufCpy_V
+4bd6 FndSelEq
+4bd9 ClrGraphXY
+4bdc Next_Y_Style
+4bdf PlotPt
+4be2 NewIndep
+4be5 Axes
+4be8 SetPenX
+4beb SetPenY
+4bee SetPenT
+4bf1 Tan_Equ_Disp
+4bf4 PutAns
+4bf7 DispOP1A
+4bfa MathTanLn
+4bfd EndDraw
+4c00 SetTblGraphDraw
+4c03 StartDialog
+4c06 DialogInit
+4c09 GetDialogNumOP1
+4c0c SetDialogNumOP1
+4c0f GetDialogNumHL
+ 4c12
+4c15 SetDialogKeyOverride
+4c18 ResDialogKeyOverride
+4c1b ForceDialogKeypress
+4c1e DialogStartGetKey
+4c21 StartDialog_Override
+4c24 CallDialogCallback
+4c27 SetDialogCallback
+4c2a ResDialogCallback
+4c2d CopyDialogNum
+4c30 MemClear
+4c33 MemSet
+4c36 ReloadAppEntryVecs
+4c39 PointOn
+4c3c ExecuteNewPrgm
+4c3f StrLength
+4c42 UserPutMap
+4c45 GetCurrentPageSub
+4c48 FindAppUp
+4c4b FindAppDn
+4c4e FindApp
+4c51 ExecuteApp
+4c54 MonReset
+ 4c57
+ 4c5a
+ 4c5d
+4c60 IBounds
+4c63 IOffset
+4c66 DrawCirc2
+4c69 CanAlphIns
+4c6c Redisp
+4c6f GetBaseVer
+4c72 SetFP0
+4c75 AppGetCbl
+4c78 AppGetCalc
+4c7b SaveDisp
+4c7e SetIgnoreKey
+4c81 SetSendThisKeyBack
+4c84 DisableApd
+4c87 EnableApd
+ 4c8a
+ 4c8d
+4c90 forcecmd
+4c93 ApdSetup
+4c96 Get_NumKey
+4c99 AppSetup
+4c9c HandleLinkActivity
+ 4c9f
+4ca2 ReleaseSedit
+4ca5 initsmalleditline
+4ca8 startsmalledit
+ 4cab
+4cae SGetTokString
+4cb1 LoadPattern
+4cb4 SStringLength
+ 4cb7
+ 4cba
+4cbd DoNothing
+ 4cc0
+ 4cc3
+ 4cc6
+ 4cc9
+ 4ccc
+4ccf SmallEditEraseEOL
+ 4cd2
+ 4cd5
+ 4cd8
+4cdb initsmalleditBox
+ 4cde
+4ce1 EmptyHook
+ 4ce4
+ 4ce7
+ 4cea
+4ced ClearRow
+ 4cf0
+4cf3 SetupSmallEditCursor
+ 4cf6
+ 4cf9
+ 4cfc
+ 4cff
+ 4d02
+ 4d05
+ 4d08
+ 4d0b
+ 4d0e
+ 4d11
+ 4d14
+ 4d17
+ 4d1a
+ 4d1d
+ 4d20
+ 4d23
+4d26 AppScreenUpDown
+4d29 AppScreenUpDown1
+ 4d2c
+4d2f initsmalleditlinevar
+4d32 initsmalleditlineop1
+4d35 initsmalleditboxvar
+4d38 initsmalleditboxop1
+ 4d3b
+ 4d3e
+4d41 ErrCustom1
+4d44 ErrCustom2
+4d47 AppStartMouse
+ 4d4a
+ 4d4d
+ 4d50
+4d53 AppEraseMouse
+ 4d56
+4d59 ATimes12
+4d5c ClearRect
+4d5f InvertRect
+4d62 FillRect
+4d65 AppUpdateMouse
+ 4d68
+ 4d6b
+4d6e initcellbox
+4d71 drawcell
+ 4d74
+4d77 invertcell
+4d7a setcelloverride
+4d7d DrawRectBorder
+4d80 ClearCell
+4d83 covercell
+4d86 EraseRectBorder
+4d89 FillRectPattern
+4d8c DrawRectBorderClear
+ 4d8f
+ 4d92
+4d95 VerticalLine
+4d98 IBoundsFull
+4d9b DisplayImage
+ 4d9e
+ 4da1
+ 4da4
+ 4da7
+ 4daa
+ 4dad
+ 4db0
+ 4db3
+ 4db6
+ 4db9
+ 4dbc
+ 4dbf
+ 4dc2
+ 4dc5
+4dc8 CPoint
+4dcb DeleteApp
+ 4dce
+4dd1 setmodecellflag
+4dd4 resetmodecellflag
+4dd7 ismodecellset
+4dda getmodecellflag
+ 4ddd
+4de0 CellBoxManager
+4de3 startnewcell
+ 4de6
+4de9 CellCursorHandle
+ 4dec
+ 4def
+4df2 ClearCurCell
+4df5 drawcurcell
+4df8 invertcurcell
+4dfb covercurcell
+4dfe BlinkCell
+4e01 BlinkCellNoLookUp
+4e04 BlinkCurCell
+4e07 BlinkCellToOn
+4e0a BlinkCellToOnNoLookUp
+4e0d BlinkCurCellToOn
+4e10 BlinkCellToOff
+4e13 BlinkCellToOffNoLookUp
+4e16 BlinkCurCellToOff
+4e19 getcurmodecellflag
+ 4e1c
+4e1f startsmalleditreturn
+ 4e22
+ 4e25
+4e28 CellkHandle
+4e2b errchkalphabox
+ 4e2e
+ 4e31
+ 4e34
+ 4e37
+4e3a eraseallcells
+4e3d iscurmodecellset
+ 4e40
+4e43 initalphabox
+ 4e46
+ 4e49
+4e4c drawblnkcell
+4e4f ClearBlnkCell
+4e52 invertblnkcell
+ 4e55
+ 4e58
+ 4e5b
+ 4e5e
+ 4e61
+ 4e64
+4e67 HorizontalLine
+4e6a CreateAppVar
+4e6d CreateProtProg
+4e70 CreateVar
+4e73 AsmComp
+4e76 GetAsmSize
+4e79 SquishPrgm
+4e7c ExecutePrgm
+4e7f ChkFindSymAsm
+4e82 ParsePrgmName
+4e85 CSub
+4e88 CAdd
+4e8b CSquare
+4e8e CMult
+4e91 CRecip
+4e94 CDiv
+4e97 CAbs
+4e9a CSqrAbs
+4e9d CSqRoot
+4ea0 CLN
+4ea3 CLog
+4ea6 CTenX
+4ea9 CEtoX
+4eac CXrootY
+ 4eaf
+4eb2 CYtoX
+4eb5 Conj
+4eb8 CMltByReal
+4ebb CDivByReal
+4ebe CTrunc
+4ec1 CFrac
+4ec4 CIntgr
+4ec7 SendHeaderPacket
+4eca CancelTransmission
+4ecd SendScreenContents
+4ed0 SendRAMVarData
+4ed3 SendRAMCmd
+4ed6 SendPacket
+4ed9 ReceiveAck
+4edc Send4BytePacket
+4edf SendDataByte
+4ee2 Send4Bytes
+4ee5 SendAByte
+4ee8 SendCByte
+4eeb GetSmallPacket
+4eee GetDataPacket
+4ef1 SendAck
+4ef4 Get4Bytes
+4ef7 Get3Bytes
+4efa Rec1stByte
+4efd Rec1stByteNC
+4f00 ContinueGetByte
+4f03 RecAByteIO
+4f06 ReceiveVar
+4f09 ReceiveVarData2
+4f0c ReceiveVarData
+4f0f SrchVLstUp
+4f12 SrchVLstDn
+4f15 SendVariable
+4f18 Get4BytesCursor
+4f1b Get4BytesNC
+ 4f1e
+4f21 SendDirectoryContents
+4f24 SendReadyPacket
+ 4f27
+ 4f2a
+ 4f2d
+4f30 SendApplication
+4f33 SendOSHeader
+4f36 SendOSPage
+4f39 SendOS
+4f3c FlashWriteDisable
+4f3f SendCmd
+4f42 SendOSSignature
+4f45 Disp
+4f48 SendGetKeyPress
+4f4b RejectCommand
+4f4e CheckLinkLines
+4f51 GetHookByte
+4f54 LoadBIndPaged
+4f57 CursorHook
+4f5a LibraryHook
+4f5d RawKeyHook
+4f60 SetCursorHook
+4f63 SetLibraryHook
+4f66 SetRawKeyHook
+4f69 ClrCursorHook
+4f6c ClrLibraryHook
+4f6f ClrRawKeyHook
+4f72 ResetHookBytes
+4f75 AdjustAllHooks
+4f78 GetKeyHook
+4f7b SetGetKeyHook
+4f7e ClrGetKeyHook
+4f81 LinkActivityHook
+4f84 SetLinkActivityHook
+4f87 ClrLinkActivityHook
+ 4f8a
+4f8d SetCatalog2Hook
+4f90 ClrCatalog2Hook
+4f93 SetLocalizeHook
+4f96 ClrLocalizeHook
+4f99 SetTokenHook
+4f9c ClrTokenHook
+ 4f9f
+ 4fa2
+ 4fa5
+4fa8 Bit_VertSplit
+4fab SetHomeScreenHook
+4fae ClrHomeScreenHook
+4fb1 SetWindowHook
+4fb4 ClrWindowHook
+4fb7 SetGraphHook
+4fba ClrGraphHook
+ 4fbd
+ 4fc0
+ 4fc3
+4fc6 DelVarArc
+4fc9 DelVarNoArc
+4fcc SetAllPlots
+4fcf SetYEquHook
+4fd2 ClrYEquHook
+4fd5 ForceYEqu
+4fd8 Arc_Unarc
+4fdb ArchiveVar
+4fde UnarchiveVar
+4fe1 ResDialogKeyOverride
+4fe4 SetFontHook
+4fe7 ClrFontHook
+4fea SetRegraphHook
+4fed ClrRegraphHook
+4ff0 RegraphHook
+4ff3 SetTraceHook
+4ff6 ClrTraceHook
+4ff9 TraceHook
+ 4ffc
+ 4fff
+5002 JForceGraphNoKey
+5005 JForceGraphKey
+5008 PowerOff
+500b GetKeyRetOff
+500e FindGroupSym
+5011 FillBasePageTable
+5014 ArcChk
+5017 FlashToRam
+501a LoadDEIndPaged
+501d LoadCIndPaged
+5020 SetupPagedPtr
+5023 PagedGet
+5026 SetParserHook
+5029 ClrParserHook
+502c SetAppChangeHook
+502f ClrAppChangeHook
+5032 SetDrawingHook
+5035 ClrDrawingHook
+5038 IPoint_NoHook
+503b ILine_NoHook
+503e CLineS_NoHook
+5041 DeleteTempPrograms
+5044 SetCatalog1Hook
+5047 ClrCatalog1Hook
+504a SetHelpHook
+504d ClrHelpHook
+ 5050
+ 5053
+ 5056
+5059 Catalog2Hook
+505c Catalog1Hook
+ 505f
+ 5062
+5065 DispMenuTitle
+ 5068
+506b SetCxRedispHook
+506e ClrCxRedispHook
+5071 BufCpy
+5074 BufClr
+ 5077
+ 507a
+ 507d
+5080 DisplayVarInfo
+5083 SetMenuHook
+5086 ClrMenuHook
+5089 GetBCOffsetIX
+ 508c
+508f ForceFullScreen
+5092 GetVariableData
+5095 FindSwapSector
+5098 CopyFlashPage
+509b FindAppNumPages
+509e HLMinus5
+50a1 SendArcPacket
+50a4 ForceGraphKeypress
+ 50a7
+50aa FormBase
+ 50ad
+50b0 IsFragmented
+50b3 Chk_Batt_Low
+50b6 Chk_Batt_Low2
+
+## OS 1.10
+50b9 Arc_Unarc2
+
+## OS 1.13
+50bc GetAppBasePage
+50bf SetExSpeed
+ 50c2
+50c5 GroupAllVars
+50c8 UngroupVar
+50cb WriteToFlash
+50ce SetSilentLinkHook
+50d1 ClrSilentLinkHook
+50d4 TwoVarSet
+ 50d7
+ 50da
+50dd GetSysInfo
+50e0 NZIf83Plus
+50e3 LinkStatus
+
+## OS 1.15
+ 50e6
+50e9 DBusKeyScn
+
+## OS 2.21
+50ec RunAppLib
+50ef FindSpecialAppHeader
+ 50f2
+ 50f5
+ 50f8
+50fb GetVarCmdUSB
+
+## OS 2.30
+ 50fe
+ 5101
+ 5104
+ 5107
+510a GetVarVersion
+ 510d
+ 5110
+ 5113
+ 5116
+ 5119
+ 511c
+ 511f
+5122 InvertTextInsMode
+ 5125
+5128 ResetDefaults
+ 512b
+512e DispHeader
+5131 ForceGroup
+ 5134
+ 5137
+ 513a
+ 513d
+ 5140
+5143 GetRelSeconds
+5146 DisableClock
+5149 EnableClock
+514c GetDayOfWeek
+514f GetDate
+5152 FormDate
+5155 GetDateFmt
+5158 FormDateString
+515b GetTime
+515e FormTime
+5161 GetTimeFmt
+5164 FormTimeString
+5167 GetClockStatus
+516a SetDateMkList
+516d SetDateFmt
+5170 SetTimeMkList
+5173 SetTimeFmt
+5176 GetAbsSeconds
+5179 AbsSecondsToTimeList
+ 517c
+517f ClrWindowAndFlags
+5182 SetMachineID
+5185 ResetLists
+ 5188
+ 518b
+ 518e
+5191 ExecLib
+ 5194
+ 5197
+ 519a
+519d OpenLib
+51a0 WaitEnterKey
+ 51a3
+ 51a6
+ 51a9
+ 51ac
+ 51af
+ 51b2
+ 51b5
+51b8 IsOP1Resid
+ 51bb
+ 51be
+ 51c1
+ 51c4
+51c7 DispAboutScreen
+51ca ChkHelpHookVer
+51cd Disp32
+ 51d0
+ 51d3
+ 51d6
+ 51d9
+51dc DrawTableEditor
+ 51df
+ 51e2
+ 51e5
+ 51e8
+ 51eb
+ 51ee
+51f1 MatrixName
+ 51f4
+ 51f7
+ 51fa
+ 51fd
+ 5200
+ 5203
+ 5206
+ 5209
+ 520c
+ 520f
+ 5212
+ 5215
+ 5218
+ 521b
+ 521e
+5221 Chk_Batt_Level
+ 5224
+ 5227
+ 522a
+ 522d
+ 5230
+5233 GoToLastRow
+5236 RectBorder
+ 5239
+ 523c
+ 523f
+5242 LoadA5
+ 5245
+5248 NamedListToOP1
+ 524b
+ 524e
+ 5251
+ 5254
+ 5257
+ 525a
+ 525d
+ 5260
+ 5263
+ 5266
+5269 CheckUSBAutoLaunchHeader
+ 526c
+ 526f
+ 5272
+ 5275
+ 5278
+ 527b
+ 527e
+ 5281
+5284 SetVertGraphActive
+5287 ClearVertGraphActive
+528a SetUSBHook
+528d ClrUSBHook
+5290 InitUSBDevice
+5293 KillUSBPeripheral
+ 5296
+ 5299
+529c GraphLine
+ 529f
+ 52a2
+ 52a5
+ 52a8
+ 52ab
+ 52ae
+52b1 ZifTableEditor
+ 52b4
+ 52b7
+ 52ba
+52bd FindAppName
+ 52c0
+ 52c3
+ 52c6
+52c9 BufCpyCustom
+ 52cc
+
+## OS 2.40
+ 52cf
+ 52d2
+ 52d5
+ 52d8
+ 52db
+ 52de
+ 52e1
+52e4 DelVarSym
+52e7 FindAppUpNoCase
+52ea FindAppDnNoCase
+52ed DeleteInvalidApps
+52f0 DeleteApp_Link
+ 52f3
+52f6 SetAppRestrictions
+52f9 RemoveAppRestrictions
+52fc CheckAppRestrictions
+52ff DispAppRestrictions
+5302 SetupHome
+ 5305
+ 5308
+ 530b
+ 530e
+ 5311
+5314 PolarEquToOP1
+ 5317
+ 531a
+531d GetRestrictionsOptions
+5320 DispResetComplete
+ 5323
+5326 FindAppCustom
+5329 ClearGraphStyles
+
+## Boot
+8018 MD5Final
+801B RSAValidate
+801E BigNumCompare
+8021 WriteAByteUnsafe
+8024 EraseFlash
+8027 FindFirstCertificateField
+802A ZeroToCertificate
+802D GetCertificateEnd
+8030 FindGroupedField
+8033 DoNothing0
+8036 DoNothing1
+8039 DoNothing2
+803C DoNothing3
+803F DoNothing4
+8042 ATimesE
+8045 ATimesDE
+8048 DivHLByE
+804B DivHLByDE
+8051 LoadAIndPaged
+8054 FlashToRAM2
+8057 GetCertificateStart
+805A GetFieldSize
+805D FindSubField
+8060 EraseCertificateSector
+8063 CheckHeaderKey
+806C Load_BootLFontV2
+806F Load_BootLFontV
+8072 OSReceive
+8075 FindOSHeaderSubField
+8078 FindNextCertificateField
+807B RecAByteBoot
+807E GetCalcSerial
+8084 EraseFlashPage
+8087 WriteFlashUnsafe
+808A DispBootVer
+808D MD5Init
+8090 MD5Update
+8093 MarkOSInvalid
+8096 FindAppKey
+8099 MarkOSValid
+809C CheckOSValid
+809F SetupAppPubKey
+80A2 RabinValidate
+80A5 TransformHash
+80A8 IsAppFreeware
+80AB FindAppHeaderSubField
+80AE RecalcValidationBytes
+80B1 Div32ByDE
+80B4 FindSimpleGroupedField
+80B7 GetBootVer
+80BA GetHWVer
+80BD XorA
+80C0 RSAValidateBigB
+80C3 ProdNrPart1
+80C6 WriteAByteSafe
+80C9 WriteFlashSafe
+80CC SetupDateStampPubKey
+80CF SetAppLimit
+80D2 BatteryError
+
+## 84+ Boot
+80E4 USBBootMainLoop
+80E7 DisplaySysMessage
+80EA NewLine2
+80ED DisplaySysErrorAndTurnOff
+80F0 CheckBattery
+80F3 CheckBattery46
+80F6 OSReceiveUSB
+80F9 OSPacketSetup
+80FC ForceReboot
+80FF SetupOSPubKey
+8102 CheckHeaderKeyHL
+
+[labels]
+8000 ramStart
+8000 appData
+8100 ramCode
+822F ramCodeEnd
+8230 baseAppBrTab
+8251 bootTemp
+8269 MD5Length
+8292 MD5Hash
+82A3 appSearchPage
+82A5 tempSwapArea
+83A5 MD5Buffer
+838D appID
+83ED ramReturnData
+83EE arcInfo
+8406 savedArcInfo
+8432 appInfo
+843C appBank_jump
+843E appPage
+843F kbdScanCode
+8440 kbdLGSC
+8441 kbdPSC
+8442 kbdWUR
+8443 kbdDebncCnt
+8444 kbdKey
+8445 kbdGetKy
+8446 keyExtend
+8447 contrast
+8448 apdSubTimer
+8449 apdTimer
+844A curTime
+844B curRow
+844C curCol
+844D curOffset
+844E curUnder
+844F curY
+8450 curType
+8451 curXRow
+8452 prevDData
+845A lFont_record
+8462 sFont_record
+846A tokVarPtr
+846C tokLen
+846E indicMem
+8476 indicCounter
+8477 indicBusy
+8478 OP1
+8483 OP2
+848E OP3
+8499 OP4
+84A4 OP5
+84AF OP6
+84BF progToEdit
+84C7 nameBuff
+84D2 equ_edit_save
+84D3 iMathPtr1
+84D5 iMathPtr2
+84D7 iMathPtr3
+84D9 iMathPtr4
+84DB iMathPtr5
+84DD chkDelPtr1
+84DF chkDelPtr2
+84E1 insDelPtr
+84E3 upDownPtr
+84E5 fOutDat
+84EB asm_data_ptr1
+84ED asm_data_ptr2
+84EF asm_sym_ptr1
+84F1 asm_sym_ptr2
+84F3 asm_ram
+8507 asm_ind_call
+8508 textShadow
+8588 textShadCur
+858A textShadTop
+858B textShadAlph
+858C textShadIns
+858D cxMain
+858F cxPPutAway
+8591 cxPutAway
+8593 cxRedisp
+8595 cxErrorEP
+8597 cxSizeWind
+8599 cxPage
+859A cxCurApp
+859B cxPrev
+85AA monQH
+85AB monQT
+85AC monQueue
+85BC onSP
+85BE onCheckSum
+85C0 promptRow
+85C1 promptCol
+85C2 promptIns
+85C3 promptShift
+85C4 promptRet
+85C6 promptValid
+85C8 promptTop
+85CA promptCursor
+85CC promptTail
+85CE promptBtm
+85D0 varType
+85D1 varCurrent
+85D9 varClass
+85DA catCurrent
+85DC menuActive
+85DD menuAppDepth
+85DE menuCurrent
+85E8 progCurrent
+85FE userMenuSA
+865F ioPrompt
+8660 RectFillPHeight
+8660 dImageWidth
+8661 RectFillPWidth
+8662 RectFillPattern
+8670 ioFlag
+8672 sndRecState
+8673 ioErrState
+8674 header
+867D ioData
+8689 ioNewData
+868B bakHeader
+86B7 calc_id
+86D7 penCol
+86D8 penRow
+86D9 rclQueue
+86DB rclQueueEnd
+86DD errNo
+86DE errSP
+86E0 errOffset
+86EC saveSScreen
+89EC usermem_offset
+89EE bstCounter
+89F0 flags
+8A3A statVars
+8C17 anovaf_vars
+8C4D infVars
+8D17 curGStyle
+8D18 curGY
+8D19 curGX
+8D1A curGY2
+8D1B curGX2
+8D1C freeSaveY
+8D1D freeSaveX
+8DA1 XOffset
+8DA2 YOffset
+8DA3 lcdTallP
+8DA4 pixWideP
+8DA5 pixWide_m_1
+8DA6 pixWide_m_2
+8DA7 lastEntryPtr
+8DA9 lastEntryStk
+8E29 numLastEntries
+8E2A currLastEntry
+8E67 curInc
+8E6A ORGXMIN
+8EB4 uThetMin
+8EBD uThetMax
+8EC6 uThetStep
+8ECF uTmin
+8ED8 uTmax
+8EE1 uTStep
+8E7E uXmin
+8E87 uXmax
+8E90 uXscl
+8E99 uYmin
+8EA2 uYmax
+8EAB uYscl
+8EEA uPlotStart
+8EF3 unMax
+8EFC uu0
+8F05 uv0
+8F0E unMin
+8F17 uu02
+8F20 uv02
+8F29 uw0
+8F32 uPlotStep
+8F3B uXres
+8F44 uw02
+8F50 Xmin
+8F59 Xmax
+8F62 Xscl
+8F6B Ymin
+8F74 Ymax
+8F7D Yscl
+8FB3 Tstep
+8FBC PlotStart
+8FC5 nMax
+8FCE u0
+8FD7 v0
+8FE0 nMin
+8FE9 u02
+8FF2 v02
+8FFB w0
+9004 PlotStep
+900D XresO
+9016 w02
+901F un1
+9028 un2
+9031 vn1
+903A vn2
+9043 wn1
+904C wn2
+9055 fin_N
+905E fin_I
+9067 fin_PV
+9070 fin_PMT
+9079 fin_FV
+9082 fin_PY
+908B fin_CY
+9094 cal_N
+909D cal_I
+90A6 cal_PV
+90AF cal_PMT
+90B8 cal_FV
+90C1 cal_PY
+90D3 smallEditRAM
+9151 Xres_int
+913F XFact
+9148 YFact
+9152 deltaX
+915B deltaY
+9164 shortX
+916D shortY
+9176 lower
+917F upper
+8F86 ThetaMin
+8F8F ThetaMax
+8F98 ThetaStep
+8FA1 TminPar
+8FAA TmaxPar
+918C XOutSym
+918E XOutDat
+9190 YOutSym
+9192 YOutDat
+9194 inputSym
+9196 inputDat
+9198 prevData
+92C9 P1Type
+92CA SavX1List
+92CF SavY1List
+92D4 SavF1List
+92D9 P1FrqOnOff
+92DA P2Type
+92DB SavX2List
+92E0 SavY2List
+92E5 SavF2List
+92EA P2FrqOnOff
+92EB P3Type
+92EC SavX3List
+92F1 SavY3List
+92F6 SavF3List
+92FB P3FrqOnOff
+92B3 TblMin
+92BC TblStep
+9302 ES
+9340 plotSScreen
+9640 seed1
+9649 seed2
+9652 parseVar
+965B begPC
+965D curPC
+965F endPC
+9776 GY1
+9777 GY2
+9778 GY3
+9779 GY4
+977A GY5
+977B GY6
+977C GY7
+977D GY8
+977E GY9
+977F GY0
+9780 GX1
+9781 GX2
+9782 GX3
+9783 GX4
+9784 GX5
+9785 GX6
+9786 GR1
+9787 GR2
+9788 GR3
+9789 GR4
+978A GR5
+978B GR6
+978C GU
+978D GV
+978E GW
+966E cmdShadow
+96EE cmdShadCur
+96F0 cmdShadAlph
+96F1 cmdShadIns
+96F2 cmdCursor
+96F4 editTop
+96F6 editCursor
+96F8 editTail
+96FA editBtm
+9706 editSym
+9708 editDat
+97A3 modePtr
+97A5 winTop
+97A6 winBtm
+97A7 winLeftEdge
+97A8 winLeft
+97AA winAbove
+97AC winRow
+97AE winCol
+97B0 fmtDigits
+97B1 fmtString
+97F2 fmtConv
+9804 fmtLeft
+9806 fmtIndex
+9808 fmtMatSym
+980A fmtMatMem
+980C EQS
+9815 delAdjAmt
+9818 tSymPtr1
+981A tSymPtr2
+981C chkDelPtr3
+981E chkDelPtr4
+9820 tempMem
+9822 fpBase
+9824 FPS
+9826 OPBase
+9828 OPS
+982A pTempCnt
+982C cleanTmp
+982E pTemp
+9830 progPtr
+9832 newDataPtr
+9834 pagedCount
+9835 pagedPN
+9836 pagedGetPtr
+9838 pagedPutPtr
+983A pagedBuf
+984D appErr1
+985A appErr2
+9867 flashByte1
+9868 flashByte2
+9869 freeArcBlock
+986B arcPage
+986C arcPtr
+9870 appRawKeyHandle
+9872 appBackUpScreen
+9B72 customHeight
+9B73 localLanguage
+9B78 linkActivityHookPtr
+9B7C cursorHookPtr
+9B80 libraryHookPtr
+9B84 rawKeyHookPtr
+9B88 getKeyHookPtr
+9B8C homescreenHookPtr
+9B90 windowHookPtr
+9B94 graphHookPtr
+9B98 yEquHookPtr
+9B9C fontHookPtr
+9BA0 regraphHookPtr
+9BA4 drawingHookPtr
+9BA8 traceHookPtr
+9BAC parserHookPtr
+9BB0 appChangeHookPtr
+9BB4 catalog1HookPtr
+9BB8 helpHookPtr
+9BBC cxRedispHookPtr
+9BC0 menuHookPtr
+9BC4 catalog2HookPtr
+9BC8 tokenHookPtr
+9BCC localizeHookPtr
+9BD0 silentLinkHookPtr
+9BD4 usbHookPtr
+9C06 baseAppBrTab2
+9CB0 DBKeyScanCode
+9CB1 DBKeyRptCtr
+9D65 localTokStr
+9D76 keyForStr
+9D77 keyToStrRam
+9D88 sedMonSp
+9D8A bpSave
+9D95 userMem
+0FE66 symTable
+
+[flags]
+00    kbdFlags
+0,00  inDelete,ioDelFlag
+2,00  trigDeg,trigFlags
+3,00  kbdSCR,kbdFlags
+4,00  kbdKeyPress,kbdFlags
+5,00  donePrgm,doneFlags
+01    editFlags
+2,01  editOpen,editFlags
+4,01  monAbandon,monFlags
+02    plotFlags
+0,02  plotTrace,plotFlags
+1,02  plotLoc,plotFlags
+2,02  plotDisp,plotFlags
+4,02  grfFuncM,grfModeFlags
+5,02  grfPolarM,grfModeFlags
+6,02  grfParamM,grfModeFlags
+7,02  grfRecurM,grfModeFlags
+03    graphFlags
+0,03  graphDraw,graphFlags
+1,03  graphProg,graphFlags
+04    grfDBFlags
+0,04  grfDot,grfDBFlags
+1,04  grfSimul,grfDBFlags
+2,04  grfGrid,grfDBFlags
+3,04  grfPolar,grfDBFlags
+4,04  grfNoCoord,grfDBFlags
+5,04  grfNoAxis,grfDBFlags
+6,04  grfLabel,grfDBFlags
+05    textFlags
+1,05  textEraseBelow,textFlags
+2,05  textScrolled,textFlags
+3,05  textInverse,textFlags
+4,05  textInsMode,textFlags
+07    ParsFlag2
+0,07  numOP1,ParsFlag2
+08    apdFlags
+0,08  preClrForMode,newDispF
+2,08  apdAble,apdFlags
+3,08  apdRunning,apdFlags
+09    onFlags
+0,09  appWantAlphaUpDn,alphaUpDnFlag
+3,09  onRunning,onFlags
+4,09  onInterrupt,onFlags
+6,09  statsValid,statFlags
+7,09  statANSDISP,statFlags
+0A    fmtFlags
+0,0A  fmtExponent,fmtFlags
+1,0A  fmtEng,fmtFlags
+2,0A  fmtHex,fmtFlags
+3,0A  fmtOct,fmtFlags
+4,0A  fmtBin,fmtFlags
+5,0A  fmtReal,fmtFlags
+6,0A  fmtRect,fmtFlags
+7,0A  fmtPolar,fmtFlags
+0B    fmtOverride
+0,0B  fmtExponent,fmtOverride
+1,0B  fmtEng,fmtOverride
+2,0B  fmtHex,fmtOverride
+3,0B  fmtOct,fmtOverride
+4,0B  fmtBin,fmtOverride
+5,0B  fmtReal,fmtOverride
+6,0B  fmtRect,fmtOverride
+7,0B  fmtPolar,fmtOverride
+0C    curFlags
+0,0C  fmtEdit,fmtEditFlags
+2,0C  curAble,curFlags
+3,0C  curOn,curFlags
+4,0C  curLock,curFlags
+5,0C  cmdVirgin,cmdFlags
+0D    appFlags
+0,0D  appWantIntrpt,appFlags
+1,0D  appTextSave,appFlags
+2,0D  appAutoScroll,appFlags
+3,0D  appMenus,appFlags
+4,0D  appLockMenus,appFlags
+5,0D  appCurGraphic,appFlags
+6,0D  appCurWord,appFlags
+7,0D  appExit,appFlags
+0E    rclFlags
+7,0E  rclQueueActive
+0F    seqFlags
+0,0F  webMode,seqFlags
+1,0F  webVert,seqFlags
+2,0F  sequv,seqFlags
+3,0F  seqvw,seqFlags
+4,0F  sequw,seqFlags
+11    promptFlags
+0,11  promptEdit,promptFlags
+3,11  expr_param,plotFlag2
+4,11  expr_writing,plotFlag2
+12    shiftFlags
+0,12  indicRun,indicFlags
+1,12  indicInUse,indicFlags
+2,12  indicOnly,indicFlags
+3,12  shift2nd,shiftFlags
+4,12  shiftAlpha,shiftFlags
+5,12  shiftLwrAlph,shiftFlags
+6,12  shiftALock,shiftFlags
+7,12  shiftKeepAlph,shiftFlags
+13    tblFlags
+4,13  autoFill,tblFlags
+5,13  autoCalc,tblFlags
+6,13  reTable,tblFlags
+14    sGrFlags
+0,14  grfSplit,sGrFlags
+1,14  vertSplit,sGrFlags
+2,14  grfSChanged,sGrFlags
+3,14  grfSplitOverride,sGrFlags
+4,14  write_on_graph,sGrFlags
+5,14  g_style_active,sGrFlags
+6,14  cmp_mod_box,sGrFlags
+7,14  textWrite,sGrFlags
+15    newIndicFlags
+0,15  extraIndic,newIndicFlags
+1,15  saIndic,newIndicFlags
+16    newFlags2
+5,16  noRestores,newFlags2
+17    smartFlags
+0,17  smartGraph,smartFlags
+1,17  smartGraph_inv,smartFlags
+1A    more_Flags
+2,1A  No_Del_Stat,more_Flags
+0,1D  gkKeyRepeating,gkFlag
+21    asm_Flag1
+22    asm_Flag2
+23    asm_Flag3
+24    appLwrCaseFlag
+1,24  comFailed,getSendFlg
+3,24  lwrCaseActive,appLwrCaseFlag
+26    groupFlags
+1,26  inGroup,groupFlags
+28    apiFlag
+0,28  appAllowContext,apiFlag
+4,28  appRunning,apiFlag
+7,28  appRetOffKey,apiFlag
+29    apiFlg2
+2A    apiFlg3
+2B    apiFlg4
+2,2B  fullScrnDraw,apiFlg4
+2C    apiFlg5
+0,2C  appWantDiagonalKeys,apiFlg5
+2E    xapFlag0
+2F    xapFlag1
+30    xapFlag2
+31    xapFlag3
+32    fontFlags
+2,32  fracDrawLFont,fontFlags
+3,32  fracTallLFont,fontFlags
+6,32  drawLFont,fontFlags
+7,32  customFont,fontFlags
+33    hookFlags0
+0,33  alt_On,scriptFlag
+1,33  alt_Off,scriptFlag
+2,33  useRclQueueEnd,rclFlag2
+3,33  ignoreBPLink,backGroundLink
+4,33  linkActivityHookActive,linkActivityHookFlag
+34    hookFlags1
+0,34  getKeyHookActive,getKeyHookFlag
+1,34  libraryHookActive,libraryHookFlag
+4,34  homescreenHookActive,homescreenHookFlag
+5,34  rawKeyHookActive,rawKeyHookFlag
+6,34  catalog2HookActive,catalog2HookFlag
+7,34  cursorHookActive,cursorHookFlag
+35    hookFlags2
+0,35  tokenHookActive,tokenHookFlag
+1,35  localizeHookActive,localizeHookFlag
+2,35  windowHookActive,windowHookFlag
+3,35  graphHookActive,graphHookFlag
+4,35  yEquHookActive,yEquHookFlag
+5,35  fontHookActive,fontHookFlag
+6,35  regraphHookActive,regraphHookFlag
+7,35  drawingHookActive,drawingHookFlag
+36    hookFlags3
+0,36  traceHookActive,traceHookFlag
+1,36  parserHookActive,parserHookFlag
+2,36  appChangeHookActive,appChangeHookFlag
+3,36  catalog1HookActive,catalog1HookFlag
+4,36  helpHookActive,helpHookFlag
+5,36  cxRedispHookActive,cxRedispHookFlag
+6,36  menuHookActive,menuHookFlag
+7,36  silentLinkHookActive,silentLinkHookFlag
+37    hookAutoFlags1
+38    hookAutoFlags2
+39    hookAutoFlags3
+3A    hookFlags4
+0,3A  usbHookActive,usbHookFlag
+3C    plotFlag3
+0,3C  bufferOnly,plotFlag3
+4,3C  useFastCirc,plotFlag3
+3D    DBKeyFlags
+0,3D  leftShiftPressed,DBKeyFlags
+1,3D  rightShiftPressed,DBKeyFlags
+2,3D  diamondPressed,DBKeyFlags
+3,3D  squarePressed,DBKeyFlags
+4,3D  repeatMost,DBKeyFlags
+5,3D  haveDBKey,DBKeyFlags
+6,3D  keyDefaultsF,DBKeyFlags
+7,3D  HWLinkErrF,DBKeyFlags
+3E    openLibFlag
+4,3E  openLibActive,openLibFlag
+3F    clockFlags
+0,3F  clockNotMonthFirst,clockFlags
+1,3F  clockYearFirst,clockFlags
+2,3F  clock24Hour,clockFlags
+4,3F  clockFmtASCII,clockFlags
diff --git a/tool/tilem-src/data/symbols/ti85.sym b/tool/tilem-src/data/symbols/ti85.sym
new file mode 100644
index 0000000..4cab7ed
--- /dev/null
+++ b/tool/tilem-src/data/symbols/ti85.sym
@@ -0,0 +1,254 @@
+[macros]
+0CD098C ROM_CALL~%C
+0CD0C8C CALL_~Z,prgm+%j
+0CD0F8C CALL_~prgm+%j
+0CD128C CALL_~NZ,prgm+%j
+0CD188C CALL_~C,prgm+%j
+0CD1E8C CALL_~NC,prgm+%j
+0CD248C JUMP_~Z,prgm+%j
+0CD278C JUMP_~prgm+%j
+0CD2A8C JUMP_~NZ,prgm+%j
+0CD308C JUMP_~C,prgm+%j
+0CD368C JUMP_~NC,prgm+%j
+0CDC88C RCALL_~%j
+
+[romcalls]
+00 TX_CHARPUT
+01 D_LT_STR
+02 M_CHARPUT
+03 D_ZM_STR
+04 D_LM_STR
+05 GET_T_CUR
+06 SCROLL_UP
+07 TR_CHARPUT
+08 CLEARLCD
+09 D_HL_DECI
+0A CLEARTEXT
+0B D_ZT_STR
+0C BUSY_OFF
+0D BUSY_ON
+80 FIND_PIXEL
+
+[labels]
+0008 rOP1TOOP2
+0010 rFINDSYM
+0018 rPUSHOP1
+0020 rMOV10TOOP1
+0028 rFPMULT
+0030 rFPADD
+
+0033 LD_HL_MHL
+008E CP_HL_DE
+009A UNPACK_HL
+01A2 READ_KEYPAD
+01B1 STORE_KEY
+01BE GET_KEY
+0115 UPDATE_APD
+0168 READ_KEY
+
+8000 kbdScanCode
+8001 kbdLGSC
+8002 kbdPSC
+8003 kbdWUR
+8004 kbdDebncCnt
+8005 kbdKey
+8006 kbdGetKy
+8007 contrast
+8008 apdSubTimer
+8009 apdTimer
+800A apdWarmUp
+800B curTime
+800C curRow
+800D curCol
+800E curUnder
+800F undelBufLen
+8010 undelBuf
+# 8074 tokVarPtr?
+# 8076 tokLen?
+8078 indicMem
+8080 indicCounter
+8081 indicBusy
+8082 OP1
+808D OP2
+8098 OP3
+80A3 OP4
+80AE OP5
+80B9 OP6
+80C6 iMathPtr1
+80C8 iMathPtr2
+80CA iMathPtr3
+80CC iMathPtr4
+80CE iMathPtr5
+# 80D0 chkDelPtr1?
+# 80D2 chkDelPtr2?
+# 80D4 insDelPtr?
+# 80D6 upDownPtr?
+80DF textShadow
+8187 textShadCur
+# 8189 textShadTop?
+# 818A textShadAlph?
+# 818B textShadIns?
+818C textAccent
+818D cxMain
+# 818F cxPPutAway?
+# 8191 cxPutAway?
+# 8193 cxRedisp?
+# 8195 cxErrorEP?
+8197 cxSizeWind
+8199 cxPage
+# 819A cxCurApp?
+819B cxPrev
+# 81AA monQH?
+# 81AB monQT?
+# 81AC monQueue?
+81BC onSP
+81BE onCheckSum
+81DD menuActive
+8333 penCol
+8334 penRow
+# 8335 rclQueue?
+8337 errNo
+8338 errSP
+# 833A errOffset?
+8346 flags
+8364 stCounter
+# 85E3 XOutDat?
+# 85E7 YOutDat?
+# 85EB inputDat?
+8629 ES
+8641 plotSScreen
+8A41 seed1
+8A4B seed2
+# 8A56 parseVar?
+8A5F begPC
+8A61 curPC
+8A63 endPC
+8A6B cmdShadow
+# 8B27 editDat?
+8B2D modePtr
+8B2F winTop
+8B30 winBtm
+8B31 winLeftEdge
+8B32 winLeft
+8B34 winAbove
+8B36 winRow
+# 8B38 winCol?
+8B3A fmtDigits
+# 8B96 fmtMatMem?
+# 8B98 EQS?
+8BD2 delAdjAmt
+8BDD tempMem
+8BDF fpBase
+8BE1 FPS
+8BE3 OPBase
+8BE5 OPS
+# 8BE7 pTempCnt?
+# 8BE9 cleanTmp?
+8BEB pTemp
+8BF7 userMem
+0FA6F symTable
+0FC00 videoRAM
+
+## ZShell
+8C3C PROGRAM_ADDR
+8C3E ROM_VERS
+8C3F ZSHELL_VER
+8C40 ZS_BITS
+
+## Usgard
+8E8B ORGSP
+8EA2 USGSHELL
+8EAB VATName
+8EB4 DEST_ADDR
+8EB4 PAGE1ADDR
+8EB6 PAGE2ADDR
+8C08 PROG_BYTE
+8C41 TX_CHARPUT
+8C44 D_LT_STR
+8C47 M_CHARPUT
+8C4A D_ZM_STR
+8C4D D_LM_STR
+# 8C50 SCROLL_UP
+# 8C53 TR_CHARPUT
+# 8C56 CLEARLCD
+# 8C59 D_HL_DECI
+# 8C5C CLEARTEXT
+# 8C5F D_ZT_STR
+# 8C62 BUSY_OFF
+# 8C65 BUSY_ON
+# 8C68 RANDOM
+8C6B FIND_PIXEL
+8C77 FREEMEM
+8C7A VAR_LENGTH
+8C7D ASCIIZ_LEN
+8C80 NEG_BC
+8C83 MUL_HL
+8C8C COPY_STRING
+8C9B USGARD_INT_INSTALL
+8C9E USGARD_INT_REMOVE
+8CA1 USGARD_INT_CLEAN
+8C95 APPEND
+8C98 UNAPPEND
+8CCB CHECK_APPEND
+8CA4 VAR_NEW
+8CA7 VAR_DELETE
+8CAA VAR_EXEC
+8CAD VAR_GET
+8CB0 VAR_RESIZE
+8CCE RELOC
+8CD1 DERELOC
+8CD7 RELOC_TAB
+8CB3 SEARCH_VAT
+8CB6 OTH_SHUTDOWN
+8CB9 DM_HL_DECI
+8CBC OTH_PAUSE
+8CBF OTH_CLEAR
+8CC2 OTH_EXIT
+8CC5 OTH_ARROW
+8CD4 OTH_FILL
+
+## Rigel
+# 8C50 GET_T_CUR
+# 8C53 SCROLL_UP
+# 8C56 TR_CHARPUT
+# 8C59 CLEARLCD
+# 8C5C D_HL_DECI
+# 8C5F CLEARTEXT
+# 8C62 D_ZT_STR
+# 8C65 BUSY_OFF
+# 8C68 BUSY_ON
+8C6E RIGEL_INT_INSTALL
+8C71 RIGEL_INT_REMOVE
+8C06 VAR_SEARCH
+
+[flags]
+00    kbdFlags
+2,00  trigDeg,trigFlags
+3,00  kbdSCR,kbdFlags
+4,00  kbdKeyPress,kbdFlags
+05    textFlags
+2,05  textScrolled,textFlags
+3,05  textInverse,textFlags
+4,05  textInsMode,textFlags
+08    apdFlags
+2,08  apdAble,apdFlags
+3,08  apdRunning,apdFlags
+09    onFlags
+3,09  onRunning,onFlags
+4,09  onInterrupt,onFlags
+0C    curFlags
+2,0C  curAble,curFlags
+3,0C  curOn,curFlags
+4,0C  curLock,curFlags
+0D    appFlags
+1,0D  appTextSave,appFlags
+5,0D  appCurGraphic,appFlags
+6,0D  appCurWord,appFlags
+12    indicFlags
+0,12  indicRun,indicFlags
+2,12  indicOnly,indicFlags
+3,12  shift2nd,shiftFlags
+4,12  shiftAlpha,shiftFlags
+5,12  shiftLwrAlph,shiftFlags
+6,12  shiftALock,shiftFlags
+7,12  shiftKeepAlph,shiftFlags
diff --git a/tool/tilem-src/data/symbols/ti86.sym b/tool/tilem-src/data/symbols/ti86.sym
new file mode 100644
index 0000000..5fb2cf3
--- /dev/null
+++ b/tool/tilem-src/data/symbols/ti86.sym
@@ -0,0 +1,1112 @@
+[labels]
+0008 rOP1TOOP2
+0010 rFINDSYM
+0018 rPUSHOP1
+0020 rMOV10TOOP1
+0028 rFPMULT
+0030 rFPADD
+
+4010 _ldhlind
+4028 _chkON
+402C _bitgrffuncm
+4030 _bitgrfpolarm
+4034 _bitgrfparamm
+4038 _bitgrfrecurm
+403C _cphlde
+4040 _put_colon
+4044 _divHLby10
+4048 _divHLbyA
+404C _divAHLby10
+4058 _timeout
+4060 _resetAPD
+4064 _scan_code
+4068 _get_key
+409C _jforcecmdnochar
+40B5 _pPutAwayPrompt
+40BD _call_cxPPutAway
+40C1 _call_cxPutAway
+40C5 _call_cxSizeWind
+40C9 _call_cxErrorEP
+40CD _call_cxMain
+40D1 _cxNull
+40D2 _p_cxNull
+40DD _err_handler
+40E1 _set_cx_100
+40E5 _set_cx_50
+40E9 _set_cx_dec
+40ED _set_context
+4101 _off
+4109 _reset
+4119 _removTok
+412D _errAxes
+4130 _errFldOrder
+4133 _errStatPlot
+4136 _errOverflow
+4139 _errDivBy0
+413c _errSingularMat
+413f _errDomain
+4142 _errIncrement
+4145 _errSyntax
+4148 _errNumberBase
+414B _errMode
+414e _errDataType
+4151 _errArgument
+4154 _errDimMismatch
+4157 _errDimension
+415A _errUndefined
+4169 _errReserved
+416c _errInvalid
+416f _errIllegalNest
+4172 _errBound
+4175 _errGraphWindow
+4178 _errZoom
+417b _errBreak
+417e _errStat
+4181 _errConversion
+4184 _errSolver
+4187 _errIterations
+418a _errBadGuess
+418d _errDifEqSetup
+4190 _errPoly
+4193 _errTolNotMet
+4196 _errLink
+4199 _errorA
+419C _error
+41A1 _instError
+41A4 _removError
+41B7 _ld_de_fp0
+41BB _ld_de_fp1
+41BF _mulHL10
+41C3 _ckop1cplx
+41C7 _ckcplx
+41CB _ckop1real
+41FB _cpop1op2
+4203 _op3toop4
+4207 _op1toop4
+420B _op2toop4
+420F _movtoop4
+4213 _op4toop2
+4217 _op4toop3
+421B _op3toop2
+421F _op1toop3
+4223 _movfrop1
+4227 _op5toop2
+422B _op5toop6
+422F _op5toop4
+4233 _op1toop2
+4237 _movtoop2
+423B _op6toop2
+423F _op6toop1
+4243 _op4toop1
+4247 _op5toop1
+424B _op3toop1
+424F _op4toop5
+4253 _op3toop5
+4257 _op2toop5
+425B _movtoop5
+425F _op2toop6
+4263 _op1toop6
+4267 _movtoop6
+426B _op1toop5
+426F _op2toop1
+4273 _movtoop1
+4277 _mov11b
+427B _mov10b
+427F _mov9b
+4283 _mov9b_
+4287 _mov8b
+428B _mov7b
+428F _mov7b_
+4293 _mov6b
+4297 _mov5b
+429B _mov4b
+429F _mov3b
+42A3 _mov2b
+42A7 _op4toop2m
+42CB _op2toop3
+42CF _movtoop3
+42D3 _op4toop6
+42D7 _mov10toop1
+42DB _mov10toop1op2
+42DF _mov10toop2
+42E3 _movfroop1
+42E7 _op4set1
+42EB _op3set1
+42EF _op2set8
+42F7 _op2set5
+42FB _op2set4
+4303 _op2set3
+430F _op1set1
+4313 _op1set4
+4317 _op1set3
+431B _op3set2
+431F _op1set2
+4323 _op2set2
+432F _op2set1
+4343 _ld_hl_8z
+4347 _ld_hl_8a
+434B _ld_hlplus1_7a
+434F _ld_hl_7a
+4353 _op4set0
+4357 _op3set0
+435B _op2set0
+435F _op1set0
+4363 _ld_hl_fp0
+4367 _zeroop1m
+436B _zeroop1
+436F _zeroop2
+4373 _zeroop3
+4377 _ld_hl_11z
+437B _ld_hl_bz
+4383 _shracc
+438B _shlacc
+446F _ex_op2_op6
+4473 _ex_op5_op6
+4477 _ex_op1_op5
+447B _ex_op1_op6
+447F _ex_op2_op4
+4483 _ex_op2_op5
+4487 _ex_op1_op3
+448B _ex_op1_op4
+448F _ex_op1_op2
+449B _ckop1fp0
+44A3 _ckop2fp0
+44B3 _ckop1int
+44B7 _ckint
+44BB _ckop1odd
+44BF _ckodd
+450B _ckop2pos
+450F _ckop1pos
+4513 _absop2
+4527 _inco1exp
+4547 _HtimesL
+458F _findsym_error
+45E3 _invsub
+45EB _PLUS1
+45EF _inc_ptr_ade
+45F3 _ex_ahl_bde
+460B _get_size_word
+4613 _setXXop1
+4617 _setXXop2
+461B _setXXXXop2
+462F _load_ram_ahl
+4633 _conv_ahl
+4637 _inc_ptr_ahl
+463B _dec_ptr_ahl
+463F _inc_ptr_bde
+4643 _dec_ptr_bde
+4647 _set_abs_src_addr
+464B _get_free_mem
+464F _set_mm_num_bytes
+4657 _round_OP1
+46AF _check_asm
+46B7 _jump_table
+46BB _memchk
+46BF _dec_ptr_ade
+46C3 _getb_ahl
+46C7 _cp_ahl_bde
+46CB _findsym
+46D3 _copy_fwd
+46D7 _del_temp_vars
+46EB _createreal
+46EF _createrconst
+46F3 _createcconst
+46F7 _createcplx
+46FB _creatervect_temp
+46FF _creatervect
+4703 _createcvect_temp
+4707 _createcvect
+470B _createrlist_temp
+470F _createrlist
+4713 _createclist_temp
+4717 _createclist
+471B _creatermat_temp
+471F _creatermat
+4723 _createcmat_temp
+4727 _createcmat
+472B _createstrng_temp
+472F _createstrng
+4733 _createequ_temp
+4737 _createequ
+473B _createpict
+474F _createprog
+475B _copy_bkwd
+475F _delvar
+476F _update_VAT_ptrs
+477B _get_size
+477F _get_var_size
+4783 _push_bc_OPS
+4787 _check_STACK_mem
+478B _pop_bc_OPS
+478F _push_a_OPS
+4793 _pop_a_OPS
+479F _popop1
+47A3 _poprealo6
+47A7 _poprealo5
+47AB _poprealo4
+47AF _poprealo3
+47A3 _poprealo2
+47A7 _poprealo1
+47CB _sub_FPS_20
+47CF _sub_FPS_10
+47D3 _sub_FPS_bc
+47DB _deallocfps1
+47E3 _ram_page_1
+47E7 _load_ram_OPS
+47EB _load_ram_ES
+47EF _load_ram_FPS
+47F3 _ram_page_7
+4813 _pushrealo1
+4893 _cpyto2fpst
+4897 _cpyto1fpst
+48AF _cpyto2fps1
+48C3 _cpyto2fps2
+48D7 _cpyo2tofpst
+48DB _cpyo6tofpst
+48DF _cpyo1tofpst
+48E3 _cpydetofpst
+48E7 _cpydetohlt
+48EB _cpydetohlc
+48EF _cpyo5tofps2
+48F3 _cpyo2tofpsto1tofps1
+48F7 _cpyo1tofps1
+48FB _cpydetofps1
+48FF _cpydetohl1
+4903 _cpyo2tofps2
+4907 _cpyo3tofps2
+490B _cpyo6tofps2
+490F _cpyo1tofps2
+4913 _cpydetofps2
+4917 _cpydetohl2
+491B _cpyo5tofps3
+491F _cpyo2tofps2o1tofps3
+4923 _cpyo1tofps3
+4927 _cpydetofps3
+492B _cpydetohl3
+492F _cpyo1tofps4
+4933 _cpydetofps4
+4937 _cpydetohl4
+493B _cpyo1tofps6
+493F _cpyo1tofps7
+4943 _cpyo1tofps8
+494F _ask_self_test
+4953 _self_test
+4957 _strlen
+495B _strcpy
+495F _strcat
+4963 _strcmp
+496B _find_bit
+498C _cursorOff
+4994 _cursorOn
+49A0 _reset_MATH
+49B0 _disp_GRAPH
+49DC _flushallmenus
+49E8 _disp_menu
+4A0A _exec_pg1
+4A27 _putmap
+4A2B _putc
+4A33 _dispAHL
+4A37 _puts
+4A3B _putps
+4A5F _newline
+4A7E _clrLCD
+4A82 _clrScrn
+4A86 _clrWindow
+4A8A _clrLine
+4A95 _homeup
+4AA1 _vputmap
+4AA5 _vputs
+4AA9 _vputsn
+4AAD _runindicon
+4AB1 _runindicoff
+4AB5 _clrText
+4B1B _exec_pg2
+4B1F _binopexec1
+4B93 _tofrac
+4B9F _gfudydx
+4C2F _INTOP1
+4C3F _ahl_plus_2_pg3
+4C47 _exec_basic
+4C9F _stoAns
+4CB3 _stoY
+4CBB _stoX
+4CBF _stoOther
+4CDF _rclY
+4CE3 _rclX
+4CE7 _rclVarSym
+4D13 _get_token
+4D1B _get_varname
+4D3F _disp
+4D43 _pause
+4D6F _PDspGrph
+4D73 _horizCmd
+4D77 _vertCmd
+4DAF _unpack_hex
+4E39 _grbufcpy
+4E51 _ILine
+4E59 _IPoint
+4E71 _geqnamea
+4FA8 _set_app_title
+514B _FindAlphaUp
+514F _FindAlphaDn
+515B _dispOP1
+515F _dispDone
+5191 _formReal
+51E9 _CLine
+5209 _get_abs_src_addr
+521D _get_word_ahl
+5221 _set_word_ahl
+5235 _abs_mov10toop1
+5239 _abs_mov10toop1_noset
+523D _abs_mov10b_set_d
+5249 _abs_mov10b
+5241 _abs_movfrop1_set_d
+5245 _abs_movfrop1
+5285 _set_abs_dest_addr
+52B5 _RcPicGrph
+52ED _mm_ldir
+52F1 _mm_lddr
+5369 _get_statvar
+5371 _getky
+5398 _low_battery
+5464 _mov10op2add
+5468 _INTGR
+5470 _MINUS1
+5474 _FPSUB
+5478 _FPADD
+5484 _TIMESPT5
+5488 _FPSQUARE
+548C _FPMULT
+5490 _invop1op2
+5494 _invop1s
+5498 _invop2s
+549C _FRAC
+54A4 _FPRECIP
+54A8 _FPDIV
+54AC _SQROOT
+54B0 _SQROOTP
+54BC _RNDGUARD
+54C0 _ROUND
+54C4 _LNX
+54C8 _LNXP
+54CC _LOGXP
+54D0 _LOGX
+54D4 _ETOX
+54D8 _TENX
+54E0 _SIN
+54E4 _COS
+54E8 _TAN
+54F0 _TANH
+54F4 _COSH
+54F8 _SINH
+5508 _ACOS
+550C _ACOSP
+5510 _ATAN
+5514 _ASIN
+551C _ATANH
+5524 _ASINH
+5528 _ACOSH
+5538 _YTOX
+5544 _randint
+5567 _writeb_inc_ahl
+5577 _convop1
+557B _set_mode
+55a3 _asmComp
+55AA _getkey
+55DA _random
+5643 _vputspace
+569D _get_char
+56A1 _get_vchar
+56EA _call_user_on
+56ED _call_user_off
+56F0 _call_sqrtexpr
+56F3 _call_sqrtparse
+56F6 _call_sqrtexec
+56F9 _call_sqrtform
+56FC _call_sqrtcmdtok
+56FF _call_sqrthome
+5702 _call_sqrtkey
+5705 _call_sqrtgrf
+5718 _exec_pg4
+5714 _exec_pg3
+571C _linkExec
+5730 _exec_assembly
+5732 _errNoSignChng
+575C _instTok
+
+0C000 _kbdScanCode
+0C001 _kbdLGSC
+0C002 _kbdPSC
+0C003 _kbdWUR
+0C004 _kbdDebncCnt
+0C005 _kbdkey
+0C006 _kbdGetKy
+0C007 _keyextend
+0C008 _contrast
+0C009 _APDSubTimer
+0C00A _APDTimer
+0C00B _APDWarmUp
+0C00C _viet
+0C00E _curTime
+0C00F _curRow
+0C010 _curCol
+0C011 _curUnder
+0C012 _undelBufLen
+0C013 _undelBuf
+0C077 _P_tokVarPtr
+0C07A _toklen
+0C07C _TOK_B3
+0C07D _DETOK_H3
+0C07E _MEMPRE_H3
+0C07F _indicMem
+0C087 _indicCounter
+0C088 _indicBusy
+0C089 _OP1
+0C08A _OP1EXPM
+0C08B _OP1EXPL
+0C08C _OP1M
+0C093 _OP1EXT
+0C094 _LOGKP
+0C094 _OP2
+0C095 _OP2EXPM
+0C096 _OP2EXPL
+0C097 _OP2M
+0C09E _OP2EXT
+0C09F _OP3
+0C09F _LOGKM
+0C0A0 _OP3EXPM
+0C0A1 _OP3EXPL
+0C0A2 _OP3M
+0C0A9 _OP3EXT
+0C0AA _CORDFLG1
+0C0AA _OP4
+0C0AB _OP4EXPM
+0C0AC _OP4EXPL
+0C0AD _OP4M
+0C0B4 _OP4EXT
+0C0B5 _EK
+0C0B5 _CORDFLG
+0C0B5 _OP5
+0C0B6 _OP5EXPM
+0C0B6 _SF
+0C0B6 _EL
+0C0B7 _OP5EXPL
+0C0B7 _EM
+0C0B8 _OP5M
+0C0B8 _EMM1
+0C0B9 _EITS
+0C0BA _ENM2
+0C0BB _ENA
+0C0BC _EEN
+0C0BF _OP5EXT
+0C0C0 _EN
+0C0C0 _OP6
+0C0C1 _EJ
+0C0C1 _OP6EXPM
+0C0C2 _OP6EXPL
+0C0C2 _EEI
+0C0C3 _OP6M
+0C0C5 _ELOW
+0C0C6 _EIGH
+0C0CA _OP6EXT
+0C0CC _OP7
+0C0D7 _CPLXTRG
+0C0D7 _IOFLAG
+0C0D8 _P_IMATHPTR1
+0C0DB _P_IMATHPTR2
+0C0DE _P_IMATHPTR3
+0C0E1 _P_IMATHPTR4
+0C0E4 _P_IMATHPTR5
+0C0E7 _CHKDELPTR1
+0C0E7 _P_CHKDELPTR1
+0C0EA _P_CHKDELPTR2
+0C0ED _P_INSDELPTR
+0C0F0 _P_UPDOWNPTR
+0C0F3 _STDRNGSGN
+0C0F4 _POLRNGSGN
+0C0F5 _PARRNGSGN
+0C0F6 _DIFRNDSGN
+0C0F7 _USRRNGSGN
+0C0F8 _STATSGN
+0C0F9 _textShadow
+0C1A1 _textShadCur
+0C1A3 _textShadTop
+0C1A4 _textShadAlph
+0C1A5 _textShadIns
+0C1A6 _textAccent
+0C1A7 _cxMain
+0C1A9 _cxPPutAway
+0C1AB _cxPutAway
+0C1AD _cxRedisp
+0C1AF _cxErrorEP
+0C1B1 _cxSizeWind
+0C1B3 _cxPage
+0C1B4 _CXCURAPP
+0C1B5 _cxPrev
+0C1C4 _monQH
+0C1C5 _monQT
+0C1C6 _monQueue
+0C1D6 _onSP
+0C1D8 _onCheckSum
+0C1DA _promptRow
+0C1DB _promptCol
+0C1DC _promptIns
+0C1DD _promptShift
+0C1DE _promptRet
+0C1E0 _promptValid
+0C1E2 _P_promptTop
+0C1E5 _P_promptCursor
+0C1E8 _P_promptTail
+0C1EB _P_promptBtm
+0C1EE _varType
+0C1EF _varCurrent
+0C1F8 _varFAFlags
+0C1FA _varClass
+0C1FB _catCurrent
+0C1FD _menuActive
+0C1FE _menu2Hilite
+0C1FF _menuSingle
+0C201 _menuAppStack
+0C20D _menuAppPtr
+0C20F _menuAppDepth
+0C210 _menuSysStack
+0C21C _menuSysPtr
+0C21E _menuSysDepth
+0C21F _menuPrvStack
+0C22B _menuPrvPtr
+0C22D _menuPrvDepth
+0C22E _m2i
+0C242 _menuDyn1
+0C26A _menuDyn5
+0C274 _userMenu1
+0C275 _userMenuTitle
+0C27C _userMenu2
+0C284 _userMenu3
+0C28C _userMenu4
+0C294 _userMenu5
+0C29C _userMenuSA
+0C31C _XSTATSAV
+0C324 _ioPrompt
+0C326 _YSTATSAV
+0C330 _FSTATSAV
+0C33A _IOSNDTYP
+0C33B _SNDRECSTATE
+0C33C _IOERRSTATE
+0C33D _HEADER
+0C346 _IODATA
+0C352 _BAKHEADER
+0C35B _TBLRNGSGN
+0C35C _calc_id
+0C37C _penCol
+0C37D _penRow
+0C37E _P_RCLQUEUE
+0C381 _ERRNO
+0C382 _ERRSP
+0C384 _errOffset
+0C386 _ram_to_use
+0C390 _offerr_sav_bc
+0C392 _ABS_SRC_ADDR
+0C395 _ABS_DEST_ADDR
+0C398 _MM_NUM_BYTES
+0C39B _mm_tmp1
+0C39D _mm_tmp2
+0C39F _mm_tmp3
+0C3A1 _mm_tmp4
+0C3A3 _mm_tmp5
+0C3A5 _ram_cache
+0C3E5 _Flags
+0C40A _ram_to_use1
+0C414 _statReg
+0C415 _STATVARS
+0C555 _STCounter
+0C555 _curgstyle
+0C556 _curGY
+0C557 _curGX
+0C558 _curGY2
+0C559 _curGX2
+0C55A _curgstyle_save
+0C55B _curgstylesave
+0C55C _plotflagsave
+0C55D _XMINPTR
+0C55F _XMAXPTR
+0C561 _XSCLPTR
+0C563 _YMINPTR
+0C565 _YMAXPTR
+0C567 _YSCLPTR
+0C569 _DIF1STCURINC
+0C56B _TRACEPLOT
+0C56C _BOXPLOTINFO
+0C56D _SCURINC
+0C56F _CURINC
+0C571 _YPIXEL
+0C572 _ORGXMIN
+0C57C _PANSHIFT
+0C586 _USRRNGSIZE
+0C588 _UTHETMIN
+0C58D _STSP
+0C58D _STRAMStart
+0C592 _UTHETMAX
+0C59C _UTHETSTEP
+0C5A6 _UTPLOT
+0C5B0 _UTMIN
+0C5BA _UTMAX
+0C5C4 _UTSTEP
+0C5CE _UXMIN
+0C5D8 _UXMAX
+0C5E2 _UXSCL
+0C5EC _UYMIN
+0C5F6 _UYMAX
+0C600 _UYSCL
+0C60A _UXRES
+0C614 _XRES_INT
+0C615 _HDERIV
+0C61F _TOL
+0C629 _XFACT
+0C633 _YFACT
+0C63D _DELTAX
+0C647 _DELTAY
+0C651 _SHORTX
+0C65B _SHORTY
+0C665 _FUNRNGSIZE
+0C667 _FLAGSF
+0C668 _XMINF
+0C672 _XMAXF
+0C67C _XSCLF
+0C686 _YMINF
+0C690 _YMAXF
+0C69A _YSCLF
+0C6A4 _LOWER
+0C6AE _UPPER
+0C6B8 _XRES
+0C6C2 _POLRNGSIZE
+0C6C4 _FLAGSPOL
+0C6C5 _THETAMIN
+0C6CF _THETAMAX
+0C6D9 _THETASTEP
+0C6E3 _XMINPOL
+0C6ED _XMAXPOL
+0C6F7 _XSCLPOL
+0C701 _YMINPOL
+0C70B _YMAXPOL
+0C715 _YSCLPOL
+0C71F _PARRNGSIZE
+0C721 _FLAGSPAR
+0C722 _TMINPAR
+0C72C _TMAXPAR
+0C736 _TSTEPPAR
+0C740 _XMINPAR
+0C74A _XMAXPAR
+0C754 _XSCLPAR
+0C75E _YMINPAR
+0C768 _YMAXPAR
+0C772 _YSCLPAR
+0C77C _DIFRNGSIZE
+0C77E _FLAGSDIF
+0C77F _TOLERDIF
+0C789 _TPLOTDIF
+0C793 _TMINDIF
+0C79D _TMAXDIF
+0C7A7 _TSTEPDIF
+0C7B1 _XMINDIF
+0C7BB _XMAXDIF
+0C7C5 _XSCLDIF
+0C7CF _YMINDIF
+0C7D9 _YMAXDIF
+0C7E3 _YSCLDIF
+0C7ED _XAXISDIF
+0C7EE _YAXISDIF
+0C7EF _SLOPEF_EQU
+0C7F0 _DIRF_X
+0C7F1 _DIRF_Y
+0C7F2 _DIRF_TIME
+0C7FC _FRES
+0C806 _INTS
+0C810 _DNEQ
+0C811 _P_XOUTSYM
+0C814 _P_XOUTDAT
+0C817 _P_YOUTSYM
+0C81A _P_YOUTDAT
+0C81D _P_INPUTSYM
+0C820 _P_INPUTDAT
+0C823 _P_FOUTDAT
+0C826 _PREVDATA
+0C862 _PREVDATA_EXT
+0C86C _P1TYPE
+0C86D _SavX1List
+0C876 _SavY1List
+0C87F _SavF1List
+0C888 _P1FRQONOFF
+0C889 _P2TYPE
+0C88A _SavX2List
+0C893 _SavY2List
+0C89C _SavF2List
+0C8A5 _P2FRQONOFF
+0C8A6 _P3TYPE
+0C8A7 _SavX3List
+0C8B0 _SavY3List
+0C8B9 _SavF3List
+0C8C2 _P3FRQONOFF
+0C8C3 _oldtype
+0C8C4 _oldxlist
+0C8CD _oldylist
+0C8D6 _oldflist
+0C8D6 _uppery
+0C8DF _oldonoff
+0C8E0 _tblpsrow
+0C8E1 _tblscroll
+0C8E3 _INPUTDAT_PG0
+0C8ED _TblLine
+0C8F7 _OldTblMin
+0C901 _TBLRNGSIZE
+0C903 _TblMin
+0C90D _TblStep
+0C917 _TABLESGN
+0C918 _TableYPtr
+0C919 _curTblcol
+0C91A _curTblrow
+0C91B _dspTblcol
+0C91C _dspTblrow
+0C91D _higTblcol
+0C91E _higTblrow
+0C920 _TABLEXDATA
+0C920 _TBLMATRIX
+0C95C _TABLEYDATA
+0C9D4 _TABLETEMPLATE
+0C9D5 _SavedEqTok
+0C9D7 _SavedEqNum1
+0C9D8 _SavedEqTok1
+0C9DA _SaveAppFlags
+0C9DB _SaveCurFlags
+0C9DC _SaveCurGstyle
+0C9DD _SaveGraphFlags
+0C9DE _evalflevel
+0C9DF _TmpMatCols
+0C9DF _ES
+0C9E0 _TmpMatRows
+0C9E1 _P_DERIVPTR
+0C9E4 _DTMPThresh
+0C9E6 _ELCPLXLCNT
+0C9E8 _DERIVLEVEL
+0C9E9 _P_DIFFEQPTR
+0C9EB _P_DSOLVPTR
+0C9EE _SOLVAR
+0C9F7 _P_QUADPTR
+0C9FA _plotSScreen
+0CDFA _SEED1
+0CE04 _SEED2
+0CE0E _PARSEVAR
+0CE18 _P_BEGPC
+0CE1B _P_CURPC
+0CE1E _P_ENDPC
+0CE21 _ELCNT
+0CE23 _COLCNT
+0CE24 _ROWCNT
+0CE25 _LCOUNT
+0CE27 _EOS_ASAP_2ND
+0CE28 _EXEC_CONV_SAVE
+0CE2A _LASTENTRYPTR
+0CE2C _LASTENTRYSTK
+0CEAC _numlastentries
+0CEAD _currlastentry
+0CEAE _FREESAVEY
+0CEAF _FREESAVEX
+0CEB0 _STRACESAVE_TYPE
+0CEB1 _STRACESAVE
+0CEB3 _TRACESAVE
+0CEB5 _DIF_T_SAVE
+0CEBF _A_B_SAVE
+0CEC0 _A_B_TYPE
+0CEC1 _GS_DELX
+0CEC2 _GS_D1_YINC
+0CEC3 _GS_D2_YINC
+0CEC4 _GS_DELY
+0CEC5 _GS_MAX_Y_PIX
+0CEC6 _CURRENT_STYLE
+0CEC7 _CL_X1
+0CEC8 _CL_X2
+0CEC9 _CL_Y_DAT
+0CECB _PREV_POINT
+0CECD _RESSAVE
+0CECE _DREQU_X
+0CECF _DREQU_XINIT
+0CED9 _DREQU_Y
+0CEDA _DREQU_YINIT
+0CEE4 _DREQU_XLIST
+0CEE7 _DREQU_YLIST
+0CEEA _DREQU_tLIST
+0CEED _DREQU_COUNT
+0CEEF _GY1
+0CF21 _GX1
+0CF53 _GR1
+0CF85 _GQ1
+0CF8A _EQU_EDIT_SAVE
+0CF8B _FORMULA_BITMAP
+0CFAB _MENUCMD_M2I
+0CFAB _cmdShadow
+0CFC9 _MENUCMD_ITEMS
+0D041 _MENUCMD_NUMROWS
+0D042 _MENUCMD_CURROW
+0D053 _cmdShadCur
+0D055 _cmdShadAlph
+0D056 _cmdShadIns
+0D057 _cmdCursor
+0D059 _P_editTop
+0D05C _P_EDITCURSOR
+0D05F _P_editTail
+0D062 _P_editBtm
+0D065 _curmatcol
+0D066 _curmatrow
+0D067 _curlstrow
+0D069 _numedTbl
+0D069 _curlistel
+0D06A _curlstrowh
+0D06B _higmatcol
+0D06C _higmatrow
+0D06D _higlstrow
+0D06F _maxdsprow
+0D070 _ForCurMat
+0D070 _higlstrowh
+0D072 _ForDspCol
+0D074 _forerrornum
+0D075 _P_editSym
+0D078 _P_editDat
+0D07B _DspMatCol
+0D07C _DspMatRow
+0D07D _TmpMatCol
+0D07E _TmpMatRow
+0D07F _numoflist
+0D080 _num1stlist
+0D081 _NumCurList
+0D082 _STATED_CUT_COL
+0D083 _listnamebuffer
+0D12E _LastName
+0D137 _modeRoot
+0D139 _modeCount
+0D13A _modeItem
+0D13B _modePtr
+0D13D _winTop
+0D13E _winBtm
+0D13F _winLeftEdge
+0D140 _winLeft
+0D142 _winAbove
+0D144 _winRow
+0D146 _winCol
+0D148 _fmtDigits
+0D149 _fmtString
+0D18A _fmtConv
+0D19E _fmtLeft
+0D1A0 _fmtIndex
+0D1A2 _P_fmtMatSym
+0D1A5 _P_fmtMatMem
+0D1A8 _EQS
+0D1AA _LSTINDEX
+0D1AC _LSTSIZE
+0D1AE _EQUINDEX
+0D1B0 _order
+0D1B1 _xnamesav
+0D1BA _ynamesav
+0D1C3 _CustMType
+0D1C3 _MCustM
+0D1C4 _CustMLen
+0D1C5 _CustMSav
+0D1E3 _custmnames
+0D279 _VARSAVECNT
+0D27A _DELADJAMT
+0D27D _TEMPINPUT
+0D27E _TSYMPTR1
+0D280 _TSYMPTR2
+0D282 _P_CHKDELPTR3
+0D285 _P_CHKDELPTR4
+0D288 _P_TEMPMEM
+0D28B _FPBASE
+0D28D _FPS
+0D28F _OPBASE
+0D291 _OPS
+0D293 _PTempCnt
+0D295 _CLEANTMP
+0D297 _P_PTEMP
+0D29A _PTEMP_END
+0D29D _FREE_MEM
+0D2A0 _newdataptr
+0D2A3 _SavBotRow
+0D2B8 _curstatplot
+0D2B9 _curstatplotprompt
+0D2BA _difeqfieldmode
+0D2BB _matedoldtype
+0D2BC _modesave1
+0D2BD _statansfirst
+0D2BF _statanslast
+0D2C1 _statanscur
+0D2C3 _charmap
+0D2CB _altcharmap
+0D2D3 _toktmp1
+0D2D4 _toktmp2
+0D2D5 _IOSAVOP1
+0D2DF _DELVAR_SAV_F
+0D2E0 _DEL_SAV_OP1
+0D2EB _alt_asm_exec_btm
+0D2ED _altlfontptr
+0D2F0 _altsfontptr
+0D2F3 _altonptr
+0D2F6 _altslinkptr
+0D2F9 _alt_ret_status
+0D2FA _alt_ret_jmp_page
+0D2FB _alt_ret_jmp_addr
+0D2FD _alt_int_chksum
+0D2FE _alt_interrupt_exec
+0D3C6 _alt_slink_chksum
+0D3C7 _alt_slink_exec
+0D48F _alt_on_chksum
+0D490 _alt_on_exec
+0D558 _alt_off_chksum
+0D559 _alt_off_exec
+0D621 _altram_end
+0D621 _asm_exec_btm
+0D623 _ASAP_IND
+0D624 _asm_reg_af
+0D625 _asm_reg_a
+0D626 _asm_reg_l
+0D626 _asm_reg_hl
+0D627 _asm_reg_h
+0D628 _asm_reg_bc
+0D628 _asm_reg_c
+0D629 _asm_reg_b
+0D62A _asm_reg_de
+0D62A _asm_reg_e
+0D62B _asm_reg_d
+0D62C _mPrgmMATH
+0D64C _mMath
+0D65A _mMath_asap1
+0D65C _mMath_asap2
+0D65E _mMath_asap3
+0D66C _iASAP1
+0D678 _iASAP2
+0D684 _iASAP3
+0D690 _iASAP4
+0D69C _iASAP5
+0D6A8 _iASAP6
+0D6B4 _iASAP7
+0D6C0 _iASAP8
+0D6CC _iASAP9
+0D6D8 _asapnames
+0D6D8 _asap_nl1
+0D6E1 _asap_nl2
+0D6EA _asap_nl3
+0D6FC _asapvar
+0D706 _tokspell_asap1
+0D706 _tokspelltblptr
+0D708 _tokspell_asap2
+0D70A _tokspell_asap3
+0D70E _numtokens
+0D70E _numtok_asap1
+0D70F _numtok_asap2
+0D710 _numtok_asap3
+0D712 _eostblptr
+0D712 _eostbl_asap1
+0D714 _eostbl_asap2
+0D716 _eostbl_asap3
+0D71A _Amenu_offset
+0D722 _reinstall_asap1
+0D722 _reinstall_vec
+0D724 _reinstall_asap2
+0D726 _reinstall_asap3
+0D72A _asap1_ram
+0D734 _asap2_ram
+0D73E _asap3_ram
+0D748 _checkStart
+0D748 _asm_exec_ram
+
+[flags]
+00    trigflags
+2,00  trigdeg,trigflags
+00    doneflags
+5,00  donePrgm,doneflags
+02    plotflags
+1,02  plotloc,plotflags
+2,02  plotdisp,plotflags
+02    grfmodeflags
+4,02  grffuncm,grfmodeflags
+5,02  grfpolarm,grfmodeflags
+6,02  grfparamm,grfmodeflags
+7,02  grfrecurm,grfmodeflags
+03    graphflags
+0,03  graphdraw,graphflags
+2,03  graphcursor,graphflags
+04    grfdbflags
+0,04  grfdot,grfdbflags
+1,04  grfsimul,grfdbflags
+2,04  grfgrid,grfdbflags
+3,04  grfpolar,grfdbflags
+4,04  grfnocoord,grfdbflags
+5,04  grfnoaxis,grfdbflags
+6,04  grflabel,grfdbflags
+05    textflags
+1,05  textEraseBelow,textflags
+2,05  textScrolled,textflags
+3,05  textInverse,textflags
+06    parsflag
+07    parsflag2
+0,06  numop1,parsflag2
+08    apdflags
+2,08  apdable,apdflags
+3,08  apdlock,apdflags
+4,08  apdwarmstart,apdflags
+09    onflags
+3,09  onRunning,onflags
+4,09  onInterrupt,onflags
+0A    fmtflags
+0,0A  fmtExponent,fmtflags
+1,0A  fmtEng,fmtflags
+2,0A  fmtHex,fmtflags
+3,0A  fmtOct,fmtflags
+4,0A  fmtBin,fmtflags
+0C    curflags
+2,0C  curAble,curflags
+3,0C  curOn,curflags
+4,0C  curLock,curflags
+0D    appflags
+1,0D  appTextSave,appflags
+2,0D  appAutoScroll,appflags
+12    indicflags
+0,12  indicRun,indicflags
+2,12  indicOnly,indicflags
+12    shiftflags
+3,12  shift2nd,shiftflags
+4,12  shiftAlpha,shiftflags
+5,12  shiftLwrAlph,shiftflags
+6,12  shiftALock,shiftflags
+16    asap_cmd_flag
+7,16  ex_asap_cmd,asap_cmd_flag
+17    NewDispf
+6,17  ProgramExecuting,NewDispf
+18    new_grf_flgs
+6,18  textwrite,new_grf_flgs
+1D    statflags
+6,1D  statsvalid,statflags
+1F    anumeditflgs
+2,1F  ex_asm_module,anumeditflgs
+23    exceptionflg
+0,23  alt_font,exceptionflg
+1,23  alt_vfont,exceptionflg
+2,23  alt_int,exceptionflg
+3,23  alt_on,exceptionflg
+4,23  alt_link,exceptionflg
+5,23  alt_sqrt,exceptionflg
+6,23  alt_grphexpr,exceptionflg
+7,23  alt_off,exceptionflg
+24    exceptionflg2
+0,24  alt_parse,exceptionflg2
+1,24  alt_form,exceptionflg2
+2,24  alt_exec,exceptionflg2
+4,24  alt_home,exceptionflg2
+5,24  alt_cmdtok,exceptionflg2
+6,24  alt_key,exceptionflg2
+7,24  alt_grf,exceptionflg2
+28    asm_flag1
+29    asm_flag2
+2A    asm_flag3
+2B    asm_flag4
+2C    asm_flag5
+2D    asm_flag6
+2E    asm_flag7
+
diff --git a/tool/tilem-src/db/Makefile.in b/tool/tilem-src/db/Makefile.in
new file mode 100644
index 0000000..89f981b
--- /dev/null
+++ b/tool/tilem-src/db/Makefile.in
@@ -0,0 +1,59 @@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+bindir = @bindir@
+datadir = @datadir@
+pkgdatadir = @datadir@/tilem2
+mandir = @mandir@
+
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+@SET_MAKE@
+
+AR = @AR@
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+DEFS = @DEFS@
+RANLIB = @RANLIB@
+SHELL = @SHELL@
+
+objects = disasm.o listing.o lstfile.o
+
+compile = $(CC) -I$(top_builddir) -I$(top_srcdir)/emu -I$(srcdir) $(CFLAGS) $(CPPFLAGS) $(DEFS)
+
+all: libtilemdb.a
+
+libtilemdb.a: $(objects)
+	$(AR) cru libtilemdb.a $(objects)
+	$(RANLIB) libtilemdb.a
+
+# Disassembler
+
+disasm.o: disasm.c tilemdb.h ../emu/tilem.h ../config.h
+	$(compile) -c $(srcdir)/disasm.c
+
+# Listing file management
+
+listing.o: listing.c tilemdb.h ../emu/tilem.h ../config.h
+	$(compile) -c $(srcdir)/listing.c
+
+lstfile.o: lstfile.c tilemdb.h ../emu/tilem.h ../config.h
+	$(compile) -c $(srcdir)/lstfile.c
+
+
+clean:
+	rm -f *.o
+	rm -f libtilemdb.a
+
+
+Makefile: Makefile.in $(top_builddir)/config.status
+	cd $(top_builddir) && $(SHELL) ./config.status
+
+$(top_builddir)/config.status: $(top_srcdir)/configure
+	cd $(top_builddir) && $(SHELL) ./config.status --recheck
+
+.PRECIOUS: Makefile $(top_builddir)/config.status
+.PHONY: all clean
diff --git a/tool/tilem-src/db/disasm.c b/tool/tilem-src/db/disasm.c
new file mode 100644
index 0000000..3326d85
--- /dev/null
+++ b/tool/tilem-src/db/disasm.c
@@ -0,0 +1,1084 @@
+/*
+ * libtilemdb - Utilities for debugging Z80 assembly programs
+ *
+ * Copyright (C) 2010 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include "tilemdb.h"
+
+typedef struct _TilemDisasmSymbol {
+	char* name;
+	dword value;
+} TilemDisasmSymbol;
+
+typedef struct _TilemDisasmSymTable {
+	int nsyms;
+	int nsyms_a;
+	TilemDisasmSymbol* syms;
+} TilemDisasmSymTable;
+
+struct _TilemDisasm {
+	TilemDisasmSymTable labels;
+	TilemDisasmSymTable romcalls;
+	TilemDisasmSymTable flags;
+	TilemDisasmSymTable macros;
+};
+
+TilemDisasm* tilem_disasm_new()
+{
+	TilemDisasm* dasm = tilem_new0(TilemDisasm, 1);
+	dasm->labels.syms = NULL;
+	dasm->romcalls.syms = NULL;
+	dasm->flags.syms = NULL;
+	dasm->macros.syms = NULL;
+	return dasm;
+}
+
+static void tilem_disasm_sym_table_free(TilemDisasmSymTable* stab)
+{
+	int i;
+
+	for (i = 0; i < stab->nsyms; i++)
+		tilem_free(stab->syms[i].name);
+	tilem_free(stab->syms);
+}
+
+void tilem_disasm_free(TilemDisasm* dasm)
+{
+	if (!dasm)
+		return;
+
+	tilem_disasm_sym_table_free(&dasm->labels);
+	tilem_disasm_sym_table_free(&dasm->romcalls);
+	tilem_disasm_sym_table_free(&dasm->flags);
+	tilem_disasm_sym_table_free(&dasm->macros);
+	tilem_free(dasm);
+}
+
+/* Find symbol in the given table, if any */
+static TilemDisasmSymbol* find_symbol(const TilemDisasmSymTable* stab,
+				      dword value)
+{
+	int start, end, i;
+
+	start = 0;
+	end = stab->nsyms;
+	while (start < end) {
+		i = (start + end) / 2;
+		if (stab->syms[i].value == value)
+			return &stab->syms[i];
+		else if (stab->syms[i].value <= value)
+			start = i + 1;
+		else
+			end = i;
+	}
+	return NULL;
+}
+
+/* Find previous symbol in the given table, if any */
+static TilemDisasmSymbol* find_prev_symbol(const TilemDisasmSymTable* stab,
+					   dword value)
+{
+	int start, end, i;
+
+	start = 0;
+	end = stab->nsyms;
+	while (start < end) {
+		i = (start + end) / 2;
+		if (stab->syms[i].value <= value)
+			start = i + 1;
+		else
+			end = i;
+	}
+	if (start > 0)
+		return &stab->syms[start - 1];
+	else
+		return NULL;
+}
+
+/* Find symbol with given name */
+static TilemDisasmSymbol* find_symbol_by_name(const TilemDisasmSymTable* stab,
+					      const char* name)
+{
+	int i;
+	for (i = 0; i < stab->nsyms; i++)
+		if (!strcmp(stab->syms[i].name, name))
+			return &stab->syms[i];
+	return NULL;
+}
+
+/* Find a given symbol in the table, or create a new one */
+static TilemDisasmSymbol* add_symbol(TilemDisasmSymTable* stab,
+				     dword value)
+{
+	int start, end, i;
+	TilemDisasmSymbol* syms;
+
+	start = 0;
+	end = stab->nsyms;
+
+	while (start < end) {
+		i = (start + end) / 2;
+		if (stab->syms[i].value == value)
+			return &stab->syms[i];
+		else if (stab->syms[i].value < value)
+			start = i + 1;
+		else
+			end = i;
+	}
+
+	/* insert new label into the array */
+	if (stab->nsyms < stab->nsyms_a) {
+		if (start < stab->nsyms)
+			memmove(&stab->syms[start + 1], &stab->syms[start],
+				((stab->nsyms - start)
+				 * sizeof(TilemDisasmSymbol)));
+	}
+	else {
+		stab->nsyms_a = (stab->nsyms + 1) * 2;
+		syms = tilem_new(TilemDisasmSymbol, stab->nsyms_a);
+		if (start > 0)
+			memcpy(syms, stab->syms,
+			       start * sizeof(TilemDisasmSymbol));
+		if (start < stab->nsyms)
+			memcpy(syms + start + 1, stab->syms + start,
+			       ((stab->nsyms - start)
+				* sizeof(TilemDisasmSymbol)));
+		tilem_free(stab->syms);
+		stab->syms = syms;
+	}
+  
+	stab->nsyms++;
+
+	stab->syms[start].value = value;
+	stab->syms[start].name = NULL;
+	return &stab->syms[start];
+}
+
+/* Remove a symbol from the table */
+static void del_symbol(TilemDisasmSymTable* stab,
+		       TilemDisasmSymbol* sym)
+{
+	int n = sym - stab->syms;
+
+	tilem_free(sym->name);
+
+	if (n < stab->nsyms - 1) {
+		memmove(sym, sym + 1,
+			(stab->nsyms - n - 1) * sizeof(TilemDisasmSymbol));
+	}
+
+	stab->nsyms--;
+}
+
+static void set_symbol(TilemDisasmSymTable* stab,
+		       const char* name, dword value)
+{
+	TilemDisasmSymbol* sym;
+
+	if ((sym = find_symbol_by_name(stab, name))) {
+		if (sym->value == value)
+			return;
+		else
+			del_symbol(stab, sym);
+	}
+
+	sym = add_symbol(stab, value);
+	tilem_free(sym->name);
+	sym->name = tilem_new_atomic(char, strlen(name) + 1);
+	strcpy(sym->name, name);
+}
+
+static char* skipws(char* p)
+{
+	while (*p == ' ' || *p == '\t')
+		p++;
+	return p;
+}
+
+static char* skipwc(char* p)
+{
+	while ((unsigned char) *p > ' ')
+		p++;
+	return p;
+}
+
+static int parse_sym_value(const char* text, dword* value)
+{
+	char* p;
+	dword x;
+
+	if (text[0] >= '0' && text[0] <= '7' && text[1] == ',') {
+		x = strtol(text + 2, &p, 16);
+		*value = 0x1000 + (x << 4) + (text[0] - '0');
+	}
+	else {
+		*value = strtol(text, &p, 16);
+	}
+
+	return (p != text && *p == 0);
+}
+
+static int parse_sym_line(TilemDisasmSymTable* stab, char* line)
+{
+	char *w1end, *w2start, *w2end, *name;
+	dword value;
+
+	if (line[0] == '#' || line[0] == ';')
+		return 1;
+
+	w1end = skipwc(line);
+	w2start = skipws(w1end);
+	w2end = skipwc(w2start);
+
+	if (w1end == line || w2start == w1end || w2end == w2start)
+		return 1;
+	if (*w2end)
+		return 1;
+
+	*w1end = *w2end = 0;
+
+	if (*line >= '0' && *line <= '9') {
+		name = w2start;
+		if (!parse_sym_value(line, &value))
+			return 1;
+	}
+	else {
+		name = line;
+		if (!parse_sym_value(w2start, &value))
+			return 1;
+	}
+
+	set_symbol(stab, name, value);
+	return 0;
+}
+
+int tilem_disasm_read_symbol_file(TilemDisasm* dasm, FILE* symfile)
+{
+	char buf[1024];
+	char* p;
+	TilemDisasmSymTable* curtbl;
+	int status = 1;
+
+	curtbl = &dasm->labels;
+
+	while (fgets(buf, sizeof(buf), symfile)) {
+		p = buf + strlen(buf);
+		while (p != buf && (p[-1] == '\n' || p[-1] == '\r'))
+			p--;
+		*p = 0;
+
+		if (!strcmp(buf, "[labels]"))
+			curtbl = &dasm->labels;
+		else if (!strcmp(buf, "[romcalls]"))
+			curtbl = &dasm->romcalls;
+		else if (!strcmp(buf, "[flags]"))
+			curtbl = &dasm->flags;
+		else if (!strcmp(buf, "[macros]"))
+			curtbl = &dasm->macros;
+		else if (!parse_sym_line(curtbl, buf))
+			status = 0;
+	}
+
+	return status;
+}
+
+void tilem_disasm_set_label(TilemDisasm* dasm, const char* name,
+			     dword value)
+{
+	set_symbol(&dasm->labels, name, value);
+}
+
+int tilem_disasm_get_label(const TilemDisasm* dasm, const char* name,
+			   dword* value)
+{
+	TilemDisasmSymbol* sym = find_symbol_by_name(&dasm->labels, name);
+
+	if (!sym)
+		return 0;
+	else if (value)
+		*value = sym->value;
+	return 1;
+}
+
+const char* tilem_disasm_get_label_at_address(const TilemDisasm* dasm,
+					      dword addr)
+{
+	TilemDisasmSymbol* sym = find_symbol(&dasm->labels, addr);
+
+	if (sym)
+		return sym->name;
+	else
+		return NULL;
+}
+
+typedef struct _TilemDisasmInstruction {
+	int length;
+	const char* pattern;
+} TilemDisasmInstruction;
+
+static const TilemDisasmInstruction insts_main[256] = {
+	{1,"NOP"},       {3,"LD~BC,%w"},  {1,"LD~(BC),A"},   {1,"INC~BC"},
+	{1,"INC~B"},     {1,"DEC~B"},     {2,"LD~B,%b"},     {1,"RLCA"},
+	{1,"EX~AF,AF'"}, {1,"ADD~HL,BC"}, {1,"LD~A,(BC)"},   {1,"DEC~BC"},
+	{1,"INC~C"},     {1,"DEC~C"},     {2,"LD~C,%b"},     {1,"RRCA"},
+	{2,"DJNZ~%r"},   {3,"LD~DE,%w"},  {1,"LD~(DE),A"},   {1,"INC~DE"},
+	{1,"INC~D"},     {1,"DEC~D"},     {2,"LD~D,%b"},     {1,"RLA"},
+	{2,"JR~%r"},     {1,"ADD~HL,DE"}, {1,"LD~A,(DE)"},   {1,"DEC~DE"},
+	{1,"INC~E"},     {1,"DEC~E"},     {2,"LD~E,%b"},     {1,"RRA"},
+	{2,"JR~NZ,%r"},  {3,"LD~HL,%w"},  {3,"LD~(%a),HL"},  {1,"INC~HL"},
+	{1,"INC~H"},     {1,"DEC~H"},     {2,"LD~H,%b"},     {1,"DAA"},
+	{2,"JR~Z,%r"},   {1,"ADD~HL,HL"}, {3,"LD~HL,(%a)"},  {1,"DEC~HL"},
+	{1,"INC~L"},     {1,"DEC~L"},     {2,"LD~L,%b"},     {1,"CPL"},
+	{2,"JR~NC,%r"},  {3,"LD~SP,%w"},  {3,"LD~(%a),A"},   {1,"INC~SP"},
+	{1,"INC~(HL)"},  {1,"DEC~(HL)"},  {2,"LD~(HL),%b"},  {1,"SCF"},
+	{2,"JR~C,%r"},   {1,"ADD~HL,SP"}, {3,"LD~A,(%a)"},   {1,"DEC~SP"},
+	{1,"INC~A"},     {1,"DEC~A"},     {2,"LD~A,%b"},     {1,"CCF"},
+
+	{1,"LD~B,B"},    {1,"LD~B,C"},    {1,"LD~B,D"},    {1,"LD~B,E"},
+	{1,"LD~B,H"},    {1,"LD~B,L"},    {1,"LD~B,(HL)"}, {1,"LD~B,A"},
+	{1,"LD~C,B"},    {1,"LD~C,C"},    {1,"LD~C,D"},    {1,"LD~C,E"},
+	{1,"LD~C,H"},    {1,"LD~C,L"},    {1,"LD~C,(HL)"}, {1,"LD~C,A"},
+	{1,"LD~D,B"},    {1,"LD~D,C"},    {1,"LD~D,D"},    {1,"LD~D,E"},
+	{1,"LD~D,H"},    {1,"LD~D,L"},    {1,"LD~D,(HL)"}, {1,"LD~D,A"},
+	{1,"LD~E,B"},    {1,"LD~E,C"},    {1,"LD~E,D"},    {1,"LD~E,E"},
+	{1,"LD~E,H"},    {1,"LD~E,L"},    {1,"LD~E,(HL)"}, {1,"LD~E,A"},
+	{1,"LD~H,B"},    {1,"LD~H,C"},    {1,"LD~H,D"},    {1,"LD~H,E"},
+	{1,"LD~H,H"},    {1,"LD~H,L"},    {1,"LD~H,(HL)"}, {1,"LD~H,A"},
+	{1,"LD~L,B"},    {1,"LD~L,C"},    {1,"LD~L,D"},    {1,"LD~L,E"},
+	{1,"LD~L,H"},    {1,"LD~L,L"},    {1,"LD~L,(HL)"}, {1,"LD~L,A"},
+	{1,"LD~(HL),B"}, {1,"LD~(HL),C"}, {1,"LD~(HL),D"}, {1,"LD~(HL),E"},
+	{1,"LD~(HL),H"}, {1,"LD~(HL),L"}, {1,"HALT"},      {1,"LD~(HL),A"},
+	{1,"LD~A,B"},    {1,"LD~A,C"},    {1,"LD~A,D"},    {1,"LD~A,E"},
+	{1,"LD~A,H"},    {1,"LD~A,L"},    {1,"LD~A,(HL)"}, {1,"LD~A,A"},
+
+	{1,"ADD~A,B"}, {1,"ADD~A,C"}, {1,"ADD~A,D"},    {1,"ADD~A,E"},
+	{1,"ADD~A,H"}, {1,"ADD~A,L"}, {1,"ADD~A,(HL)"}, {1,"ADD~A,A"},
+	{1,"ADC~A,B"}, {1,"ADC~A,C"}, {1,"ADC~A,D"},    {1,"ADC~A,E"},
+	{1,"ADC~A,H"}, {1,"ADC~A,L"}, {1,"ADC~A,(HL)"}, {1,"ADC~A,A"},
+	{1,"SUB~B"},   {1,"SUB~C"},   {1,"SUB~D"},      {1,"SUB~E"},
+	{1,"SUB~H"},   {1,"SUB~L"},   {1,"SUB~(HL)"},   {1,"SUB~A"},
+	{1,"SBC~A,B"}, {1,"SBC~A,C"}, {1,"SBC~A,D"},    {1,"SBC~A,E"},
+	{1,"SBC~A,H"}, {1,"SBC~A,L"}, {1,"SBC~A,(HL)"}, {1,"SBC~A,A"},
+	{1,"AND~B"},   {1,"AND~C"},   {1,"AND~D"},      {1,"AND~E"},
+	{1,"AND~H"},   {1,"AND~L"},   {1,"AND~(HL)"},   {1,"AND~A"},
+	{1,"XOR~B"},   {1,"XOR~C"},   {1,"XOR~D"},      {1,"XOR~E"},
+	{1,"XOR~H"},   {1,"XOR~L"},   {1,"XOR~(HL)"},   {1,"XOR~A"},
+	{1,"OR~B"},    {1,"OR~C"},    {1,"OR~D"},       {1,"OR~E"},
+	{1,"OR~H"},    {1,"OR~L"},    {1,"OR~(HL)"},    {1,"OR~A"},
+	{1,"CP~B"},    {1,"CP~C"},    {1,"CP~D"},       {1,"CP~E"},
+	{1,"CP~H"},    {1,"CP~L"},    {1,"CP~(HL)"},    {1,"CP~A"},
+
+	{1,"RET~NZ"},      {1,"POP~BC"},   {3,"JP~NZ,%j"},  {3,"JP~%j"},
+	{3,"CALL~NZ,%j"},  {1,"PUSH~BC"},  {2,"ADD~A,%b"},  {1,"RST~%z"},
+	{1,"RET~Z"},       {1,"RET"},      {3,"JP~Z,%j"},   {1,0},
+	{3,"CALL~Z,%j"},   {3,"CALL~%j"},  {2,"ADC~A,%b"},  {1,"RST~%z"},
+	{1,"RET~NC"},      {1,"POP~DE"},   {3,"JP~NC,%j"},  {2,"OUT~(%b),A"},
+	{3,"CALL~NC,%j"},  {1,"PUSH~DE"},  {2,"SUB~%b"},    {1,"RST~%z"},
+	{1,"RET~C"},       {1,"EXX"},      {3,"JP~C,%j"},   {2,"IN~A,(%b)"},
+	{3,"CALL~C,%j"},   {1,0},          {2,"SBC~A,%b"},  {1,"RST~%z"},
+	{1,"RET~PO"},      {1,"POP~HL"},   {3,"JP~PO,%j"},  {1,"EX~(SP),HL"},
+	{3,"CALL~PO,%j"},  {1,"PUSH~HL"},  {2,"AND~%b"},    {1,"RST~%z"},
+	{1,"RET~PE"},      {1,"JP~(HL)"},  {3,"JP~PE,%j"},  {1,"EX~DE,HL"},
+	{3,"CALL~PE,%j"},  {1,0},          {2,"XOR~%b"},    {1,"RST~%z"},
+	{1,"RET~P"},       {1,"POP~AF"},   {3,"JP~P,%j"},   {1,"DI"},
+	{3,"CALL~P,%j"},   {1,"PUSH~AF"},  {2,"OR~%b"},     {1,"RST~%z"},
+	{1,"RET~M"},       {1,"LD~SP,HL"}, {3,"JP~M,%j"},   {1,"EI"},
+	{3,"CALL~M,%j"},   {1,0},          {2,"CP~%b"},     {1,"RST~%z"}};
+
+static const TilemDisasmInstruction insts_ddfd[256] = {
+	{1,0},            {1,0},            {1,0},              {1,0},
+	{1,0},            {1,0},            {1,0},              {1,0},
+	{1,0},            {2,"ADD~%i,BC"},  {1,0},              {1,0},
+	{1,0},            {1,0},            {1,0},              {1,0},
+	{1,0},            {1,0},            {1,0},              {1,0},
+	{1,0},            {1,0},            {1,0},              {1,0},
+	{1,0},            {2,"ADD~%i,DE"},  {1,0},              {1,0},
+	{1,0},            {1,0},            {1,0},              {1,0},
+	{1,0},            {4,"LD~%i,%w"},   {4,"LD~(%a),%i"},   {2,"INC~%i"},
+	{2,"INC~%iH"},    {2,"DEC~%iH"},    {3,"LD~%iH,%b"},    {1,0},
+	{1,0},            {2,"ADD~%i,%i"},  {4,"LD~%i,(%a)"},   {2,"DEC~%i"},
+	{2,"INC~%iL"},    {2,"DEC~%iL"},    {3,"LD~%iL,%b"},    {1,0},
+	{1,0},            {1,0},            {1,0},              {1,0},
+	{3,"INC~(%i%s)"}, {3,"DEC~(%i%s)"}, {4,"LD~(%i%s),%b"}, {1,0},
+	{1,0},            {2,"ADD~%i,SP"},  {1,0},              {1,0},
+	{1,0},            {1,0},            {1,0},              {1,0},
+
+	{1,0},             {1,0},             {1,0},             {1,0},
+	{2,"LD~B,%iH"},    {2,"LD~B,%iL"},    {3,"LD~B,(%i%s)"}, {1,0},
+	{1,0},             {1,0},             {1,0},             {1,0},
+	{2,"LD~C,%iH"},    {2,"LD~C,%iL"},    {3,"LD~C,(%i%s)"}, {1,0},
+	{1,0},             {1,0},             {1,0},             {1,0},
+	{2,"LD~D,%iH"},    {2,"LD~D,%iL"},    {3,"LD~D,(%i%s)"}, {1,0},
+	{1,0},             {1,0},             {1,0},             {1,0},
+	{2,"LD~E,%iH"},    {2,"LD~E,%iL"},    {3,"LD~E,(%i%s)"}, {1,0},
+	{2,"LD~%iH,B"},    {2,"LD~%iH,C"},    {2,"LD~%iH,D"},    {2,"LD~%iH,E"},
+	{2,"LD~%iH,%iH"},  {2,"LD~%iH,%iL"},  {3,"LD~H,(%i%s)"}, {2,"LD~%iH,A"},
+	{2,"LD~%iL,B"},    {2,"LD~%iL,C"},    {2,"LD~%iL,D"},    {2,"LD~%iL,E"},
+	{2,"LD~%iL,%iH"},  {2,"LD~%iL,%iL"},  {3,"LD~L,(%i%s)"}, {2,"LD~%iL,A"},
+	{3,"LD~(%i%s),B"}, {3,"LD~(%i%s),C"}, {3,"LD~(%i%s),D"}, {3,"LD~(%i%s),E"},
+	{3,"LD~(%i%s),H"}, {3,"LD~(%i%s),L"}, {1,0},             {3,"LD~(%i%s),A"},
+	{1,0},             {1,0},             {1,0},             {1,0},
+	{2,"LD~A,%iH"},    {2,"LD~A,%iL"},    {3,"LD~A,(%i%s)"}, {1,0},
+
+	{1,0},           {1,0},           {1,0},              {1,0},
+	{2,"ADD~A,%iH"}, {2,"ADD~A,%iL"}, {3,"ADD~A,(%i%s)"}, {1,0},
+	{1,0},           {1,0},           {1,0},              {1,0},
+	{2,"ADC~A,%iH"}, {2,"ADC~A,%iL"}, {3,"ADC~A,(%i%s)"}, {1,0},
+	{1,0},           {1,0},           {1,0},              {1,0},
+	{2,"SUB~%iH"},   {2,"SUB~%iL"},   {3,"SUB~(%i%s)"},   {1,0},
+	{1,0},           {1,0},           {1,0},              {1,0},
+	{2,"SBC~A,%iH"}, {2,"SBC~A,%iL"}, {3,"SBC~A,(%i%s)"}, {1,0},
+	{1,0},           {1,0},           {1,0},              {1,0},
+	{2,"AND~%iH"},   {2,"AND~%iL"},   {3,"AND~(%i%s)"},   {1,0},
+	{1,0},           {1,0},           {1,0},              {1,0},
+	{2,"XOR~%iH"},   {2,"XOR~%iL"},   {3,"XOR~(%i%s)"},   {1,0},
+	{1,0},           {1,0},           {1,0},              {1,0},
+	{2,"OR~%iH"},    {2,"OR~%iL"},    {3,"OR~(%i%s)"},    {1,0},
+	{1,0},           {1,0},           {1,0},              {1,0},
+	{2,"CP~%iH"},    {2,"CP~%iL"},    {3,"CP~(%i%s)"},    {1,0},
+
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {2,"POP~%i"},   {1,0}, {2,"EX~(SP),%i"},
+	{1,0}, {2,"PUSH~%i"},  {1,0}, {1,0},
+	{1,0}, {2,"JP~(%i)"},  {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0},
+	{1,0}, {2,"LD~SP,%i"}, {1,0}, {1,0},
+	{1,0}, {1,0},          {1,0}, {1,0}};
+
+static const TilemDisasmInstruction insts_cb[256] = {
+	{2,"RLC~B"},  {2,"RLC~C"},  {2,"RLC~D"},     {2,"RLC~E"},
+	{2,"RLC~H"},  {2,"RLC~L"},  {2,"RLC~(HL)"},  {2,"RLC~A"},
+	{2,"RRC~B"},  {2,"RRC~C"},  {2,"RRC~D"},     {2,"RRC~E"},
+	{2,"RRC~H"},  {2,"RRC~L"},  {2,"RRC~(HL)"},  {2,"RRC~A"},
+	{2,"RL~B"},   {2,"RL~C"},   {2,"RL~D"},      {2,"RL~E"},
+	{2,"RL~H"},   {2,"RL~L"},   {2,"RL~(HL)"},   {2,"RL~A"},
+	{2,"RR~B"},   {2,"RR~C"},   {2,"RR~D"},      {2,"RR~E"},
+	{2,"RR~H"},   {2,"RR~L"},   {2,"RR~(HL)"},   {2,"RR~A"},
+	{2,"SLA~B"},  {2,"SLA~C"},  {2,"SLA~D"},     {2,"SLA~E"},
+	{2,"SLA~H"},  {2,"SLA~L"},  {2,"SLA~(HL)"},  {2,"SLA~A"},
+	{2,"SRA~B"},  {2,"SRA~C"},  {2,"SRA~D"},     {2,"SRA~E"},
+	{2,"SRA~H"},  {2,"SRA~L"},  {2,"SRA~(HL)"},  {2,"SRA~A"},
+	{2,"SLIA~B"}, {2,"SLIA~C"}, {2,"SLIA~D"},    {2,"SLIA~E"},
+	{2,"SLIA~H"}, {2,"SLIA~L"}, {2,"SLIA~(HL)"}, {2,"SLIA~A"},
+	{2,"SRL~B"},  {2,"SRL~C"},  {2,"SRL~D"},     {2,"SRL~E"},
+	{2,"SRL~H"},  {2,"SRL~L"},  {2,"SRL~(HL)"},  {2,"SRL~A"},
+
+	{2,"BIT~0,B"}, {2,"BIT~0,C"}, {2,"BIT~0,D"},    {2,"BIT~0,E"},
+	{2,"BIT~0,H"}, {2,"BIT~0,L"}, {2,"BIT~0,(HL)"}, {2,"BIT~0,A"},
+	{2,"BIT~1,B"}, {2,"BIT~1,C"}, {2,"BIT~1,D"},    {2,"BIT~1,E"},
+	{2,"BIT~1,H"}, {2,"BIT~1,L"}, {2,"BIT~1,(HL)"}, {2,"BIT~1,A"},
+	{2,"BIT~2,B"}, {2,"BIT~2,C"}, {2,"BIT~2,D"},    {2,"BIT~2,E"},
+	{2,"BIT~2,H"}, {2,"BIT~2,L"}, {2,"BIT~2,(HL)"}, {2,"BIT~2,A"},
+	{2,"BIT~3,B"}, {2,"BIT~3,C"}, {2,"BIT~3,D"},    {2,"BIT~3,E"},
+	{2,"BIT~3,H"}, {2,"BIT~3,L"}, {2,"BIT~3,(HL)"}, {2,"BIT~3,A"},
+	{2,"BIT~4,B"}, {2,"BIT~4,C"}, {2,"BIT~4,D"},    {2,"BIT~4,E"},
+	{2,"BIT~4,H"}, {2,"BIT~4,L"}, {2,"BIT~4,(HL)"}, {2,"BIT~4,A"},
+	{2,"BIT~5,B"}, {2,"BIT~5,C"}, {2,"BIT~5,D"},    {2,"BIT~5,E"},
+	{2,"BIT~5,H"}, {2,"BIT~5,L"}, {2,"BIT~5,(HL)"}, {2,"BIT~5,A"},
+	{2,"BIT~6,B"}, {2,"BIT~6,C"}, {2,"BIT~6,D"},    {2,"BIT~6,E"},
+	{2,"BIT~6,H"}, {2,"BIT~6,L"}, {2,"BIT~6,(HL)"}, {2,"BIT~6,A"},
+	{2,"BIT~7,B"}, {2,"BIT~7,C"}, {2,"BIT~7,D"},    {2,"BIT~7,E"},
+	{2,"BIT~7,H"}, {2,"BIT~7,L"}, {2,"BIT~7,(HL)"}, {2,"BIT~7,A"},
+
+	{2,"RES~0,B"}, {2,"RES~0,C"}, {2,"RES~0,D"},    {2,"RES~0,E"},
+	{2,"RES~0,H"}, {2,"RES~0,L"}, {2,"RES~0,(HL)"}, {2,"RES~0,A"},
+	{2,"RES~1,B"}, {2,"RES~1,C"}, {2,"RES~1,D"},    {2,"RES~1,E"},
+	{2,"RES~1,H"}, {2,"RES~1,L"}, {2,"RES~1,(HL)"}, {2,"RES~1,A"},
+	{2,"RES~2,B"}, {2,"RES~2,C"}, {2,"RES~2,D"},    {2,"RES~2,E"},
+	{2,"RES~2,H"}, {2,"RES~2,L"}, {2,"RES~2,(HL)"}, {2,"RES~2,A"},
+	{2,"RES~3,B"}, {2,"RES~3,C"}, {2,"RES~3,D"},    {2,"RES~3,E"},
+	{2,"RES~3,H"}, {2,"RES~3,L"}, {2,"RES~3,(HL)"}, {2,"RES~3,A"},
+	{2,"RES~4,B"}, {2,"RES~4,C"}, {2,"RES~4,D"},    {2,"RES~4,E"},
+	{2,"RES~4,H"}, {2,"RES~4,L"}, {2,"RES~4,(HL)"}, {2,"RES~4,A"},
+	{2,"RES~5,B"}, {2,"RES~5,C"}, {2,"RES~5,D"},    {2,"RES~5,E"},
+	{2,"RES~5,H"}, {2,"RES~5,L"}, {2,"RES~5,(HL)"}, {2,"RES~5,A"},
+	{2,"RES~6,B"}, {2,"RES~6,C"}, {2,"RES~6,D"},    {2,"RES~6,E"},
+	{2,"RES~6,H"}, {2,"RES~6,L"}, {2,"RES~6,(HL)"}, {2,"RES~6,A"},
+	{2,"RES~7,B"}, {2,"RES~7,C"}, {2,"RES~7,D"},    {2,"RES~7,E"},
+	{2,"RES~7,H"}, {2,"RES~7,L"}, {2,"RES~7,(HL)"}, {2,"RES~7,A"},
+
+	{2,"SET~0,B"}, {2,"SET~0,C"}, {2,"SET~0,D"},    {2,"SET~0,E"},
+	{2,"SET~0,H"}, {2,"SET~0,L"}, {2,"SET~0,(HL)"}, {2,"SET~0,A"},
+	{2,"SET~1,B"}, {2,"SET~1,C"}, {2,"SET~1,D"},    {2,"SET~1,E"},
+	{2,"SET~1,H"}, {2,"SET~1,L"}, {2,"SET~1,(HL)"}, {2,"SET~1,A"},
+	{2,"SET~2,B"}, {2,"SET~2,C"}, {2,"SET~2,D"},    {2,"SET~2,E"},
+	{2,"SET~2,H"}, {2,"SET~2,L"}, {2,"SET~2,(HL)"}, {2,"SET~2,A"},
+	{2,"SET~3,B"}, {2,"SET~3,C"}, {2,"SET~3,D"},    {2,"SET~3,E"},
+	{2,"SET~3,H"}, {2,"SET~3,L"}, {2,"SET~3,(HL)"}, {2,"SET~3,A"},
+	{2,"SET~4,B"}, {2,"SET~4,C"}, {2,"SET~4,D"},    {2,"SET~4,E"},
+	{2,"SET~4,H"}, {2,"SET~4,L"}, {2,"SET~4,(HL)"}, {2,"SET~4,A"},
+	{2,"SET~5,B"}, {2,"SET~5,C"}, {2,"SET~5,D"},    {2,"SET~5,E"},
+	{2,"SET~5,H"}, {2,"SET~5,L"}, {2,"SET~5,(HL)"}, {2,"SET~5,A"},
+	{2,"SET~6,B"}, {2,"SET~6,C"}, {2,"SET~6,D"},    {2,"SET~6,E"},
+	{2,"SET~6,H"}, {2,"SET~6,L"}, {2,"SET~6,(HL)"}, {2,"SET~6,A"},
+	{2,"SET~7,B"}, {2,"SET~7,C"}, {2,"SET~7,D"},    {2,"SET~7,E"},
+	{2,"SET~7,H"}, {2,"SET~7,L"}, {2,"SET~7,(HL)"}, {2,"SET~7,A"}};
+
+static const TilemDisasmInstruction insts_ddfdcb[256] = {
+	{4,"RLC~B,(%i%s)"},  {4,"RLC~C,(%i%s)"},  {4,"RLC~D,(%i%s)"},  {4,"RLC~E,(%i%s)"},
+	{4,"RLC~H,(%i%s)"},  {4,"RLC~L,(%i%s)"},  {4,"RLC~(%i%s)"},    {4,"RLC~A,(%i%s)"},
+	{4,"RRC~B,(%i%s)"},  {4,"RRC~C,(%i%s)"},  {4,"RRC~D,(%i%s)"},  {4,"RRC~E,(%i%s)"},
+	{4,"RRC~H,(%i%s)"},  {4,"RRC~L,(%i%s)"},  {4,"RRC~(%i%s)"},    {4,"RRC~A,(%i%s)"},
+	{4,"RL~B,(%i%s)"},   {4,"RL~C,(%i%s)"},   {4,"RL~D,(%i%s)"},   {4,"RL~E,(%i%s)"},
+	{4,"RL~H,(%i%s)"},   {4,"RL~L,(%i%s)"},   {4,"RL~(%i%s)"},     {4,"RL~A,(%i%s)"},
+	{4,"RR~B,(%i%s)"},   {4,"RR~C,(%i%s)"},   {4,"RR~D,(%i%s)"},   {4,"RR~E,(%i%s)"},
+	{4,"RR~H,(%i%s)"},   {4,"RR~L,(%i%s)"},   {4,"RR~(%i%s)"},     {4,"RR~A,(%i%s)"},
+	{4,"SLA~B,(%i%s)"},  {4,"SLA~C,(%i%s)"},  {4,"SLA~D,(%i%s)"},  {4,"SLA~E,(%i%s)"},
+	{4,"SLA~H,(%i%s)"},  {4,"SLA~L,(%i%s)"},  {4,"SLA~(%i%s)"},    {4,"SLA~A,(%i%s)"},
+	{4,"SRA~B,(%i%s)"},  {4,"SRA~C,(%i%s)"},  {4,"SRA~D,(%i%s)"},  {4,"SRA~E,(%i%s)"},
+	{4,"SRA~H,(%i%s)"},  {4,"SRA~L,(%i%s)"},  {4,"SRA~(%i%s)"},    {4,"SRA~A,(%i%s)"},
+	{4,"SLIA~B,(%i%s)"}, {4,"SLIA~C,(%i%s)"}, {4,"SLIA~D,(%i%s)"}, {4,"SLIA~E,(%i%s)"},
+	{4,"SLIA~H,(%i%s)"}, {4,"SLIA~L,(%i%s)"}, {4,"SLIA~(%i%s)"},   {4,"SLIA~A,(%i%s)"},
+	{4,"SRL~B,(%i%s)"},  {4,"SRL~C,(%i%s)"},  {4,"SRL~D,(%i%s)"},  {4,"SRL~E,(%i%s)"},
+	{4,"SRL~H,(%i%s)"},  {4,"SRL~L,(%i%s)"},  {4,"SRL~(%i%s)"},    {4,"SRL~A,(%i%s)"},
+
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f"},  {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f"},  {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f"},  {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f"},  {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f"},  {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f"},  {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f"},  {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f*"},
+	{4,"BIT~%f*"}, {4,"BIT~%f*"}, {4,"BIT~%f"},  {4,"BIT~%f*"},
+
+	{4,"RES~0,B,(%i%s)"}, {4,"RES~0,C,(%i%s)"}, {4,"RES~0,D,(%i%s)"}, {4,"RES~0,E,(%i%s)"},
+	{4,"RES~0,H,(%i%s)"}, {4,"RES~0,L,(%i%s)"}, {4,"RES~%f"},         {4,"RES~0,A,(%i%s)"},
+	{4,"RES~1,B,(%i%s)"}, {4,"RES~1,C,(%i%s)"}, {4,"RES~1,D,(%i%s)"}, {4,"RES~1,E,(%i%s)"},
+	{4,"RES~1,H,(%i%s)"}, {4,"RES~1,L,(%i%s)"}, {4,"RES~%f"},         {4,"RES~1,A,(%i%s)"},
+	{4,"RES~2,B,(%i%s)"}, {4,"RES~2,C,(%i%s)"}, {4,"RES~2,D,(%i%s)"}, {4,"RES~2,E,(%i%s)"},
+	{4,"RES~2,H,(%i%s)"}, {4,"RES~2,L,(%i%s)"}, {4,"RES~%f"},         {4,"RES~2,A,(%i%s)"},
+	{4,"RES~3,B,(%i%s)"}, {4,"RES~3,C,(%i%s)"}, {4,"RES~3,D,(%i%s)"}, {4,"RES~3,E,(%i%s)"},
+	{4,"RES~3,H,(%i%s)"}, {4,"RES~3,L,(%i%s)"}, {4,"RES~%f"},         {4,"RES~3,A,(%i%s)"},
+	{4,"RES~4,B,(%i%s)"}, {4,"RES~4,C,(%i%s)"}, {4,"RES~4,D,(%i%s)"}, {4,"RES~4,E,(%i%s)"},
+	{4,"RES~4,H,(%i%s)"}, {4,"RES~4,L,(%i%s)"}, {4,"RES~%f"},         {4,"RES~4,A,(%i%s)"},
+	{4,"RES~5,B,(%i%s)"}, {4,"RES~5,C,(%i%s)"}, {4,"RES~5,D,(%i%s)"}, {4,"RES~5,E,(%i%s)"},
+	{4,"RES~5,H,(%i%s)"}, {4,"RES~5,L,(%i%s)"}, {4,"RES~%f"},         {4,"RES~5,A,(%i%s)"},
+	{4,"RES~6,B,(%i%s)"}, {4,"RES~6,C,(%i%s)"}, {4,"RES~6,D,(%i%s)"}, {4,"RES~6,E,(%i%s)"},
+	{4,"RES~6,H,(%i%s)"}, {4,"RES~6,L,(%i%s)"}, {4,"RES~%f"},         {4,"RES~6,A,(%i%s)"},
+	{4,"RES~7,B,(%i%s)"}, {4,"RES~7,C,(%i%s)"}, {4,"RES~7,D,(%i%s)"}, {4,"RES~7,E,(%i%s)"},
+	{4,"RES~7,H,(%i%s)"}, {4,"RES~7,L,(%i%s)"}, {4,"RES~%f"},         {4,"RES~7,A,(%i%s)"},
+
+	{4,"SET~0,B,(%i%s)"}, {4,"SET~0,C,(%i%s)"}, {4,"SET~0,D,(%i%s)"}, {4,"SET~0,E,(%i%s)"},
+	{4,"SET~0,H,(%i%s)"}, {4,"SET~0,L,(%i%s)"}, {4,"SET~%f"},         {4,"SET~0,A,(%i%s)"},
+	{4,"SET~1,B,(%i%s)"}, {4,"SET~1,C,(%i%s)"}, {4,"SET~1,D,(%i%s)"}, {4,"SET~1,E,(%i%s)"},
+	{4,"SET~1,H,(%i%s)"}, {4,"SET~1,L,(%i%s)"}, {4,"SET~%f"},         {4,"SET~1,A,(%i%s)"},
+	{4,"SET~2,B,(%i%s)"}, {4,"SET~2,C,(%i%s)"}, {4,"SET~2,D,(%i%s)"}, {4,"SET~2,E,(%i%s)"},
+	{4,"SET~2,H,(%i%s)"}, {4,"SET~2,L,(%i%s)"}, {4,"SET~%f"},         {4,"SET~2,A,(%i%s)"},
+	{4,"SET~3,B,(%i%s)"}, {4,"SET~3,C,(%i%s)"}, {4,"SET~3,D,(%i%s)"}, {4,"SET~3,E,(%i%s)"},
+	{4,"SET~3,H,(%i%s)"}, {4,"SET~3,L,(%i%s)"}, {4,"SET~%f"},         {4,"SET~3,A,(%i%s)"},
+	{4,"SET~4,B,(%i%s)"}, {4,"SET~4,C,(%i%s)"}, {4,"SET~4,D,(%i%s)"}, {4,"SET~4,E,(%i%s)"},
+	{4,"SET~4,H,(%i%s)"}, {4,"SET~4,L,(%i%s)"}, {4,"SET~%f"},         {4,"SET~4,A,(%i%s)"},
+	{4,"SET~5,B,(%i%s)"}, {4,"SET~5,C,(%i%s)"}, {4,"SET~5,D,(%i%s)"}, {4,"SET~5,E,(%i%s)"},
+	{4,"SET~5,H,(%i%s)"}, {4,"SET~5,L,(%i%s)"}, {4,"SET~%f"},         {4,"SET~5,A,(%i%s)"},
+	{4,"SET~6,B,(%i%s)"}, {4,"SET~6,C,(%i%s)"}, {4,"SET~6,D,(%i%s)"}, {4,"SET~6,E,(%i%s)"},
+	{4,"SET~6,H,(%i%s)"}, {4,"SET~6,L,(%i%s)"}, {4,"SET~%f"},         {4,"SET~6,A,(%i%s)"},
+	{4,"SET~7,B,(%i%s)"}, {4,"SET~7,C,(%i%s)"}, {4,"SET~7,D,(%i%s)"}, {4,"SET~7,E,(%i%s)"},
+	{4,"SET~7,H,(%i%s)"}, {4,"SET~7,L,(%i%s)"}, {4,"SET~%f"},         {4,"SET~7,A,(%i%s)"}};
+
+static const TilemDisasmInstruction insts_ed[256] = {
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+
+	{2,"IN~B,(C)"}, {2,"OUT~(C),B"}, {2,"SBC~HL,BC"}, {4,"LD~(%a),BC"},
+	{2,"NEG"},      {2,"RETN"},      {2,"IM~0"},      {2,"LD~I,A"},
+	{2,"IN~C,(C)"}, {2,"OUT~(C),C"}, {2,"ADC~HL,BC"}, {4,"LD~BC,(%a)"},
+	{2,"NEG*"},     {2,"RETI"},      {2,"IM~0*"},     {2,"LD~R,A"},
+	{2,"IN~D,(C)"}, {2,"OUT~(C),D"}, {2,"SBC~HL,DE"}, {4,"LD~(%a),DE"},
+	{2,"NEG*"},     {2,"RETN*"},     {2,"IM~1"},      {2,"LD~A,I"},
+	{2,"IN~E,(C)"}, {2,"OUT~(C),E"}, {2,"ADC~HL,DE"}, {4,"LD~DE,(%a)"},
+	{2,"NEG*"},     {2,"RETN*"},     {2,"IM~2"},      {2,"LD~A,R"},
+	{2,"IN~H,(C)"}, {2,"OUT~(C),H"}, {2,"SBC~HL,HL"}, {4,"LD~(%a),HL*"},
+	{2,"NEG*"},     {2,"RETN*"},     {2,"IM~0*"},     {2,"RRD"},
+	{2,"IN~L,(C)"}, {2,"OUT~(C),L"}, {2,"ADC~HL,HL"}, {4,"LD~HL,(%a)*"},
+	{2,"NEG*"},     {2,"RETN*"},     {2,"IM~0*"},     {2,"RLD"},
+	{2,"IN~(C)"},   {2,"OUT~(C),0"}, {2,"SBC~HL,SP"}, {4,"LD~(%a),SP"},
+	{2,"NEG*"},     {2,"RETN*"},     {2,"IM~1*"},     {2,0},
+	{2,"IN~A,(C)"}, {2,"OUT~(C),A"}, {2,"ADC~HL,SP"}, {4,"LD~SP,(%a)"},
+	{2,"NEG*"},     {2,"RETN*"},     {2,"IM~2*"},     {2,0},
+
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,"LDI"},  {2,"CPI"},  {2,"INI"},  {2,"OUTI"},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,"LDD"},  {2,"CPD"},  {2,"IND"},  {2,"OUTD"},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,"LDIR"}, {2,"CPIR"}, {2,"INIR"}, {2,"OTIR"},
+	{2,0},      {2,0},      {2,0},      {2,0},
+	{2,"LDDR"}, {2,"CPDR"}, {2,"INDR"}, {2,"OTDR"},
+	{2,0},      {2,0},      {2,0},      {2,0},
+
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0},
+	{2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}, {2,0}};
+
+/* Count number of bytes of arguments for a given instruction/macro
+   pattern */
+static int pattern_arg_size(const char* pattern)
+{
+	char* p;
+	int count = 0, offs;
+
+	while (*pattern) {
+		if (*pattern != '%')
+			pattern++;
+		else {
+			pattern++;
+
+			if (*pattern >= '0' && *pattern <= '9') {
+				offs = strtol(pattern, &p, 10);
+				pattern = p;
+			}
+			else {
+				offs = count;
+			}
+
+			switch (*pattern) {
+			case 0:
+				pattern--;
+				break;
+
+			case 'b':
+			case 'C':
+			case 'r':
+			case 's':
+				offs++;
+				break;
+
+			case 'a':
+			case 'c':
+			case 'f':
+			case 'j':
+			case 'w':
+				offs += 2;
+				break;
+			}
+
+			pattern++;
+			if (offs > count)
+				count = offs;
+		}
+	}
+
+	return count;
+}
+
+static void get_instruction_info(const TilemDisasm* dasm,
+				 const byte* instr,
+				 int* length, int* argbase,
+				 const char** pattern)
+{
+	const TilemDisasmSymbol* sym;
+	const TilemDisasmInstruction* ii;
+	dword mvalue;
+	int i;
+
+	mvalue = 0;
+	for (i = 0; i < 4; i++) {
+		mvalue = (mvalue << 8) | instr[i];
+		if ((sym = find_symbol(&dasm->macros, mvalue))) {
+			*pattern = sym->name;
+			*length = i + 1 + pattern_arg_size(sym->name);
+			*argbase = i + 1;
+			return;
+		}
+	}
+
+	if (instr[0] == 0xed) {
+		ii = &insts_ed[instr[1]];
+		*argbase = 2;
+	}
+	else if (instr[0] == 0xdd || instr[0] == 0xfd) {
+		if (instr[1] == 0xcb) {
+			ii = &insts_ddfdcb[instr[3]];
+		}
+		else {
+			ii = &insts_ddfd[instr[1]];
+		}
+		*argbase = 2;
+	}
+	else if (instr[0] == 0xcb) {
+		ii = &insts_cb[instr[1]];
+		*argbase = 2;
+	}
+	else {
+		ii = &insts_main[instr[0]];
+		*argbase = 1;
+	}
+
+	*length = ii->length;
+
+	if (ii->pattern) {
+		*pattern = ii->pattern;
+	}
+	else {
+		*argbase = 0;
+		if (ii->length == 1)
+			*pattern = "DB~%b";
+		else
+			*pattern = "DB~%b,%b";
+	}
+}
+
+static void TILEM_ATTR_PRINTF(3, 4)
+printv(char** buf, int* bsize, const char* fmt, ...)
+{
+	va_list ap;
+	int n;
+
+	if (*bsize == 0)
+		return;
+
+	va_start(ap, fmt);
+	n = vsnprintf(*buf, *bsize, fmt, ap);
+	va_end(ap);
+
+	if (n >= *bsize) {
+		*buf += *bsize - 1;
+		**buf = 0;
+		*bsize = 0;
+	}
+	else {
+		*buf += n;
+		**buf = 0;
+		*bsize -= n;
+	}
+}
+
+static void print_byte(char** buf, int* bsize, unsigned int b)
+{
+	printv(buf, bsize, "$%02X", b);
+}
+
+static void print_word(const TilemDisasm* dasm, char** buf, int* bsize,
+		       dword w, int autonum, int autodiff)
+{
+	TilemDisasmSymbol* sym;
+
+	if (autonum && w < 0x100) {
+		printv(buf, bsize, "$%04X", w);
+		return;
+	}
+
+	sym = find_prev_symbol(&dasm->labels, w);
+
+	if (sym && !strcmp(sym->name, "flags")) {
+		w -= sym->value;
+		sym = find_symbol(&dasm->flags, w);
+		if (sym) {
+			printv(buf, bsize, "flags + %s", sym->name);
+		}
+		else {
+			printv(buf, bsize, "flags + $%02X", w);
+		}
+	}
+	else if (sym && w == sym->value) {
+		printv(buf, bsize, "%s", sym->name);
+	}
+	else if (sym && autodiff && w > 0x8000 && w - sym->value < 64) {
+		printv(buf, bsize, "%s + %d", sym->name, w - sym->value);
+	}
+	else {
+		printv(buf, bsize, "$%04X", w);
+	}
+}
+
+static void print_romcall(const TilemDisasm* dasm, char** buf, int* bsize,
+			  dword w)
+{
+	TilemDisasmSymbol* sym;
+
+	sym = find_symbol(&dasm->romcalls, w);
+	if (sym) {
+		printv(buf, bsize, "%s", sym->name);
+	}
+	else {
+		printv(buf, bsize, "$%04X", w);
+	}
+}
+
+static void print_flag(const TilemDisasm* dasm, char** buf, int* bsize,
+		       unsigned int bit, unsigned int offset,
+		       unsigned int prefix)
+{
+	TilemDisasmSymbol* sym;
+	int i;
+
+	if (prefix == 0xfd) {
+		sym = find_symbol(&dasm->flags, 0x1000 + (offset << 4) + bit);
+		if (sym) {
+			for (i = 0; sym->name[i]; i++) {
+				printv(buf, bsize, "%c", sym->name[i]);
+				if (sym->name[i] == ',')
+					printv(buf, bsize, " (IY + ");
+			}
+			printv(buf, bsize, ")");
+			return;
+		}
+
+		sym = find_symbol(&dasm->flags, offset);
+		if (sym) {
+			printv(buf, bsize, "%d, (IY + %s)", bit, sym->name);
+			return;
+		}
+	}
+
+	printv(buf, bsize, "%d, (%s", bit, (prefix == 0xfd ? "IY" : "IX"));
+
+	if (offset & 0x80) {
+		printv(buf, bsize, " - $%02X", 0x100 - offset);
+	}
+	else if (offset) {
+		printv(buf, bsize, " + $%02X", offset);
+	}
+
+	printv(buf, bsize, ")");
+}
+
+static void disassemble_pattern(const TilemDisasm* dasm, const char* pattern,
+				const byte* ibuf, dword pc, int argbase,
+				char** buf, int* bsize)
+{
+	int argidx, offs;
+	char* p;
+	dword w;
+	TilemDisasmSymbol* sym;
+
+	argidx = argbase;
+
+	while (*bsize && *pattern) {
+		if (*pattern == '~')
+			printv(buf, bsize, "\t");
+		else if (*pattern == ',') {
+			printv(buf, bsize, ", ");
+		}
+		else if (*pattern != '%') {
+			printv(buf, bsize, "%c", *pattern);
+		}
+		else {
+			pattern++;
+			if (*pattern >= '0' && *pattern <= '9') {
+				offs = argbase + strtol(pattern, &p, 10);
+				pattern = p;
+			}
+			else {
+				offs = argidx;
+			}
+
+			switch (*pattern) {
+			case 0:
+				pattern--;
+				break;
+
+			case '%':
+				printv(buf, bsize, "%%");
+				break;
+
+			case 'a':
+				/* %a: word value, always an address */
+				w = ibuf[offs] | (ibuf[offs + 1] << 8);
+				print_word(dasm, buf, bsize, w, 0, 1);
+				offs += 2;
+				break;
+
+			case 'b':
+				/* %b: byte value */
+				print_byte(buf, bsize, ibuf[offs]);
+				offs++;
+				break;
+
+			case 'c':
+				/* %c: word value, always a ROM call number */
+				w = ibuf[offs] | (ibuf[offs + 1] << 8);
+				print_romcall(dasm, buf, bsize, w);
+				offs += 2;
+				break;
+
+			case 'C':
+				/* %C: byte value, always a ROM call number */
+				print_romcall(dasm, buf, bsize, ibuf[offs]);
+				offs++;
+				break;
+
+			case 'f':
+				/* %f: flag value */
+				print_flag(dasm, buf, bsize,
+					   (ibuf[offs + 1] >> 3) & 7,
+					   ibuf[offs], ibuf[0]);
+				offs += 2;
+				break;
+
+			case 'i':
+				/* %i: IX or IY by instruction prefix */
+				if (ibuf[0] == 0xdd)
+					printv(buf, bsize, "IX");
+				else
+					printv(buf, bsize, "IY");
+				break;
+
+			case 'j':
+				/* %j: word value, always a jump address */
+				w = ibuf[offs] | (ibuf[offs + 1] << 8);
+				print_word(dasm, buf, bsize, w, 0, 0);
+				offs += 2;
+				break;
+
+			case 'r':
+				/* %r: one-byte PC-relative value */
+				if (ibuf[offs] & 0x80)
+					w = pc + offs - 0xff + ibuf[offs];
+				else
+					w = pc + offs + 1 + ibuf[offs];
+				print_word(dasm, buf, bsize, w, 0, 0);
+				offs++;
+				break;
+
+			case 's':
+				/* %s: one-byte signed displacement */
+				if (ibuf[0] == 0xfd
+				    && (sym = find_symbol(&dasm->flags,
+							  ibuf[offs]))) {
+					printv(buf, bsize, " + %s", sym->name);
+				}
+				else if (ibuf[offs] & 0x80) {
+					printv(buf, bsize, " - ");
+					print_byte(buf, bsize,
+						   0x100 - ibuf[offs]);
+				}
+				else if (ibuf[offs]) {
+					printv(buf, bsize, " + ");
+					print_byte(buf, bsize, ibuf[offs]);
+				}
+				offs++;
+				break;
+
+			case 'w':
+				/* %w: word value */
+				w = ibuf[offs] | (ibuf[offs + 1] << 8);
+				print_word(dasm, buf, bsize, w, 1, 1);
+				offs += 2;
+				break;
+				
+			case 'z':
+				/* %z: RST target address */
+				print_word(dasm, buf, bsize, ibuf[0] & 0x38,
+					   0, 0);
+				break;
+			}
+			if (offs > argidx)
+				argidx = offs;
+		}
+
+		pattern++;
+	}
+}
+
+void tilem_disasm_disassemble(const TilemDisasm* dasm, TilemCalc* calc,
+			      int phys, dword addr, dword* nextaddr,
+			      char* buffer, int bufsize)
+{
+	byte ibuf[64];
+	dword a, addr_l, max;
+	int length, argbase, i;
+	const char* pattern;
+
+	if (phys) {
+		max = calc->hw.romsize + calc->hw.ramsize;
+		for (i = 0; i < 4; i++) {
+			a = (addr + i) % max;
+			ibuf[i] = calc->mem[a];
+		}
+
+		addr_l = (*calc->hw.mem_ptol)(calc, addr);
+		if (addr_l == 0xffffffff)
+			addr_l = (addr & 0x3fff) | 0x4000;
+	}
+	else {
+		max = 0x10000;
+		for (i = 0; i < 4; i++) {
+			a = (addr + i) & 0xffff;
+			ibuf[i] = calc->mem[(*calc->hw.mem_ltop)(calc, a)];
+		}
+
+		addr_l = addr;
+	}
+
+	get_instruction_info(dasm, ibuf, &length, &argbase, &pattern);
+
+	if (phys) {
+		for (i = 0; i < length; i++) {
+			ibuf[i] = calc->mem[addr];
+			addr = (addr + 1) % max;
+		}
+	}
+	else {
+		for (i = 0; i < length; i++) {
+			ibuf[i] = calc->mem[(*calc->hw.mem_ltop)(calc, addr)];
+			addr = (addr + 1) & 0xffff;
+		}
+	}
+
+	if (nextaddr)
+		*nextaddr = addr;
+
+	if (buffer) {
+		disassemble_pattern(dasm, pattern, ibuf, addr_l, argbase,
+				    &buffer, &bufsize);
+	}
+}
diff --git a/tool/tilem-src/db/listing.c b/tool/tilem-src/db/listing.c
new file mode 100644
index 0000000..3936d6c
--- /dev/null
+++ b/tool/tilem-src/db/listing.c
@@ -0,0 +1,379 @@
+/*
+ * libtilemdb - Utilities for debugging Z80 assembly programs
+ *
+ * Copyright (C) 2010 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "tilemdb.h"
+
+struct _TilemListingLineCache {
+	dword minaddr;
+	dword maxaddr;
+	int* order;
+};
+
+static void sort_lines(TilemListing* lst,
+		       int start, int end)
+{
+	int pivot, ncstart, ncend, n;
+	dword addr, pivotaddr;
+	int* order = lst->linecache->order;
+
+	pivot = order[start];
+	pivotaddr = lst->lines[pivot].address;
+
+	ncstart = start + 1;
+	ncend = end;
+
+	while (ncstart < ncend) {
+		n = order[ncstart];
+		addr = lst->lines[n].address;
+		if (addr < pivotaddr) {
+			ncstart++;
+		}
+		else {
+			order[ncstart] = order[ncend - 1];
+			order[ncend - 1] = n;
+			ncend--;
+		}
+	}
+
+	ncstart--;
+	order[start] = order[ncstart];
+	order[ncstart] = pivot;
+
+	if (ncstart > start + 1)
+		sort_lines(lst, start, ncstart);
+	if (end > ncend + 1)
+		sort_lines(lst, ncend, end);
+}
+
+static void ensure_order(TilemListing* lst)
+{
+	int i;
+
+	if (lst->nlines == 0) {
+		lst->linecache->minaddr = 0xffff;
+		lst->linecache->maxaddr = 0;
+	}
+	else if (!lst->linecache->order) {
+		lst->linecache->order = tilem_new_atomic(int, lst->nlines);
+		for (i = 0; i < lst->nlines; i++)
+			lst->linecache->order[i] = i;
+		sort_lines(lst, 0, lst->nlines);
+
+		i = lst->linecache->order[0];
+		lst->linecache->minaddr = lst->lines[i].address;
+		i = lst->linecache->order[lst->nlines - 1];
+		lst->linecache->maxaddr = lst->lines[i].address;
+	}
+}
+
+static void order_destroyed(TilemListing* lst)
+{
+	tilem_free(lst->linecache->order);
+	lst->linecache->order = NULL;
+}
+
+static int first_at_addr(TilemListing* lst, dword addr)
+{
+	int start, end, i;
+
+	start = 0;
+	end = lst->nlines;
+	while (start < end) {
+		i = (start + end) / 2;
+		if (lst->lines[lst->linecache->order[i]].address < addr)
+			start = i + 1;
+		else
+			end = i;
+	}
+	return start;
+}
+
+TilemListing* tilem_listing_new()
+{
+	TilemListing* lst = tilem_new0(TilemListing, 1);
+	lst->lines = NULL;
+	lst->linecache = tilem_new0(TilemListingLineCache, 1);
+	lst->linecache->order = NULL;
+	return lst;
+}
+
+void tilem_listing_free(TilemListing* lst)
+{
+	int i;
+
+	if (!lst)
+		return;
+
+	for (i = 0; i < lst->nlines; i++)
+		tilem_free(lst->lines[i].text);
+	tilem_free(lst->lines);
+	tilem_free(lst->linecache->order);
+	tilem_free(lst->linecache);
+	tilem_free(lst);
+}
+
+void tilem_listing_file_clear(TilemListing* lst)
+{
+	order_destroyed(lst);
+	lst->nlines = 0;
+	lst->nlines_a = 0;
+	tilem_free(lst->lines);
+	lst->lines = NULL;
+}
+
+void tilem_listing_append_line(TilemListing* lst, int srclinenum, dword address,
+			       int depth, int datasize, const byte* data,
+			       const char* text, int is_expansion)
+{
+	TilemListingLine* line;
+	int i;
+
+	order_destroyed(lst);
+	lst->nlines++;
+	if (lst->nlines >= lst->nlines_a) {
+		lst->nlines_a = lst->nlines * 2;
+		lst->lines = tilem_renew(TilemListingLine,
+					 lst->lines, lst->nlines_a);
+	}
+
+	line = &lst->lines[lst->nlines - 1];
+
+	line->listing = lst;
+	line->srclinenum = srclinenum;
+	line->address = address & 0xffff;
+	line->depth = depth;
+
+	if (datasize > TILEM_MAX_LINE_BYTES)
+		datasize = TILEM_MAX_LINE_BYTES;
+
+	line->datasize = datasize;
+	for (i = 0; i < datasize; i++)
+		line->data[i] = data[i];
+
+	if (text) {
+		line->text = tilem_new_atomic(char, strlen(text) + 1);
+		strcpy(line->text, text);
+
+		if ((text[0] >= 'A' && text[0] <= 'Z')
+		    || (text[0] >= 'a' && text[0] <= 'z')
+		    || (text[0] & 0x80))
+			line->is_label = 1;
+		else
+			line->is_label = 0;
+	}
+	else {
+		line->text = NULL;
+		line->is_label = 0;
+	}
+
+	line->is_expansion = is_expansion;
+}
+
+void tilem_listing_get_address_range(TilemListing* lst, dword* min, dword* max)
+{
+	ensure_order(lst);
+	if (min)
+		*min = lst->linecache->minaddr;
+	if (max)
+		*max = lst->linecache->maxaddr;
+}
+
+TilemListingLine* tilem_listing_line_get_next(TilemListingLine* line)
+{
+	if (!line)
+		return NULL;
+	
+	if (line != line->listing->lines + line->listing->nlines)
+		return line + 1;
+	else
+		return NULL;
+}
+
+TilemListingLine* tilem_listing_line_get_prev(TilemListingLine* line)
+{
+	if (!line)
+		return NULL;
+
+	if (line != line->listing->lines)
+		return line - 1;
+	else
+		return NULL;
+
+}
+
+TilemListingLine* tilem_listing_get_loaded_line_at_addr(TilemListing* lst,
+							dword address,
+							TilemCalc* calc,
+							int match_internal)
+{
+	int i;
+	TilemListingLine *line;
+
+	ensure_order(lst);
+	i = first_at_addr(lst, address + 1);
+
+	while (--i >= 0) {
+		line = &lst->lines[lst->linecache->order[i]];
+
+		if (match_internal) {
+			if (line->address + TILEM_MAX_LINE_BYTES <= address)
+				return NULL;
+			else if (line->address + line->datasize <= address)
+				continue;
+		}
+		else {
+			if (line->address != address)
+				return NULL;
+		}
+
+		if (tilem_listing_line_is_loaded(line, calc))
+			return line;
+	}
+	return NULL;
+}
+
+static inline unsigned int getbyte(TilemCalc* calc, dword addr)
+{
+	dword pa = (*calc->hw.mem_ltop)(calc, addr & 0xffff);
+	return calc->mem[pa];
+}
+
+static inline int line_load_count(const TilemListingLine* line, TilemCalc* calc)
+{
+	int i, n;
+
+	for (i = n = 0; i < line->datasize; i++)
+		if (getbyte(calc, line->address + i) == line->data[i])
+			n++;
+	return n;
+}
+
+static inline TilemListingLine* get_next_local(TilemListingLine* line)
+{
+	TilemListingLine* nline = tilem_listing_line_get_next(line);
+
+	if (nline && nline->address != line->address + line->datasize)
+		return NULL;
+	else
+		return nline;
+}
+
+static inline TilemListingLine* get_prev_local(TilemListingLine* line)
+{
+	TilemListingLine* nline = tilem_listing_line_get_prev(line);
+
+	if (nline && nline->address != line->address + line->datasize)
+		return NULL;
+	else
+		return nline;
+}
+
+int tilem_listing_line_is_loaded(TilemListingLine* line, TilemCalc* calc)
+{
+	int nbytes, ngood;
+	TilemListingLine *nline;
+
+	while (line->datasize == 0 && (nline = get_next_local(line)))
+		line = nline;
+
+	if (line_load_count(line, calc) != line->datasize)
+		return 0;
+	else {
+		nbytes = ngood = line->datasize;
+
+		nline = line;
+		while ((nline = get_next_local(nline))
+		       && nline->address < line->address + 32) {
+			nbytes += nline->datasize;
+			ngood += line_load_count(nline, calc);
+		}
+
+		nline = line;
+		while ((nline = get_prev_local(nline))
+		       && line->address < nline->address + 32) {
+			nbytes += nline->datasize;
+			ngood += line_load_count(nline, calc);
+		}
+
+		return (ngood > nbytes / 2);
+	}
+}
+
+static int bptest_listing_line(TilemCalc* calc, dword addr TILEM_ATTR_UNUSED,
+			       void* data)
+{
+	return tilem_listing_line_is_loaded(data, calc);
+}
+
+int tilem_listing_line_add_breakpoint(TilemListingLine* line,
+				      TilemCalc* calc, int bptype,
+				      int match_internal)
+{
+	dword max;
+
+	if (match_internal && line->datasize > 0)
+		max = line->address + line->datasize - 1;
+	else
+		max = line->address;
+
+	return tilem_z80_add_breakpoint(calc, bptype, line->address, max,
+					0xffff, &bptest_listing_line,
+					(void*) line);
+}
+
+static int bptest_listing_int(TilemCalc* calc, dword addr, void* data)
+{
+	TilemListing* lst = data;
+
+	if (tilem_listing_get_loaded_line_at_addr(lst, addr, calc, 1))
+		return 1;
+	else
+		return 0;
+}
+
+static int bptest_listing_top(TilemCalc* calc, dword addr, void* data)
+{
+	TilemListing* lst = data;
+
+	if (tilem_listing_get_loaded_line_at_addr(lst, addr, calc, 0))
+		return 1;
+	else
+		return 0;
+}
+
+int tilem_listing_add_breakpoint(TilemListing* lst, TilemCalc* calc,
+				 int bptype, int match_internal)
+{
+	dword min, max;
+
+	tilem_listing_get_address_range(lst, &min, &max);
+	return tilem_z80_add_breakpoint(calc, bptype, min, max, 0xffff,
+					(match_internal
+					 ? &bptest_listing_int
+					 : &bptest_listing_top), lst);
+}
+
diff --git a/tool/tilem-src/db/lstfile.c b/tool/tilem-src/db/lstfile.c
new file mode 100644
index 0000000..938ce1c
--- /dev/null
+++ b/tool/tilem-src/db/lstfile.c
@@ -0,0 +1,384 @@
+/*
+ * libtilemdb - Utilities for debugging Z80 assembly programs
+ *
+ * Copyright (C) 2010 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include "tilemdb.h"
+
+/* Test if TEXT contains a correctly-formatted number WIDTH characters
+   wide.  If PAD = 0, number is padded on left with zeroes; if PAD =
+   1, number is padded on left with spaces; if PAD = -1, number is
+   padded on right with spaces. */
+static int match_num(const char* text, int width, int pad, int base,
+		     int* value)
+{
+	int start, end;
+	char* p;
+
+	if ((int) strlen(text) < width)
+		return 0;
+
+	start = 0;
+	while (start < width && text[start] == ' ')
+		start++;
+
+	end = width;
+	while (end > start && text[end - 1] == ' ')
+		end--;
+
+	if (start == end)
+		return 0;
+
+	if (pad != 1 && start != 0)
+		return 0;
+
+	if (pad != -1 && end != width)
+		return 0;
+
+	if (pad != 0 && text[start] == '0' && start != end - 1)
+		return 0;
+
+	*value = strtol(text + start, &p, base);
+
+	return (p == text + end);
+}
+
+/* Test if string exactly matches a printf-like format string.
+   %<pad><width>d, %<pad><width>x match decimal and hex integers;
+   %<width>s matches any string of exactly that width. */
+static int match_pattern(const char* text, const char* pattern, int* datasize,
+			 byte* data, ...)
+{
+	int width, pad, value, *ip;
+	char **sp;
+	char *end;
+	va_list ap;
+	int failed = 0, lastdb = 0;
+
+	va_start(ap, data);
+
+	*datasize = 0;
+
+	while (!failed && *pattern) {
+		if (*pattern != '%') {
+			if (*text == *pattern)
+				text++;
+			else
+				failed = 1;
+			pattern++;
+		}
+		else {
+			pattern++;
+			if (*pattern == '0') {
+				pad = 0;
+				pattern++;
+			}
+			else if (*pattern == '-') {
+				pad = -1;
+				pattern++;
+			}
+			else
+				pad = 1;
+
+			width = strtol(pattern, &end, 10);
+			pattern = end;
+			if (!width)
+				width = strlen(text);
+			else if (width > (int) strlen(text)) {
+				failed = 1;
+				break;
+			}
+
+			switch (*pattern) {
+			case '%':
+				if (*text == '%')
+					text++;
+				else
+					failed = 1;
+				break;
+
+			case 'B':
+				if (text[0] == ' ' && text[1] == ' ') {
+					lastdb = 1;
+					text += 2;
+					break;
+				}
+			case 'b':
+				if (match_num(text, width, pad, 16, &value)
+				    && !lastdb) {
+					data[*datasize] = value;
+					(*datasize)++;
+					text += width;
+				}
+				else {
+					failed = 1;
+				}
+				break;
+
+			case 'd':
+				ip = va_arg(ap, int *);
+				if (match_num(text, width, pad, 10, ip)) {
+					text += width;
+				}
+				else {
+					failed = 1;
+				}
+				break;
+
+			case 'x':
+				ip = va_arg(ap, int *);
+				if (match_num(text, width, pad, 16, ip)) {
+					text += width;
+				}
+				else {
+					failed = 1;
+				}
+				break;
+
+			case 's':
+				sp = va_arg(ap, char **);
+				*sp = (char*) text;
+				text += width;
+				break;
+			}
+			pattern++;
+		}
+	}
+
+	va_end(ap);
+
+	return (!failed && *text == 0);
+}
+
+static int get_tasm_depth(const char* s)
+{
+	if (!strncmp(s, "+++", 3))
+		return 4;
+	else if (!strncmp(s, "++ ", 3))
+		return 3;
+	else if (!strncmp(s, "+  ", 3))
+		return 2;
+	else if (!strncmp(s, "   ", 3))
+		return 1;
+	else
+		return 0;
+}
+
+static int parse_listing(const char* line, int* linenum, int* addr, int* depth,
+			 int* datasize, byte* data, char** text, int* isexp,
+			 const TilemListingLine* prevline)
+{
+	int dummy;
+	char *p, *q;
+
+	/* tasm */
+
+	if (match_pattern(line, "%04d%3s%04x%1s%02B %02B %02B %02B %s",
+			  datasize, data, linenum, &p, addr, &q, text)) {
+		*depth = get_tasm_depth(p);
+		*isexp = 0;
+		return 1;
+	}
+	if (match_pattern(line, "%04d%3s%04x%1s%02b %02b %02b ",
+			  datasize, data, linenum, &p, addr, &q)
+	    || match_pattern(line, "%04d%3s%04x%1s%02b %02b ",
+			     datasize, data, linenum, &p, addr, &q)
+	    || match_pattern(line, "%04d%3s%04x%1s%02b ",
+			     datasize, data, linenum, &p, addr, &q)) {
+		*depth = get_tasm_depth(p);
+		*text = NULL;
+		*isexp = 0;
+		return 1;
+	}
+
+	/* zmasm */
+
+	if (match_pattern(line, "%08x %02B %02B %02B %02B %02B %02B   %4s %5d %s",
+			  datasize, data, addr, &p, linenum, text)
+	    && p[0] >= 'A' && p[0] <= 'Z') {
+		*depth = p[0] - 'A' + 1;
+		*isexp = (p[1] == '+');
+		return 1;
+	}
+	if (match_pattern(line, "%08x %02B %02B %02B %02B %02B %02B   %4s %5d",
+			  datasize, data, addr, &p, linenum)
+	    && p[0] >= 'A' && p[0] <= 'Z') {
+		*text = NULL;
+		*depth = p[0] - 'A' + 1;
+		*isexp = (p[1] == '+');
+		return 1;
+	}
+	if (match_pattern(line, "                             %4s %5d %s",
+			  datasize, data, &p, linenum, text)
+	    && p[0] >= 'A' && p[0] <= 'Z') {
+		*addr = prevline->address + prevline->datasize;
+		*depth = p[0] - 'A' + 1;
+		*isexp = (p[1] == '+');
+		return 1;
+	}
+	if (match_pattern(line, "                   %08x  %4s %5d %s",
+			  datasize, data, &dummy, &p, linenum, text)
+	    && p[0] >= 'A' && p[0] <= 'Z') {
+		*addr = prevline->address + prevline->datasize;
+		*depth = p[0] - 'A' + 1;
+		*isexp = (p[1] == '+');
+		return 1;
+	}
+
+	/* spasm - old and new versions */
+
+	if (match_pattern(line, "%5d %04x: %02b %02b %02b %02b %s",
+			  datasize, data, linenum, addr, text)
+	    || match_pattern(line, "%5d %04x: %02b %02b %02b -  %s",
+			     datasize, data, linenum, addr, text)
+	    || match_pattern(line, "%5d %04x: %02b %02b -  -  %s",
+			     datasize, data, linenum, addr, text)
+	    || match_pattern(line, "%5d %04x: %02b -  -  -  %s",
+			     datasize, data, linenum, addr, text)
+	    || match_pattern(line, "%5d %04x: -  -  -  -  %s",
+			     datasize, data, linenum, addr, text)
+
+	    || match_pattern(line, "%5d %02x:%04x %02b %02b %02b %02b %s",
+			     datasize, data, linenum, &dummy, addr, text)
+	    || match_pattern(line, "%5d %02x:%04x %02b %02b %02b -  %s",
+			     datasize, data, linenum, &dummy, addr, text)
+	    || match_pattern(line, "%5d %02x:%04x %02b %02b -  -  %s",
+			     datasize, data, linenum, &dummy, addr, text)
+	    || match_pattern(line, "%5d %02x:%04x %02b -  -  -  %s",
+			     datasize, data, linenum, &dummy, addr, text)
+	    || match_pattern(line, "%5d %02x:%04x -  -  -  -  %s",
+			     datasize, data, linenum, &dummy, addr, text)) {
+		*depth = *isexp = 0;
+		return 1;
+	}
+	if (match_pattern(line, "            %02b %02b %02b %02b %s",
+			  datasize, data, text)
+	    || match_pattern(line, "            %02b %02b %02b -  %s",
+			     datasize, data, text)
+	    || match_pattern(line, "            %02b %02b -  -  %s",
+			     datasize, data, text)
+	    || match_pattern(line, "            %02b -  -  -  %s",
+			     datasize, data, text)
+
+	    || match_pattern(line, "              %02b %02b %02b %02b %s",
+			     datasize, data, text)
+	    || match_pattern(line, "              %02b %02b %02b -  %s",
+			     datasize, data, text)
+	    || match_pattern(line, "              %02b %02b -  -  %s",
+			     datasize, data, text)
+	    || match_pattern(line, "              %02b -  -  -  %s",
+			     datasize, data, text)) {
+		*linenum = prevline->srclinenum;
+		*addr = prevline->address + prevline->datasize;
+		*depth = *isexp = 0;
+		return 1;
+	}
+
+	/* tpasm or miniasm */
+
+	if (match_pattern(line, "%-5d %08x %02B %02B %02B %02B %02B %2s%s",
+			  datasize, data, linenum, addr, &p, text)
+	    || match_pattern(line, "%5d %08x %02B %02B %02B %02B %02B %2s%s",
+			     datasize, data, linenum, addr, &p, text)) {
+		*depth = 0;
+		*isexp = (*p == 'm' || *p == 'a');
+		return 1;
+	}
+	if (match_pattern(line, "%-5d %08x (%08x)     %2s%s",
+			  datasize, data, linenum, addr, &dummy, &p, text)
+	    || match_pattern(line, "%5d %08x (%08x)     %2s%s",
+			     datasize, data, linenum, addr, &dummy, &p, text)) {
+		*depth = 0;
+		*isexp = (*p == 'm' || *p == 'a');
+		return 1;
+	}
+	if (match_pattern(line, "               %02B %02B %02B %02B %02B %1s",
+			  datasize, data, &p)) {
+		*text = NULL;
+		*linenum = prevline->srclinenum;
+		*addr = prevline->address + prevline->datasize;
+		*depth = 0;
+		*isexp = (*p == 'm' || *p == 'a');
+		return 1;
+	}
+	if (match_pattern(line, "               %02b %02b %02b %02b %02b",
+			  datasize, data)
+	    || match_pattern(line, "               %02b %02b %02b %02b",
+			     datasize, data)
+	    || match_pattern(line, "               %02b %02b %02b",
+			     datasize, data)
+	    || match_pattern(line, "               %02b %02b",
+			     datasize, data)
+	    || match_pattern(line, "               %02b",
+			     datasize, data)) {
+		*text = NULL;
+		*linenum = prevline->srclinenum;
+		*addr = prevline->address + prevline->datasize;
+		*depth = *isexp = 0;
+		return 1;
+	}
+
+	printf("***\t%s\n", line);
+
+	return 0;
+}
+
+int tilem_listing_read_file(TilemListing* lst, FILE* lstfile)
+{
+	char buf[1024];
+	int linenum, addr, depth, isexp, datasize;
+	byte data[TILEM_MAX_LINE_BYTES];
+	char *text;
+	int i, status = 1;
+	TilemListingLine prevline;
+
+	prevline.text = NULL;
+	prevline.srclinenum = 0;
+	prevline.address = 0xffffffff;
+	prevline.depth = 0;
+	prevline.datasize = 0;
+
+	while (fgets(buf, sizeof(buf), lstfile)) {
+		i = strlen(buf);
+		while (i > 0 && (buf[i - 1] == '\r' || buf[i - 1] == '\n'))
+			i--;
+		buf[i] = 0;
+    
+		if (parse_listing(buf, &linenum, &addr, &depth, &datasize,
+				  data, &text, &isexp, &prevline)) {
+
+			if (linenum)
+				status = 0;
+
+			tilem_listing_append_line(lst, linenum, addr, depth,
+						  datasize, data, text, isexp);
+			prevline = lst->lines[lst->nlines - 1];
+		}
+	}
+
+	return status;
+}
+
diff --git a/tool/tilem-src/db/tilemdb.h b/tool/tilem-src/db/tilemdb.h
new file mode 100644
index 0000000..daef3dc
--- /dev/null
+++ b/tool/tilem-src/db/tilemdb.h
@@ -0,0 +1,149 @@
+/*
+ * libtilemdb - Utilities for debugging Z80 assembly programs
+ *
+ * Copyright (C) 2010 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEMDB_H
+#define _TILEMDB_H
+
+#include <tilem.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Disassembler */
+
+typedef struct _TilemDisasm TilemDisasm;
+
+/* Create a new disassembly context. */
+TilemDisasm* tilem_disasm_new(void);
+
+/* Free a disassembly context. */
+void tilem_disasm_free(TilemDisasm* dasm);
+
+/* Read symbols from SYMFILE. */
+int tilem_disasm_read_symbol_file(TilemDisasm* dasm, FILE* symfile);
+
+/* Set symbol NAME to value VALUE. */
+void tilem_disasm_set_label(TilemDisasm* dasm, const char* name,
+			    dword value);
+
+/* Check if symbol NAME is defined.  If symbol is defined and VALUE is
+   non-null, set *VALUE to symbol's value. */
+int tilem_disasm_get_label(const TilemDisasm* dasm, const char* name,
+			   dword* value);
+
+/* Check if a label is defined at the given address. */
+const char* tilem_disasm_get_label_at_address(const TilemDisasm* dasm,
+					      dword addr);
+
+/* Disassemble a line starting at address ADDR.  Store text (up to
+   BUFSIZE characters) in BUFFER, and set *NEXTADDR to the address of
+   the following line.  If PHYS is 0, use logical addresses; otherwise
+   use physical addresses. */
+void tilem_disasm_disassemble(const TilemDisasm* dasm, TilemCalc* calc,
+			      int phys, dword addr, dword* nextaddr,
+			      char* buffer, int bufsize);
+
+
+/* Assembly listing files */
+
+typedef struct _TilemListing TilemListing;
+typedef struct _TilemListingLine TilemListingLine;
+typedef struct _TilemListingLineCache TilemListingLineCache;
+
+#define TILEM_MAX_LINE_BYTES 6
+
+struct _TilemListingLine {
+	TilemListing* listing;		 /* Listing to which this line
+					    belongs */
+	char* text;			 /* Text of source line */
+	int srclinenum;			 /* Line number in original
+					    source file */
+	dword address;			 /* Address */
+	int depth;			 /* Source file inclusion
+					    depth */
+	byte datasize;			 /* Number of data bytes */
+	unsigned is_label : 1;		 /* = 1 if line appears to
+					    contain a label */
+	unsigned is_expansion : 1;	 /* = 1 if line is part of a
+					    macro expansion */
+	byte data[TILEM_MAX_LINE_BYTES]; /* Data bytes on this line */
+};
+
+struct _TilemListing {
+	int nlines;
+	int nlines_a;
+	TilemListingLine* lines;
+	TilemListingLineCache* linecache;
+};
+
+/* Create new assembly listing. */
+TilemListing* tilem_listing_new(void);
+
+/* Free listing data. */
+void tilem_listing_free(TilemListing* lst);
+
+/* Clear listing file contents. */
+void tilem_listing_clear(TilemListing* lst);
+
+/* Add a line to the end of the listing file. */
+void tilem_listing_append_line(TilemListing* lst, int srclinenum, dword address,
+			       int depth, int datasize, const byte* data,
+			       const char* text, int is_expansion);
+
+/* Calculate minimum and maximum address used by a listing file. */
+void tilem_listing_get_address_range(TilemListing* lst, dword* min, dword* max);
+
+/* Get next line, if any. */
+TilemListingLine* tilem_listing_line_get_next(TilemListingLine* line);
+
+/* Get previous line, if any. */
+TilemListingLine* tilem_listing_line_get_prev(TilemListingLine* line);
+
+/* Find the line (if any) currently loaded at the given address.  If
+   MATCH_INTERNAL = 0, find only lines that begin at that address. */
+TilemListingLine* tilem_listing_get_loaded_line_at_addr(TilemListing* lst,
+							dword address,
+							TilemCalc* calc,
+							int match_internal);
+
+/* Check if given line is currently loaded (and mapped into Z80 memory
+   space.) */
+int tilem_listing_line_is_loaded(TilemListingLine* line, TilemCalc* calc);
+
+/* Set a breakpoint to be triggered on the given line. */
+int tilem_listing_line_add_breakpoint(TilemListingLine* line,
+				      TilemCalc* calc, int bptype,
+				      int match_internal);
+
+/* Set a breakpoint to be triggered on any line in the listing. */
+int tilem_listing_add_breakpoint(TilemListing* lst, TilemCalc* calc,
+				 int bptype, int match_internal);
+
+/* Read assembly listing from LSTFILE. */
+int tilem_listing_read_file(TilemListing* lst, FILE* lstfile);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tool/tilem-src/emu/Makefile.in b/tool/tilem-src/emu/Makefile.in
new file mode 100644
index 0000000..915d6d8
--- /dev/null
+++ b/tool/tilem-src/emu/Makefile.in
@@ -0,0 +1,218 @@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+bindir = @bindir@
+datadir = @datadir@
+pkgdatadir = @datadir@/tilem2
+mandir = @mandir@
+
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+@SET_MAKE@
+
+AR = @AR@
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+DEFS = @DEFS@
+OPT_CFLAGS = @OPT_CFLAGS@
+RANLIB = @RANLIB@
+SHELL = @SHELL@
+
+core_objects = calcs.o z80.o state.o rom.o flash.o link.o keypad.o lcd.o \
+	cert.o md5.o timers.o monolcd.o graylcd.o grayimage.o graycolor.o
+
+x7_objects = x7_init.o x7_io.o x7_memory.o x7_subcore.o
+x1_objects = x1_init.o x1_io.o x1_memory.o x1_subcore.o
+x2_objects = x2_init.o x2_io.o x2_memory.o x2_subcore.o
+x3_objects = x3_init.o x3_io.o x3_memory.o x3_subcore.o
+xp_objects = xp_init.o xp_io.o xp_memory.o xp_subcore.o
+xs_objects = xs_init.o xs_io.o xs_memory.o xs_subcore.o
+x4_objects = x4_init.o x4_io.o x4_memory.o x4_subcore.o
+xz_objects = xz_init.o xz_io.o xz_memory.o xz_subcore.o
+xn_objects = xn_init.o xn_io.o xn_memory.o xn_subcore.o
+x5_objects = x5_init.o x5_io.o x5_memory.o x5_subcore.o
+x6_objects = x6_init.o x6_io.o x6_memory.o x6_subcore.o
+
+objects = $(core_objects) $(x7_objects) $(x1_objects) $(x2_objects) \
+	$(x3_objects) $(xp_objects) $(xs_objects) $(x4_objects) $(xz_objects) \
+	$(xn_objects) $(x5_objects) $(x6_objects)
+
+compile = $(CC) -I$(top_builddir) -I$(srcdir) $(CFLAGS) $(CPPFLAGS) $(DEFS) $(OPT_CFLAGS)
+
+all: libtilemcore.a
+
+libtilemcore.a: $(objects)
+	$(AR) cru libtilemcore.a $(objects)
+	$(RANLIB) libtilemcore.a
+
+# Main emulator core
+
+calcs.o: calcs.c tilem.h z80.h ../config.h
+	$(compile) -c $(srcdir)/calcs.c
+z80.o: z80.c z80.h z80cmds.h z80main.h z80cb.h z80ddfd.h z80ed.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/z80.c
+state.o: state.c tilem.h z80.h ../config.h
+	$(compile) -c $(srcdir)/state.c
+rom.o: rom.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/rom.c
+flash.o: flash.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/flash.c
+link.o: link.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/link.c
+keypad.o: keypad.c tilem.h scancodes.h ../config.h
+	$(compile) -c $(srcdir)/keypad.c
+lcd.o: lcd.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/lcd.c
+cert.o: cert.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/cert.c
+md5.o: md5.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/md5.c
+timers.o: timers.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/timers.c
+monolcd.o: monolcd.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/monolcd.c
+graylcd.o: graylcd.c tilem.h graylcd.h ../config.h
+	$(compile) -c $(srcdir)/graylcd.c
+grayimage.o: grayimage.c tilem.h graylcd.h ../config.h
+	$(compile) -c $(srcdir)/grayimage.c
+graycolor.o: graycolor.c tilem.h ../config.h
+	$(compile) -c $(srcdir)/graycolor.c
+
+# TI-73
+
+x7_init.o: x7/x7_init.c x7/x7.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x7/x7_init.c
+x7_io.o: x7/x7_io.c x7/x7.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x7/x7_io.c
+x7_memory.o: x7/x7_memory.c x7/x7.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x7/x7_memory.c
+x7_subcore.o: x7/x7_subcore.c x7/x7.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x7/x7_subcore.c
+
+# TI-81
+
+x1_init.o: x1/x1_init.c x1/x1.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x1/x1_init.c
+x1_io.o: x1/x1_io.c x1/x1.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x1/x1_io.c
+x1_memory.o: x1/x1_memory.c x1/x1.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x1/x1_memory.c
+x1_subcore.o: x1/x1_subcore.c x1/x1.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x1/x1_subcore.c
+
+# TI-82
+
+x2_init.o: x2/x2_init.c x2/x2.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x2/x2_init.c
+x2_io.o: x2/x2_io.c x2/x2.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x2/x2_io.c
+x2_memory.o: x2/x2_memory.c x2/x2.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x2/x2_memory.c
+x2_subcore.o: x2/x2_subcore.c x2/x2.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x2/x2_subcore.c
+
+# TI-83
+
+x3_init.o: x3/x3_init.c x3/x3.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x3/x3_init.c
+x3_io.o: x3/x3_io.c x3/x3.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x3/x3_io.c
+x3_memory.o: x3/x3_memory.c x3/x3.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x3/x3_memory.c
+x3_subcore.o: x3/x3_subcore.c x3/x3.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x3/x3_subcore.c
+
+# TI-83 Plus
+
+xp_init.o: xp/xp_init.c xp/xp.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xp/xp_init.c
+xp_io.o: xp/xp_io.c xp/xp.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xp/xp_io.c
+xp_memory.o: xp/xp_memory.c xp/xp.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xp/xp_memory.c
+xp_subcore.o: xp/xp_subcore.c xp/xp.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xp/xp_subcore.c
+
+# TI-83 Plus SE
+
+xs_init.o: xs/xs_init.c xs/xs.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xs/xs_init.c
+xs_io.o: xs/xs_io.c xs/xs.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xs/xs_io.c
+xs_memory.o: xs/xs_memory.c xs/xs.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xs/xs_memory.c
+xs_subcore.o: xs/xs_subcore.c xs/xs.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xs/xs_subcore.c
+
+# TI-84 Plus
+
+x4_init.o: x4/x4_init.c x4/x4.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x4/x4_init.c
+x4_io.o: x4/x4_io.c x4/x4.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x4/x4_io.c
+x4_memory.o: x4/x4_memory.c x4/x4.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x4/x4_memory.c
+x4_subcore.o: x4/x4_subcore.c x4/x4.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x4/x4_subcore.c
+
+# TI-84 Plus SE
+
+xz_init.o: xz/xz_init.c xz/xz.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xz/xz_init.c
+xz_io.o: xz/xz_io.c xz/xz.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xz/xz_io.c
+xz_memory.o: xz/xz_memory.c xz/xz.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xz/xz_memory.c
+xz_subcore.o: xz/xz_subcore.c xz/xz.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xz/xz_subcore.c
+
+# TI-Nspire 84 Plus emulator
+
+xn_init.o: xn/xn_init.c xn/xn.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xn/xn_init.c
+xn_io.o: xn/xn_io.c xn/xn.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xn/xn_io.c
+xn_memory.o: xn/xn_memory.c xn/xn.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xn/xn_memory.c
+xn_subcore.o: xn/xn_subcore.c xn/xn.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/xn/xn_subcore.c
+
+# TI-85
+
+x5_init.o: x5/x5_init.c x5/x5.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x5/x5_init.c
+x5_io.o: x5/x5_io.c x5/x5.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x5/x5_io.c
+x5_memory.o: x5/x5_memory.c x5/x5.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x5/x5_memory.c
+x5_subcore.o: x5/x5_subcore.c x5/x5.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x5/x5_subcore.c
+
+# TI-86
+
+x6_init.o: x6/x6_init.c x6/x6.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x6/x6_init.c
+x6_io.o: x6/x6_io.c x6/x6.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x6/x6_io.c
+x6_memory.o: x6/x6_memory.c x6/x6.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x6/x6_memory.c
+x6_subcore.o: x6/x6_subcore.c x6/x6.h tilem.h ../config.h
+	$(compile) -c $(srcdir)/x6/x6_subcore.c
+
+
+clean:
+	rm -f *.o
+	rm -f libtilemcore.a
+
+
+Makefile: Makefile.in $(top_builddir)/config.status
+	cd $(top_builddir) && $(SHELL) ./config.status
+
+$(top_builddir)/config.status: $(top_srcdir)/configure
+	cd $(top_builddir) && $(SHELL) ./config.status --recheck
+
+.PRECIOUS: Makefile $(top_builddir)/config.status
+.PHONY: clean all
diff --git a/tool/tilem-src/emu/calcs.c b/tool/tilem-src/emu/calcs.c
new file mode 100644
index 0000000..b251f60
--- /dev/null
+++ b/tool/tilem-src/emu/calcs.c
@@ -0,0 +1,186 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "tilem.h"
+#include "z80.h"
+
+extern const TilemHardware hardware_ti73, hardware_ti76,
+	hardware_ti81, hardware_ti82, hardware_ti83,
+	hardware_ti83p, hardware_ti83pse, hardware_ti84p,
+	hardware_ti84pse, hardware_ti84pns,
+	hardware_ti85, hardware_ti86;
+
+const TilemHardware* hwmodels[] = {
+	&hardware_ti73,
+	&hardware_ti76,
+	&hardware_ti81,
+	&hardware_ti82,
+	&hardware_ti83,
+	&hardware_ti83p,
+	&hardware_ti83pse,
+	&hardware_ti84p,
+	&hardware_ti84pse,
+	&hardware_ti84pns,
+	&hardware_ti85,
+	&hardware_ti86 };
+
+#define NUM_MODELS (sizeof(hwmodels) / sizeof(TilemHardware*))
+
+void tilem_get_supported_hardware(const TilemHardware*** models,
+				  int* nmodels)
+{
+	*models = hwmodels;
+	*nmodels = NUM_MODELS;
+}
+
+void tilem_calc_reset(TilemCalc* calc)
+{
+	tilem_z80_reset(calc);
+	tilem_lcd_reset(calc);
+	tilem_linkport_reset(calc);
+	tilem_keypad_reset(calc);
+	tilem_flash_reset(calc);
+	tilem_md5_assist_reset(calc);
+	tilem_user_timers_reset(calc);
+	if (calc->hw.reset)
+		(*calc->hw.reset)(calc);
+}
+
+TilemCalc* tilem_calc_new(char id)
+{
+	int i;
+	TilemCalc* calc;
+	dword msize;
+
+	for (i = 0; i < (int) NUM_MODELS; i++) {
+		if (hwmodels[i]->model_id == id) {
+			calc = tilem_try_new0(TilemCalc, 1);
+			if (!calc) {
+				return NULL;
+			}
+
+			calc->hw = *hwmodels[i];
+
+			calc->poweronhalt = 1;
+			calc->battery = 60;
+			calc->hwregs = tilem_try_new_atomic(dword, calc->hw.nhwregs);
+			if (!calc->hwregs) {
+				tilem_free(calc);
+				return NULL;
+			}
+
+			memset(calc->hwregs, 0, calc->hw.nhwregs * sizeof(dword));
+
+			msize = (calc->hw.romsize + calc->hw.ramsize
+				 + calc->hw.lcdmemsize);
+
+			calc->mem = tilem_try_new_atomic(byte, msize);
+			if (!calc->mem) {
+				tilem_free(calc->hwregs);
+				tilem_free(calc);
+				return NULL;
+			}
+
+			calc->ram = calc->mem + calc->hw.romsize;
+			calc->lcdmem = calc->ram + calc->hw.ramsize;
+
+			memset(calc->ram, 0, msize - calc->hw.romsize);
+
+			calc->lcd.emuflags = TILEM_LCD_REQUIRE_DELAY;
+			calc->flash.emuflags = TILEM_FLASH_REQUIRE_DELAY;
+
+			tilem_calc_reset(calc);
+			return calc;
+		}
+	}
+
+	fprintf(stderr, "INTERNAL ERROR: invalid model ID '%c'\n", id);
+	return NULL;
+}
+
+TilemCalc* tilem_calc_copy(TilemCalc* calc)
+{
+	TilemCalc* newcalc;
+	dword msize;
+
+	newcalc = tilem_try_new(TilemCalc, 1);
+	if (!newcalc)
+		return NULL;
+	memcpy(newcalc, calc, sizeof(TilemCalc));
+
+	newcalc->hwregs = tilem_try_new_atomic(dword, calc->hw.nhwregs);
+	if (!newcalc->hwregs) {
+		tilem_free(newcalc);
+		return NULL;
+	}
+	memcpy(newcalc->hwregs, calc->hwregs, calc->hw.nhwregs * sizeof(dword));
+
+	newcalc->z80.timers = tilem_try_new(TilemZ80Timer,
+					    newcalc->z80.ntimers);
+	if (!newcalc->z80.timers) {
+		tilem_free(newcalc->hwregs);
+		tilem_free(newcalc);
+		return NULL;
+	}
+	memcpy(newcalc->z80.timers, calc->z80.timers,
+	       newcalc->z80.ntimers * sizeof(TilemZ80Timer));
+
+	newcalc->z80.breakpoints = tilem_try_new(TilemZ80Breakpoint,
+					     newcalc->z80.nbreakpoints);
+	if (!newcalc->z80.breakpoints) {
+		tilem_free(newcalc->z80.timers);
+		tilem_free(newcalc->hwregs);
+		tilem_free(newcalc);
+		return NULL;
+	}
+	memcpy(newcalc->z80.breakpoints, calc->z80.breakpoints,
+	       newcalc->z80.nbreakpoints * sizeof(TilemZ80Breakpoint));
+
+	msize = (calc->hw.romsize + calc->hw.ramsize + calc->hw.lcdmemsize);
+	newcalc->mem = tilem_try_new_atomic(byte, msize);
+	if (!newcalc->mem) {
+		tilem_free(newcalc->z80.breakpoints);
+		tilem_free(newcalc->z80.timers);
+		tilem_free(newcalc->hwregs);
+		tilem_free(newcalc);
+		return NULL;
+	}
+	memcpy(newcalc->mem, calc->mem, msize * sizeof(byte));
+
+	newcalc->ram = newcalc->mem + calc->hw.romsize;
+	newcalc->lcdmem = newcalc->ram + calc->hw.ramsize;
+
+	return newcalc;
+}
+
+void tilem_calc_free(TilemCalc* calc)
+{
+	tilem_free(calc->mem);
+	tilem_free(calc->hwregs);
+	tilem_free(calc->z80.breakpoints);
+	tilem_free(calc->z80.timers);
+	tilem_free(calc);
+}
diff --git a/tool/tilem-src/emu/cert.c b/tool/tilem-src/emu/cert.c
new file mode 100644
index 0000000..5c2375c
--- /dev/null
+++ b/tool/tilem-src/emu/cert.c
@@ -0,0 +1,136 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "tilem.h"
+
+static int certificate_valid(byte* cert)
+{
+	int i, n;
+
+	if (cert[0] != 0)
+		return 0;
+
+	i = 1;
+
+	/* check that the actual certificate area consists of valid
+	   certificate fields */
+	while (cert[i] <= 0x0F) {
+		switch (cert[i + 1] & 0x0F) {
+		case 0x0D:
+			n = cert[i + 2] + 3;
+			break;
+		case 0x0E:
+			n = (cert[i + 2] << 8) + cert[i + 3] + 4;
+			break;
+		case 0x0F:
+			n = 6;
+			break;
+		default:
+			n = (cert[i + 1] & 0xf) + 2;
+		}
+		i += n;
+		if (i >= 0x2000)
+			return 0;
+	}
+
+	/* check that the fields end with FF */
+	if (cert[i] != 0xFF)
+		return 0;
+
+	/* if there are fields present, assume the certificate is OK */
+	if (i > 1)
+		return 1;
+
+	/* no fields present -> this could be an incompletely-patched
+	   certificate from an older version of TilEm; verify that the
+	   next 4k bytes are truly empty */
+	while (i < 0x1000) {
+		if (cert[i] != 0xFF)
+			return 0;
+		i++;
+	}
+
+	return 1;
+}
+
+void tilem_calc_fix_certificate(TilemCalc* calc, byte* cert,
+                                int app_start, int app_end,
+                                unsigned exptab_offset)
+{
+	int i, base, max_apps, page;
+	unsigned insttab_offset = 0x1fe0;
+
+	/* If the ROM was dumped from an unpatched OS, the certificate
+	   needs to be patched for some calculator functions to
+	   work. */
+
+	/* First, check if the certificate is already valid */
+
+	if (cert[0x2000] == 0)
+		base = 0x2000;
+	else
+		base = 0;
+
+	if (certificate_valid(cert + base)) {
+		return;
+	}
+
+	tilem_message(calc, "Repairing certificate area...");
+
+	memset(cert, 0xff, 16384);
+
+	cert[0] = 0;
+
+	cert[insttab_offset] = 0xfe;
+
+	if (app_start < app_end)
+		max_apps = app_end - app_start + 1;
+	else
+		max_apps = app_start - app_end + 1;
+
+	for (i = 0; i < max_apps; i++) {
+		if (app_start < app_end)
+			page = app_start + i;
+		else
+			page = app_start - i;
+
+		/* Clear installed bit / set expiration count for
+		   existing apps.  (If this incorrectly detects pages
+		   that aren't really apps, don't worry about it;
+		   better to err on the side of caution.) */
+		if (calc->mem[page << 14] != 0x80
+		    || calc->mem[(page << 14) + 1] != 0x0f)
+			continue;
+
+		tilem_message(calc, "Found application at page %02x (index %d)",
+		              page, i);
+
+		cert[insttab_offset + ((i + 1) / 8)] &= ~(1 << ((i + 1) % 8));
+
+		cert[exptab_offset + 2 * i] = 0x80;
+		cert[exptab_offset + 2 * i + 1] = 0x00;
+	}
+}
diff --git a/tool/tilem-src/emu/flash.c b/tool/tilem-src/emu/flash.c
new file mode 100644
index 0000000..eefef6f
--- /dev/null
+++ b/tool/tilem-src/emu/flash.c
@@ -0,0 +1,326 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include "tilem.h"
+
+#define FLASH_READ 0
+#define FLASH_AA   1
+#define FLASH_55   2
+#define FLASH_PROG 3
+#define FLASH_ERASE 4
+#define FLASH_ERAA 5
+#define FLASH_ER55 6
+#define FLASH_ERROR 7
+#define FLASH_FASTMODE 8
+#define FLASH_FASTPROG 9
+#define FLASH_FASTEXIT 10
+
+#define FLASH_BUSY_PROGRAM 1
+#define FLASH_BUSY_ERASE_WAIT 2
+#define FLASH_BUSY_ERASE 3
+
+/* Still to do:
+   - autoselect
+   - erase suspend
+   - fast program
+   - CFI
+ */
+
+#define WARN(xxx) \
+	tilem_warning(calc, "Flash error (" xxx ")")
+#define WARN2(xxx, yyy, zzz) \
+	tilem_warning(calc, "Flash error (" xxx ")", (yyy), (zzz))
+
+void tilem_flash_reset(TilemCalc* calc)
+{
+	calc->flash.unlock = 0;
+	calc->flash.state = FLASH_READ;
+	calc->flash.busy = 0;
+}
+
+void tilem_flash_delay_timer(TilemCalc* calc, void* data TILEM_ATTR_UNUSED)
+{
+	if (calc->flash.busy == FLASH_BUSY_ERASE_WAIT) {
+		calc->flash.busy = FLASH_BUSY_ERASE;
+		tilem_z80_set_timer(calc, TILEM_TIMER_FLASH_DELAY,
+				    200000, 0, 1);
+	}
+	else {
+		calc->flash.busy = 0;
+	}
+}
+
+#ifdef DISABLE_FLASH_DELAY
+# define set_busy(fl, bm, t)
+#else
+static inline void set_busy(TilemCalc* calc, int busymode, int time)
+{
+	if (!(calc->flash.emuflags & TILEM_FLASH_REQUIRE_DELAY))
+		return;
+
+	calc->flash.busy = busymode;
+	tilem_z80_set_timer(calc, TILEM_TIMER_FLASH_DELAY,
+			    time, 0, 1);
+}
+#endif
+
+static inline void program_byte(TilemCalc* calc, dword a, byte v)
+{
+	calc->mem[a] &= v;
+	calc->flash.progaddr = a;
+	calc->flash.progbyte = v;
+
+	if (calc->mem[a] != v) {
+		WARN2("bad program %02x over %02x", v, calc->mem[a]);
+		calc->flash.state = FLASH_ERROR;
+	}
+	else {
+		calc->flash.state = FLASH_READ;
+	}
+
+	set_busy(calc, FLASH_BUSY_PROGRAM, 7);
+}
+
+static inline void erase_sector(TilemCalc* calc, dword a, dword l)
+{
+	dword i;
+
+	calc->flash.progaddr = a;
+	for (i = 0; i < l; i++)
+		calc->mem[a + i]=0xFF;
+	calc->flash.state = FLASH_READ;
+
+	set_busy(calc, FLASH_BUSY_ERASE_WAIT, 50);
+}
+
+static const TilemFlashSector* get_sector(TilemCalc* calc, dword pa)
+{
+	int i;
+	const TilemFlashSector* sec;
+
+	for (i = 0; i < calc->hw.nflashsectors; i++) {
+		sec = &calc->hw.flashsectors[i];
+		if (pa >= sec->start && pa < sec->start + sec->size)
+			return sec;
+	}
+
+	return NULL;
+}
+
+static int sector_writable(TilemCalc* calc, const TilemFlashSector* sec)
+{
+	return !(sec->protectgroup & ~calc->flash.overridegroup);
+}
+
+byte tilem_flash_read_byte(TilemCalc* calc, dword pa)
+{
+	byte value;
+
+	if (calc->flash.busy == FLASH_BUSY_PROGRAM) {
+		if (pa != calc->flash.progaddr)
+			WARN("reading from Flash while programming");
+		value = (~calc->flash.progbyte & 0x80);
+		value |= calc->flash.toggles;
+		calc->flash.toggles ^= 0x40;
+		return (value);
+	}
+	else if (calc->flash.busy == FLASH_BUSY_ERASE) {
+		if ((pa >> 16) != (calc->flash.progaddr >> 16))
+			WARN("reading from Flash while erasing");
+		value = calc->flash.toggles | 0x08;
+		calc->flash.toggles ^= 0x44;
+		return (value);
+	}
+	else if (calc->flash.busy == FLASH_BUSY_ERASE_WAIT) {
+		if ((pa >> 16) != (calc->flash.progaddr >> 16))
+			WARN("reading from Flash while erasing");
+		value = calc->flash.toggles;
+		calc->flash.toggles ^= 0x44;
+		return (value);
+	}
+
+        if (calc->flash.state == FLASH_ERROR) {
+		value = ((~calc->flash.progbyte & 0x80) | 0x20);
+		value |= calc->flash.toggles;
+		calc->flash.toggles ^= 0x40;
+		return (value);
+	}
+	else if (calc->flash.state == FLASH_FASTMODE) {
+		return (calc->mem[pa]);
+	}
+	else if (calc->flash.state == FLASH_READ) {
+		return (calc->mem[pa]);
+	}
+	else {
+		WARN("reading during program/erase sequence");
+		calc->flash.state = FLASH_READ;
+		return (calc->mem[pa]);
+	}
+}
+
+void tilem_flash_erase_address(TilemCalc* calc, dword pa)
+{
+	const TilemFlashSector* sec = get_sector(calc, pa);
+
+	if (sector_writable(calc, sec)) {
+		tilem_message(calc, "Erasing Flash sector at %06x", pa);
+		erase_sector(calc, sec->start, sec->size);
+	}
+	else {
+		WARN("erasing protected sector");
+	}
+}
+
+void tilem_flash_write_byte(TilemCalc* calc, dword pa, byte v)
+{
+	int oldstate;
+	int i;
+	const TilemFlashSector* sec;
+
+	if (!calc->flash.unlock)
+		return;
+
+#ifndef DISABLE_FLASH_DELAY
+	if (calc->flash.busy == FLASH_BUSY_PROGRAM
+	    || calc->flash.busy == FLASH_BUSY_ERASE)
+		return;
+#endif
+
+	oldstate = calc->flash.state;
+	calc->flash.state = FLASH_READ;
+
+	switch (oldstate) {
+	case FLASH_READ:
+		if (((pa&0xFFF) == 0xAAA) && (v == 0xAA))
+			calc->flash.state = FLASH_AA;
+		return;
+
+	case FLASH_AA:
+		if (((pa&0xFFF) == 0x555) && (v == 0x55))
+			calc->flash.state = FLASH_55;
+		else if (v != 0xF0) {
+			WARN2("undefined command %02x->%06x after AA", v, pa);
+		}
+		return;
+
+	case FLASH_55:
+		if ((pa&0xFFF) == 0xAAA) {
+			switch (v) {
+			case 0x10:
+			case 0x30:
+				WARN("attempt to erase without pre-erase");
+				return;
+			case 0x20:
+				//WARN("entering fast mode");
+				calc->flash.state = FLASH_FASTMODE;
+				return;
+			case 0x80:
+				calc->flash.state = FLASH_ERASE;
+				return;
+			case 0x90:
+				WARN("autoselect is not implemented");
+				return;
+			case 0xA0:
+				calc->flash.state = FLASH_PROG;
+				return;
+			}
+		}
+		if (v != 0xF0)
+			WARN2("undefined command %02x->%06x after AA,55", v, pa);
+		return;
+
+	case FLASH_PROG:
+		sec = get_sector(calc, pa);
+		if (!sector_writable(calc, sec))
+			WARN("programming protected sector");
+		else
+			program_byte(calc, pa, v);
+		return;
+
+	case FLASH_FASTMODE:
+		//WARN2("fast mode cmd %02x->%06x", v, pa);
+		if ( v == 0x90 )
+			calc->flash.state = FLASH_FASTEXIT;
+		else if ( v == 0xA0 )
+			calc->flash.state = FLASH_FASTPROG;
+		else
+			// TODO : figure out whether mixing is allowed on real HW
+			WARN2("mixing fast programming with regular programming : %02x->%06x", v, pa);
+		return;
+
+	case FLASH_FASTPROG:
+		//WARN2("fast prog %02x->%06x", v, pa);
+		sec = get_sector(calc, pa);
+		if (!sector_writable(calc, sec))
+			WARN("programming protected sector");
+		else
+			program_byte(calc, pa, v);
+		calc->flash.state = FLASH_FASTMODE;
+		return;
+
+	case FLASH_FASTEXIT:
+		//WARN("leaving fast mode");
+		if ( v != 0xF0 )
+		{
+			WARN2("undefined command %02x->%06x after fast mode pre-exit 90", v, pa);
+			// TODO : figure out whether fast mode remains in such a case
+			calc->flash.state = FLASH_FASTMODE;
+		}
+		return;
+		
+	case FLASH_ERASE:
+		if (((pa&0xFFF) == 0xAAA) && (v == 0xAA))
+			calc->flash.state = FLASH_ERAA;
+		else if (v != 0xF0)
+			WARN2("undefined command %02x->%06x after pre-erase", v, pa);
+		return;
+
+	case FLASH_ERAA:
+		if (((pa&0xFFF) == 0x555) && (v == 0x55))
+			calc->flash.state = FLASH_ER55;
+		else if (v != 0xF0)
+			WARN2("undefined command %02x->%06x after pre-erase AA", v, pa);
+		return;
+
+	case FLASH_ER55:
+		if (((pa&0xFFF) == 0xAAA) && v==0x10) {
+			tilem_message(calc, "Erasing entire Flash chip");
+
+			for (i = 0; i < calc->hw.nflashsectors; i++) {
+				sec = &calc->hw.flashsectors[i];
+				if (sector_writable(calc, sec))
+					erase_sector(calc, sec->start,
+						     sec->size);
+			}
+		}
+		else if (v == 0x30) {
+			tilem_flash_erase_address(calc, pa);
+		}
+		else if (v != 0xF0)
+			WARN2("undefined command %02x->%06x after pre-erase AA,55", v, pa);
+		return;
+	}
+}
diff --git a/tool/tilem-src/emu/graycolor.c b/tool/tilem-src/emu/graycolor.c
new file mode 100644
index 0000000..5864c0b
--- /dev/null
+++ b/tool/tilem-src/emu/graycolor.c
@@ -0,0 +1,90 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2010 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include "tilem.h"
+
+dword* tilem_color_palette_new(int rlight, int glight, int blight,
+			       int rdark, int gdark, int bdark,
+			       double gamma)
+{
+	dword* pal = tilem_new_atomic(dword, 256);
+	double r0, g0, b0, dr, dg, db;
+	double igamma = 1.0 / gamma;
+	double s = (1.0 / 255.0);
+	int r, g, b, i;
+
+	r0 = pow(rlight * s, gamma);
+	g0 = pow(glight * s, gamma);
+	b0 = pow(blight * s, gamma);
+	dr = (pow(rdark * s, gamma) - r0) * s;
+	dg = (pow(gdark * s, gamma) - g0) * s;
+	db = (pow(bdark * s, gamma) - b0) * s;
+
+	pal[0] = (rlight << 16) | (glight << 8) | blight;
+
+	for (i = 1; i < 255; i++) {
+		r = pow(r0 + i * dr, igamma) * 255.0 + 0.5;
+		if (r < 0) r = 0;
+		if (r > 255) r = 255;
+
+		g = pow(g0 + i * dg, igamma) * 255.0 + 0.5;
+		if (g < 0) g = 0;
+		if (g > 255) g = 255;
+
+		b = pow(b0 + i * db, igamma) * 255.0 + 0.5;
+		if (b < 0) b = 0;
+		if (b > 255) b = 255;
+
+		pal[i] = (r << 16) | (g << 8) | b;
+	}
+
+	pal[255] = (rdark << 16) | (gdark << 8) | bdark;
+
+	return pal;
+}
+
+byte* tilem_color_palette_new_packed(int rlight, int glight, int blight,
+                                     int rdark, int gdark, int bdark,
+                                     double gamma)
+{
+	dword* palette;
+	byte* packed;
+	int i;
+
+	palette = tilem_color_palette_new(rlight, glight, blight,
+	                                  rdark, gdark, bdark, gamma);
+
+	packed = tilem_new_atomic(byte, 256 * 3);
+	for (i = 0; i < 256; i++) {
+		packed[i * 3] = palette[i] >> 16;
+		packed[i * 3 + 1] = palette[i] >> 8;
+		packed[i * 3 + 2] = palette[i];
+	}
+
+	tilem_free(palette);
+
+	return packed;
+}
diff --git a/tool/tilem-src/emu/grayimage.c b/tool/tilem-src/emu/grayimage.c
new file mode 100644
index 0000000..46ee275
--- /dev/null
+++ b/tool/tilem-src/emu/grayimage.c
@@ -0,0 +1,324 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2010-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "tilem.h"
+
+/* Scale the input buffer, multiply by F * INCOUNT, and add to the
+   output buffer (which must be an exact multiple of the size of the
+   input buffer.) */
+static inline void add_scale1d_exact(const byte * restrict in, int incount,
+				     unsigned int * restrict out,
+				     int outcount, int f)
+{
+	int i, j;
+
+	for (i = 0; i < incount; i++) {
+		for (j = 0; j < outcount / incount; j++) {
+			*out += *in * f * incount;
+			out++;
+		}
+		in++;
+	}
+}
+
+/* Scale a 1-dimensional buffer, multiply by F * INCOUNT, and add to
+   the output buffer. */
+static inline void add_scale1d_smooth(const byte * restrict in, int incount,
+				      unsigned int * restrict out,
+				      int outcount, int f)
+{
+	int in_rem, out_rem;
+	unsigned int outv;
+	int i;
+
+	in_rem = outcount;
+	out_rem = incount;
+	outv = 0;
+	i = outcount;
+	while (i > 0) {
+		if (in_rem < out_rem) {
+			out_rem -= in_rem;
+			outv += in_rem * *in * f;
+			in++;
+			in_rem = outcount;
+		}
+		else {
+			in_rem -= out_rem;
+			outv += out_rem * *in * f;
+			*out += outv;
+			outv = 0;
+			out++;
+			out_rem = incount;
+			i--;
+		}
+	}
+}
+
+/* Scale a 2-dimensional buffer, multiply by INWIDTH * INHEIGHT, and
+   store in the output buffer. */
+static void scale2d_smooth(const byte * restrict in,
+			   int inwidth, int inheight, int inrowstride,
+			   unsigned int * restrict out,
+			   int outwidth, int outheight, int outrowstride)
+{
+	int in_rem, out_rem;
+	int i;
+
+	memset(out, 0, outrowstride * outheight * sizeof(int));
+
+	in_rem = outheight;
+	out_rem = inheight;
+	i = outheight;
+	while (i > 0) {
+		if (in_rem < out_rem) {
+			if (in_rem) {
+				if (outwidth % inwidth)
+					add_scale1d_smooth(in, inwidth, out,
+							   outwidth, in_rem);
+				else
+					add_scale1d_exact(in, inwidth, out,
+							  outwidth, in_rem);
+			}
+			out_rem -= in_rem;
+			in += inrowstride;
+			in_rem = outheight;
+		}
+		else {
+			in_rem -= out_rem;
+			if (outwidth % inwidth)
+				add_scale1d_smooth(in, inwidth, out, outwidth,
+						   out_rem);
+			else
+				add_scale1d_exact(in, inwidth, out, outwidth,
+						  out_rem);
+			out += outrowstride;
+			out_rem = inheight;
+			i--;
+		}
+	}
+}
+
+/* Quickly scale a 1-dimensional buffer and store in the output
+   buffer. */
+static inline void scale1d_fast(const byte * restrict in, int incount,
+				byte * restrict out, int outcount)
+{
+	int i, e;
+
+	e = outcount - incount / 2;
+	i = outcount;
+	while (i > 0) {
+		if (e >= 0) {
+			*out = *in;
+			out++;
+			e -= incount;
+			i--;
+		}
+		else {
+			e += outcount;
+			in++;
+		}
+	}
+}
+
+/* Quickly scale a 2-dimensional buffer and store in the output
+   buffer. */
+static void scale2d_fast(const byte * restrict in,
+			 int inwidth, int inheight, int inrowstride,
+			 byte * restrict out,
+			 int outwidth, int outheight, int outrowstride)
+{
+	int i, e;
+
+	e = outheight - inheight / 2;
+	i = outheight;
+	while (i > 0) {
+		if (e >= 0) {
+			scale1d_fast(in, inwidth, out, outwidth);
+			out += outrowstride;
+			e -= inheight;
+			i--;
+		}
+		else {
+			e += outheight;
+			in += inrowstride;
+		}
+	}
+}
+
+/* Determine range of linear pixel values corresponding to a given
+   contrast level. */
+static void get_contrast_settings(unsigned int contrast,
+                                  int *cbase, int *cfact)
+{
+	if (contrast < 32) {
+		*cbase = 0;
+		*cfact = contrast * 8;
+	}
+	else {
+		*cbase = (contrast - 32) * 8;
+		*cfact = 255 - *cbase;
+	}
+}
+
+#define GETSCALEBUF(ttt, www, hhh) \
+	((ttt *) alloc_scalebuf(buf, (www) * (hhh) * sizeof(ttt)))
+
+static void* alloc_scalebuf(TilemLCDBuffer *buf, unsigned int size)
+{
+	if (TILEM_UNLIKELY(size > buf->tmpbufsize)) {
+		buf->tmpbufsize = size;
+		tilem_free(buf->tmpbuf);
+		buf->tmpbuf = tilem_malloc_atomic(size);
+	}
+
+	return buf->tmpbuf;
+}
+
+void tilem_draw_lcd_image_indexed(TilemLCDBuffer * restrict buf,
+                                  byte * restrict buffer,
+                                  int imgwidth, int imgheight,
+                                  int rowstride, int scaletype)
+{
+	int dwidth = buf->width;
+	int dheight = buf->height;
+	int i, j, v;
+	unsigned int * restrict ibuf;
+	int cbase, cfact;
+	byte cindex[129];
+
+	if (dwidth == 0 || dheight == 0 || buf->contrast == 0) {
+		for (i = 0; i < imgheight; i++) {
+			for (j = 0; j < imgwidth; j++)
+				buffer[j] = 0;
+			buffer += rowstride;
+		}
+		return;
+	}
+
+	get_contrast_settings(buf->contrast, &cbase, &cfact);
+
+	for (i = 0; i <= 128; i++)
+		cindex[i] = ((i * cfact) >> 7) + cbase;
+
+	if (scaletype == TILEM_SCALE_FAST
+	    || (imgwidth % dwidth == 0 && imgheight % dheight == 0)) {
+		scale2d_fast(buf->data, dwidth, dheight, buf->rowstride,
+		             buffer, imgwidth, imgheight, rowstride);
+
+		for (i = 0; i < imgwidth * imgheight; i++)
+			buffer[i] = cindex[buffer[i]];
+	}
+	else {
+		ibuf = GETSCALEBUF(unsigned int, imgwidth, imgheight);
+
+		scale2d_smooth(buf->data, dwidth, dheight, buf->rowstride,
+		               ibuf, imgwidth, imgheight, imgwidth);
+
+		for (i = 0; i < imgheight; i++) {
+			for (j = 0; j < imgwidth; j++) {
+				v = ibuf[j] / (dwidth * dheight);
+				buffer[j] = cindex[v];
+			}
+			ibuf += imgwidth;
+			buffer += rowstride;
+		}
+	}
+}
+
+void tilem_draw_lcd_image_rgb(TilemLCDBuffer * restrict buf,
+                              byte * restrict buffer,
+                              int imgwidth, int imgheight, int rowstride,
+                              int pixbytes, const dword * restrict palette,
+                              int scaletype)
+{
+	int dwidth = buf->width;
+	int dheight = buf->height;
+	int i, j, v;
+	int padbytes = rowstride - (imgwidth * pixbytes);
+	byte * restrict bbuf;
+	unsigned int * restrict ibuf;
+	int cbase, cfact;
+	dword cpalette[129];
+
+	if (dwidth == 0 || dheight == 0 || buf->contrast == 0) {
+		for (i = 0; i < imgheight; i++) {
+			for (j = 0; j < imgwidth; j++) {
+				buffer[0] = palette[0] >> 16;
+				buffer[1] = palette[0] >> 8;
+				buffer[2] = palette[0];
+				buffer += pixbytes;
+			}
+			buffer += padbytes;
+		}
+		return;
+	}
+
+	get_contrast_settings(buf->contrast, &cbase, &cfact);
+
+	for (i = 0; i <= 128; i++) {
+		v = ((i * cfact) >> 7) + cbase;
+		cpalette[i] = palette[v];
+	}
+
+	if (scaletype == TILEM_SCALE_FAST
+	    || (imgwidth % dwidth == 0 && imgheight % dheight == 0)) {
+		bbuf = GETSCALEBUF(byte, imgwidth, imgheight);
+
+		scale2d_fast(buf->data, dwidth, dheight, buf->rowstride,
+		             bbuf, imgwidth, imgheight, imgwidth);
+
+		for (i = 0; i < imgheight; i++) {
+			for (j = 0; j < imgwidth; j++) {
+				v = bbuf[j];
+				buffer[0] = cpalette[v] >> 16;
+				buffer[1] = cpalette[v] >> 8;
+				buffer[2] = cpalette[v];
+				buffer += pixbytes;
+			}
+			bbuf += imgwidth;
+			buffer += padbytes;
+		}
+	}
+	else {
+		ibuf = GETSCALEBUF(unsigned int, imgwidth, imgheight);
+
+		scale2d_smooth(buf->data, dwidth, dheight, buf->rowstride,
+			       ibuf, imgwidth, imgheight, imgwidth);
+
+		for (i = 0; i < imgheight; i++) {
+			for (j = 0; j < imgwidth; j++) {
+				v = (ibuf[j] / (dwidth * dheight));
+				buffer[0] = cpalette[v] >> 16;
+				buffer[1] = cpalette[v] >> 8;
+				buffer[2] = cpalette[v];
+				buffer += pixbytes;
+			}
+			ibuf += imgwidth;
+			buffer += padbytes;
+		}
+	}
+}
diff --git a/tool/tilem-src/emu/graylcd.c b/tool/tilem-src/emu/graylcd.c
new file mode 100644
index 0000000..d27ddcc
--- /dev/null
+++ b/tool/tilem-src/emu/graylcd.c
@@ -0,0 +1,263 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2010-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "tilem.h"
+#include "graylcd.h"
+
+/* Read screen contents and update pixels that have changed */
+static void tmr_screen_update(TilemCalc *calc, void *data)
+{
+	TilemGrayLCD *glcd = data;
+	byte *np, *op, nb, ob, d;
+	int i, j, n;
+	dword delta;
+
+	glcd->t++;
+
+	if (calc->z80.lastlcdwrite == glcd->lcdupdatetime)
+		return;
+	glcd->lcdupdatetime = calc->z80.lastlcdwrite;
+
+	(*calc->hw.get_lcd)(calc, glcd->newbits);
+
+	np = glcd->newbits;
+	op = glcd->oldbits;
+	glcd->oldbits = np;
+	glcd->newbits = op;
+	n = 0;
+
+	for (i = 0; i < glcd->bwidth * glcd->height; i++) {
+		nb = *np;
+		ob = *op;
+		d = nb ^ ob;
+		for (j = 0; j < 8; j++) {
+			if (d & (0x80 >> j)) {
+				delta = glcd->t - glcd->tchange[n];
+				glcd->tchange[n] = glcd->t;
+
+				if (ob & (0x80 >> j)) {
+					glcd->curpixels[n].ndark += delta;
+					glcd->curpixels[n].ndarkseg++;
+				}
+				else {
+					glcd->curpixels[n].nlight += delta;
+					glcd->curpixels[n].nlightseg++;
+				}
+			}
+			n++;
+		}
+
+		np++;
+		op++;
+	}
+}
+
+TilemGrayLCD* tilem_gray_lcd_new(TilemCalc *calc, int windowsize, int sampleint)
+{
+	TilemGrayLCD *glcd = tilem_new(TilemGrayLCD, 1);
+	int nbytes, npixels, i;
+
+	glcd->bwidth = (calc->hw.lcdwidth + 7) / 8;
+	glcd->height = calc->hw.lcdheight;
+	nbytes = glcd->bwidth * glcd->height;
+	npixels = nbytes * 8;
+
+	glcd->oldbits = tilem_new_atomic(byte, nbytes);
+	glcd->newbits = tilem_new_atomic(byte, nbytes);
+	glcd->tchange = tilem_new_atomic(dword, npixels);
+	glcd->tframestart = tilem_new_atomic(dword, windowsize);
+	glcd->framestamp = tilem_new_atomic(dword, windowsize);
+	glcd->curpixels = tilem_new_atomic(TilemGrayLCDPixel, npixels);
+	glcd->framebasepixels = tilem_new_atomic(TilemGrayLCDPixel,
+						 npixels * windowsize);
+
+	memset(glcd->oldbits, 0, nbytes);
+	memset(glcd->tchange, 0, npixels * sizeof(dword));
+	memset(glcd->tframestart, 0, windowsize * sizeof(dword));
+	memset(glcd->curpixels, 0, npixels * sizeof(TilemGrayLCDPixel));
+	memset(glcd->framebasepixels, 0, (npixels * windowsize
+					  * sizeof(TilemGrayLCDPixel)));
+
+	glcd->calc = calc;
+	glcd->timer_id = tilem_z80_add_timer(calc, sampleint / 2, sampleint, 1,
+					     &tmr_screen_update, glcd);
+
+	/* assign arbitrary but unique timestamps to the initial n
+	   frames */
+	for (i = 0; i < windowsize; i++)
+		glcd->framestamp[i] = calc->z80.lastlcdwrite - i;
+
+	glcd->lcdupdatetime = calc->z80.lastlcdwrite - 1;
+	glcd->t = 0;
+	glcd->windowsize = windowsize;
+	glcd->sampleint = sampleint;
+	glcd->framenum = 0;
+
+	return glcd;
+}
+
+void tilem_gray_lcd_free(TilemGrayLCD *glcd)
+{
+	tilem_z80_remove_timer(glcd->calc, glcd->timer_id);
+
+	tilem_free(glcd->oldbits);
+	tilem_free(glcd->newbits);
+	tilem_free(glcd->tchange);
+	tilem_free(glcd->tframestart);
+	tilem_free(glcd->framestamp);
+	tilem_free(glcd->curpixels);
+	tilem_free(glcd->framebasepixels);
+	tilem_free(glcd);
+}
+
+/* Update levelbuf with values based on the accumulated grayscale
+   data */
+void tilem_gray_lcd_get_frame(TilemGrayLCD * restrict glcd,
+                              TilemLCDBuffer * restrict buf)
+{
+	int i, j, n;
+	unsigned int current, delta, fd, fl;
+	word ndark, nlight, ndarkseg, nlightseg;
+	dword tbase, tlimit;
+	dword lastwrite;
+	byte * restrict bp;
+	byte * restrict op;
+	TilemGrayLCDPixel * restrict pix;
+	TilemGrayLCDPixel * restrict basepix;
+	dword * restrict tchange;
+
+	if (TILEM_UNLIKELY(buf->height != glcd->height
+	                   || buf->rowstride != glcd->bwidth * 8)) {
+		/* reallocate data buffer */
+		tilem_free(buf->data);
+		buf->data = tilem_new_atomic(byte,
+		                             glcd->height * glcd->bwidth * 8);
+		buf->rowstride = glcd->bwidth * 8;
+		buf->height = glcd->height;
+	}
+
+	buf->width = glcd->calc->hw.lcdwidth;
+
+	if (!glcd->calc->lcd.active
+	    || (glcd->calc->z80.halted && !glcd->calc->poweronhalt)) {
+		/* screen is turned off */
+		buf->stamp = glcd->calc->z80.lastlcdwrite;
+		buf->contrast = 0;
+		return;
+	}
+
+	buf->contrast = glcd->calc->lcd.contrast;
+
+	/* If LCD remains unchanged throughout the window, set
+	   timestamp to the time when the LCD was last changed, so
+	   that consecutive frames have the same timestamp.  If LCD
+	   has changed during the window, values of gray pixels will
+	   vary from one frame to another, so use a unique timestamp
+	   for each frame */
+	lastwrite = glcd->calc->z80.lastlcdwrite;
+	if (glcd->framestamp[glcd->framenum] == lastwrite)
+		buf->stamp = lastwrite;
+	else
+		buf->stamp = glcd->calc->z80.clock + 0x80000000;
+	glcd->framestamp[glcd->framenum] = lastwrite;
+
+	/* set tbase to the sample number where the window began; this
+	   is used to limit the weight of unchanging pixels */
+	tbase = glcd->tframestart[glcd->framenum];
+	glcd->tframestart[glcd->framenum] = glcd->t;
+	tlimit = glcd->t - tbase; /* number of samples per window */
+
+	bp = glcd->newbits;
+	op = buf->data;
+	pix = glcd->curpixels;
+	basepix = glcd->framebasepixels + (glcd->framenum * glcd->height
+					   * glcd->bwidth * 8);
+	tchange = glcd->tchange;
+
+	(*glcd->calc->hw.get_lcd)(glcd->calc, bp);
+
+	n = 0;
+
+	for (i = 0; i < glcd->bwidth * glcd->height; i++) {
+		for (j = 0; j < 8; j++) {
+			/* check if pixel is currently set */
+			current = *bp & (0x80 >> j);
+
+			/* compute number of dark and light samples
+			   within the window */
+			ndark = pix[n].ndark - basepix[n].ndark;
+			nlight = pix[n].nlight - basepix[n].nlight;
+
+			/* compute number of dark and light segments
+			   within the window */
+			ndarkseg = pix[n].ndarkseg - basepix[n].ndarkseg;
+			nlightseg = pix[n].nlightseg - basepix[n].nlightseg;
+
+			/* average light segment in this window is
+			   (nlight / nlightseg); average dark segment
+			   is (ndark / ndarkseg) */
+
+			/* ensure tchange is later than or equal to tbase */
+			if (tchange[n] - tbase > tlimit) {
+				tchange[n] = tbase;
+			}
+
+			/* if current segment is longer than average,
+			   count it as well */
+			delta = glcd->t - tchange[n];
+
+			if (current) {
+				if (delta * ndarkseg >= ndark) {
+					ndark += delta;
+					ndarkseg++;
+				}
+			}
+			else {
+				if (delta * nlightseg >= nlight) {
+					nlight += delta;
+					nlightseg++;
+				}
+			}
+
+			fd = ndark * nlightseg;
+			fl = nlight * ndarkseg;
+
+			if (fd + fl == 0)
+				*op = (ndark ? 128 : 0);
+			else
+				*op = ((fd * 128) / (fd + fl));
+
+			n++;
+			op++;
+		}
+		bp++;
+	}
+
+	memcpy(basepix, pix, (glcd->height * glcd->bwidth * 8
+			      * sizeof(TilemGrayLCDPixel)));
+
+	glcd->framenum = (glcd->framenum + 1) % glcd->windowsize;
+}
diff --git a/tool/tilem-src/emu/graylcd.h b/tool/tilem-src/emu/graylcd.h
new file mode 100644
index 0000000..dbbf8da
--- /dev/null
+++ b/tool/tilem-src/emu/graylcd.h
@@ -0,0 +1,57 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2010-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_GRAYLCD_H
+#define _TILEM_GRAYLCD_H
+
+typedef struct _TilemGrayLCDPixel {
+	word ndark;		/* Sum of lengths of dark intervals */
+	word nlight;		/* Sum of lengths of light intervals */
+	word ndarkseg;		/* Number of dark intervals */
+	word nlightseg;		/* Number of light intervals */
+} TilemGrayLCDPixel;
+
+struct _TilemGrayLCD {
+	TilemCalc *calc;	/* Calculator */
+	int timer_id;		/* Screen update timer */
+	dword lcdupdatetime;	/* CPU time of last known LCD update */
+
+	dword t;		/* Time counter */
+	int windowsize;		/* Number of frames in the sampling
+				   window */
+	int framenum;		/* Current frame number */
+	int sampleint;		/* Microseconds per sample */ 
+
+	int bwidth;		/* Width of LCD, bytes */
+	int height;		/* Height of LCD, pixels */
+	byte *oldbits;		/* Original pixel values (current buffer) */
+	byte *newbits;		/* Original pixel values (alternate buffer) */
+
+	dword *tchange;		/* Time when pixels changed */
+	dword *tframestart;	/* Time at start of frame */
+	dword *framestamp;	/* LCD update time at start of frame */
+
+	TilemGrayLCDPixel *curpixels; /* Current pixel counters */
+	TilemGrayLCDPixel *framebasepixels; /* Pixel counters as of
+					       the start of each
+					       frame */
+};
+
+#endif
diff --git a/tool/tilem-src/emu/keypad.c b/tool/tilem-src/emu/keypad.c
new file mode 100644
index 0000000..37953e7
--- /dev/null
+++ b/tool/tilem-src/emu/keypad.c
@@ -0,0 +1,91 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include "tilem.h"
+#include "scancodes.h"
+
+void tilem_keypad_reset(TilemCalc* calc)
+{
+	int i;
+
+	calc->keypad.group = 0xff;
+	calc->keypad.onkeydown = 0;
+	calc->keypad.onkeyint = 0;
+	for (i = 0; i < 8; i++)
+		calc->keypad.keysdown[i] = 0;
+}
+
+void tilem_keypad_set_group(TilemCalc* calc, byte group)
+{
+	calc->keypad.group = group;
+}
+
+byte tilem_keypad_read_keys(TilemCalc* calc)
+{
+	int i;
+	byte keys, old;
+
+	keys = 0;
+	for (i = 0; i < 8; i++) {
+		if (!(calc->keypad.group & (1 << i)))
+			keys |= calc->keypad.keysdown[i];
+	}
+
+	do {
+		old = keys;
+		for (i = 0; i < 8; i++) {
+			if (keys & calc->keypad.keysdown[i])
+				keys |= calc->keypad.keysdown[i];
+		}
+	} while (keys != old);
+
+	return ~keys;
+}
+
+void tilem_keypad_press_key(TilemCalc* calc, int scancode)
+{
+	if (scancode == TILEM_KEY_ON) {
+		if (!calc->keypad.onkeydown && calc->keypad.onkeyint)
+			calc->z80.interrupts |= TILEM_INTERRUPT_ON_KEY;
+		calc->keypad.onkeydown = 1;
+	}
+	else if (scancode > 0 && scancode < 65) {
+		scancode--;
+		calc->keypad.keysdown[scancode / 8] |= (1 << (scancode % 8));
+	}
+}
+
+void tilem_keypad_release_key(TilemCalc* calc, int scancode)
+{
+	if (scancode == TILEM_KEY_ON) {
+		if (calc->keypad.onkeydown && calc->keypad.onkeyint)
+			calc->z80.interrupts |= TILEM_INTERRUPT_ON_KEY;
+		calc->keypad.onkeydown = 0;
+	}
+	else if (scancode > 0 && scancode < 65) {
+		scancode--;
+		calc->keypad.keysdown[scancode / 8] &= ~(1 << (scancode % 8));
+	}
+}
diff --git a/tool/tilem-src/emu/lcd.c b/tool/tilem-src/emu/lcd.c
new file mode 100644
index 0000000..1820805
--- /dev/null
+++ b/tool/tilem-src/emu/lcd.c
@@ -0,0 +1,268 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include "tilem.h"
+
+#ifdef DISABLE_LCD_DRIVER_DELAY
+# define BUSY 0
+# define SET_BUSY 0
+#else
+# define BUSY check_delay_timer(calc)
+# define SET_BUSY set_delay_timer(calc)
+
+static inline int check_delay_timer(TilemCalc* calc)
+{
+	int t;
+
+	if (!calc->lcd.busy)
+		return 0;
+	
+	t = tilem_z80_get_timer_clocks(calc, TILEM_TIMER_LCD_DELAY);
+	return (t > 0);
+}
+
+static inline void set_delay_timer(TilemCalc* calc)
+{
+	int delay;
+
+	if (!(calc->lcd.emuflags & TILEM_LCD_REQUIRE_DELAY))
+		return;
+
+	if (calc->lcd.emuflags & TILEM_LCD_REQUIRE_LONG_DELAY)
+		delay = 70;
+	else
+		delay = 50;
+
+	calc->lcd.busy = 1;
+
+	tilem_z80_set_timer(calc, TILEM_TIMER_LCD_DELAY, delay, 0, 0);
+}
+#endif
+
+void tilem_lcd_reset(TilemCalc* calc)
+{
+	calc->lcd.active = 0;
+	calc->lcd.contrast = 32;
+	calc->lcd.addr = 0;
+	calc->lcd.mode = 1;
+	calc->lcd.nextbyte = 0;
+	calc->lcd.x = calc->lcd.y = 0;
+	calc->lcd.inc = 7;
+	calc->lcd.rowshift = 0;
+	calc->lcd.busy = 0;
+
+	if (calc->hw.lcdmemsize)
+		calc->lcd.rowstride = (calc->hw.lcdmemsize
+				       / calc->hw.lcdheight);
+	else
+		calc->lcd.rowstride = (calc->hw.lcdwidth / 8);
+}
+
+void tilem_lcd_delay_timer(TilemCalc* calc, void* data TILEM_ATTR_UNUSED)
+{
+	calc->lcd.busy = 0;
+}
+
+byte tilem_lcd_t6a04_status(TilemCalc* calc)
+{
+	return (calc->lcd.busy << 7
+		| calc->lcd.mode << 6
+		| calc->lcd.active << 5
+		| (calc->lcd.inc & 3));
+}
+
+void tilem_lcd_t6a04_control(TilemCalc* calc, byte val)
+{
+	if (BUSY) return;
+
+	if (val <= 1) {
+		calc->lcd.mode = val;
+
+	} else if (val == 2) {
+		calc->lcd.active = 0;
+
+	} else if (val == 3) {
+		calc->lcd.active = 1;
+
+	} else if (val <= 7) {
+		calc->lcd.inc = val;
+
+	} else if ((val >= 0x20) && (val <= 0x3F)){
+		calc->lcd.x = val - 0x20;
+
+	} else if ((val >= 0x80) && (val <= 0xBF)) {
+		calc->lcd.y = val - 0x80;
+
+	} else if ((val >= 0x40) && (val <= 0x7F)) {
+		calc->lcd.rowshift = val - 0x40;
+
+	} else if (val >= 0xc0) {
+		calc->lcd.contrast = val - 0xc0;
+
+	}
+
+	calc->z80.lastlcdwrite = calc->z80.clock;
+	SET_BUSY;
+}
+
+
+byte tilem_lcd_t6a04_read(TilemCalc* calc)
+{
+	byte retv = calc->lcd.nextbyte;
+	byte* lcdbuf = calc->lcdmem;
+	int stride = calc->lcd.rowstride;
+	int xlimit;
+
+	if (BUSY) return(0);
+
+	if (calc->lcd.mode)
+		xlimit = stride;
+	else
+		xlimit = (stride * 8 + 5) / 6;
+
+	if (calc->lcd.x >= xlimit)
+		calc->lcd.x = 0;
+	else if (calc->lcd.x < 0)
+		calc->lcd.x = xlimit - 1;
+
+	if (calc->lcd.y >= 0x40)
+		calc->lcd.y = 0;
+	else if (calc->lcd.y < 0)
+		calc->lcd.y = 0x3F;
+
+	if (calc->lcd.mode) {
+		calc->lcd.nextbyte = *(lcdbuf + calc->lcd.x + stride * calc->lcd.y);
+
+	} else {
+		int col = 0x06 * calc->lcd.x;
+		int ofs = calc->lcd.y * stride + (col >> 3);
+		int shift = 0x0A - (col & 0x07);
+
+		calc->lcd.nextbyte = ((*(lcdbuf + ofs) << 8) | *(lcdbuf + ofs + 1)) >> shift;
+	}
+
+	switch (calc->lcd.inc) {
+		case 4: calc->lcd.y--; break;
+		case 5: calc->lcd.y++; break;
+		case 6: calc->lcd.x--; break;
+		case 7: calc->lcd.x++; break;
+	}
+
+	SET_BUSY;
+	return(retv);
+}
+
+
+void tilem_lcd_t6a04_write(TilemCalc* calc, byte sprite)
+{
+	byte* lcdbuf = calc->lcdmem;
+	int stride = calc->lcd.rowstride;
+	int xlimit;
+
+	if (BUSY) return;
+
+	if (calc->lcd.mode)
+		xlimit = stride;
+	else
+		xlimit = (stride * 8 + 5) / 6;
+
+	if (calc->lcd.x >= xlimit)
+		calc->lcd.x = 0;
+	else if (calc->lcd.x < 0)
+		calc->lcd.x = xlimit - 1;
+
+	if (calc->lcd.y >= 0x40)
+		calc->lcd.y = 0;
+	else if (calc->lcd.y < 0)
+		calc->lcd.y = 0x3F;
+
+	if (calc->lcd.mode) {
+		*(lcdbuf + calc->lcd.x + stride * calc->lcd.y) = sprite;
+
+	} else {
+		int col = 0x06 * calc->lcd.x;
+		int ofs = calc->lcd.y * stride + (col >> 3);
+		int shift = col & 0x07;
+		int mask;
+
+		sprite <<= 2;
+		mask = ~(0xFC >> shift);
+		*(lcdbuf + ofs) = (*(lcdbuf + ofs) & mask) | (sprite >> shift);
+		if (shift > 2 && (col >> 3) < (stride - 1)) {
+			ofs++;
+			shift = 8 - shift;
+			mask = ~(0xFC << shift);
+			*(lcdbuf + ofs) = (*(lcdbuf + ofs) & mask) | (sprite << shift);
+		}
+	}
+
+	switch (calc->lcd.inc) {
+		case 4: calc->lcd.y--; break;
+		case 5: calc->lcd.y++; break;
+		case 6: calc->lcd.x--; break;
+		case 7: calc->lcd.x++; break;
+	}
+
+	calc->z80.lastlcdwrite = calc->z80.clock;
+	SET_BUSY;
+	return;
+}
+
+
+void tilem_lcd_t6a04_get_data(TilemCalc* calc, byte* data)
+{
+	int width = calc->hw.lcdwidth / 8;
+	byte* lcdbuf = calc->lcdmem;
+	int stride = calc->lcd.rowstride;
+	int i, j, k;
+
+	for (i = 0; i < calc->hw.lcdheight; i++) {
+		j = (i + calc->lcd.rowshift) % 64;
+		for (k = 0; k < width; k++)
+			data[k] = lcdbuf[j * stride + k];
+		data += width;
+	}
+}
+
+void tilem_lcd_t6a43_get_data(TilemCalc* calc, byte* data)
+{
+	int width = calc->hw.lcdwidth / 8;
+	byte* lcdbuf = calc->ram + calc->lcd.addr;
+	int stride = calc->lcd.rowstride;
+	int i, j;
+
+	for (i = 0; i < calc->hw.lcdheight; i++) {
+		for (j = 0; j < 10; j++)
+			data[j] = lcdbuf[j];
+		for (; j < 10 + width - stride; j++)
+			data[j] = 0;
+		for (; j < width; j++)
+			data[j] = lcdbuf[j + stride - width];
+
+		data += width;
+		lcdbuf += stride;
+	}
+}
diff --git a/tool/tilem-src/emu/link.c b/tool/tilem-src/emu/link.c
new file mode 100644
index 0000000..0bd5ca9
--- /dev/null
+++ b/tool/tilem-src/emu/link.c
@@ -0,0 +1,456 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include "tilem.h"
+
+/* Internal link port control */
+
+static inline void dbus_interrupt(TilemCalc* calc, dword inttype,
+				  dword mask)
+{
+	if (!(calc->linkport.mode & mask))
+		return;
+
+	calc->z80.interrupts |= inttype;
+}
+
+static inline void dbus_set_lines(TilemCalc* calc, byte lines)
+{
+	if (lines != calc->linkport.lines) {
+		calc->linkport.lines = lines;
+		if (calc->linkport.linkemu == TILEM_LINK_EMULATOR_BLACK) {
+			tilem_z80_stop(calc, TILEM_STOP_LINK_STATE);
+		}
+	}
+}
+
+static inline void dbus_set_extlines(TilemCalc* calc, byte lines)
+{
+	if ((lines ^ calc->linkport.extlines) & ~calc->linkport.lines) {
+		dbus_interrupt(calc, TILEM_INTERRUPT_LINK_ACTIVE,
+			       TILEM_LINK_MODE_INT_ON_ACTIVE);
+	}
+	calc->linkport.extlines = lines;
+}
+
+void tilem_linkport_assist_timer(TilemCalc* calc,
+				 void* data TILEM_ATTR_UNUSED)
+{
+	TilemLinkport* lp = &calc->linkport;
+
+	if (lp->assistflags & TILEM_LINK_ASSIST_WRITE_BUSY) {
+		lp->assistflags &= ~TILEM_LINK_ASSIST_WRITE_BUSY;
+		lp->assistflags |= TILEM_LINK_ASSIST_WRITE_ERROR;
+	}
+	else if (lp->assistflags & TILEM_LINK_ASSIST_READ_BUSY) {
+		lp->assistflags &= ~TILEM_LINK_ASSIST_READ_BUSY;
+		lp->assistflags |= TILEM_LINK_ASSIST_READ_ERROR;
+	}
+	else
+		return;
+
+	dbus_interrupt(calc, TILEM_INTERRUPT_LINK_ERROR,
+		       TILEM_LINK_MODE_INT_ON_ERROR);
+}
+
+static inline void assist_set_timeout(TilemCalc* calc)
+{
+	if (calc->linkport.mode & TILEM_LINK_MODE_NO_TIMEOUT)
+		return;
+
+	tilem_z80_set_timer(calc, TILEM_TIMER_LINK_ASSIST, 2000000, 0, 1);
+}
+
+static inline void assist_clear_timeout(TilemCalc* calc)
+{
+	tilem_z80_set_timer(calc, TILEM_TIMER_LINK_ASSIST, 0, 0, 0);
+}
+
+static void assist_update_write(TilemCalc* calc)
+{
+	switch (calc->linkport.extlines) {
+	case 0:
+		if (calc->linkport.lines == 0 && calc->linkport.assistoutbits > 0) {
+			/* Ready to send next bit */
+			if (calc->linkport.assistout & 1)
+				dbus_set_lines(calc, 2);
+			else
+				dbus_set_lines(calc, 1);
+			calc->linkport.assistout >>= 1;
+			calc->linkport.assistoutbits--;
+			assist_set_timeout(calc); /* other device must
+						     respond within 2
+						     seconds */
+		}
+		else if (calc->linkport.lines == 0) {
+			/* Finished sending a byte */
+			calc->linkport.assistflags &= ~TILEM_LINK_ASSIST_WRITE_BUSY;
+			assist_clear_timeout(calc);
+		}
+		break;
+
+	case 1:
+	case 2:
+		if (calc->linkport.extlines == (calc->linkport.lines ^ 3)) {
+			/* Other device acknowledged our bit.  Note
+			   that the timeout is NOT set at this point.
+			   My experiments indicate that the assist
+			   will wait, apparently indefinitely, for the
+			   other device to bring its lines high. */
+			dbus_set_lines(calc, 0);
+			assist_clear_timeout(calc);
+		}
+		break;
+
+	case 3:
+		/* illegal line state; flag error */
+		calc->linkport.assistflags &= ~TILEM_LINK_ASSIST_WRITE_BUSY;
+		calc->linkport.assistflags |= TILEM_LINK_ASSIST_WRITE_ERROR;
+		dbus_set_lines(calc, 0);
+		assist_clear_timeout(calc);
+		dbus_interrupt(calc, TILEM_INTERRUPT_LINK_ERROR,
+			       TILEM_LINK_MODE_INT_ON_ERROR);
+		break;
+	}
+}
+
+static void assist_update_read(TilemCalc* calc)
+{
+	switch (calc->linkport.extlines) {
+	case 0:
+		/* Finished receiving a bit */
+		if (calc->linkport.lines == 1) {
+			calc->linkport.assistin >>= 1;
+			calc->linkport.assistin |= 0x80;
+			calc->linkport.assistinbits++;
+		}
+		else if (calc->linkport.lines == 2) {
+			calc->linkport.assistin >>= 1;
+			calc->linkport.assistinbits++;
+		}
+
+		if (calc->linkport.assistinbits >= 8) {
+			/* finished receiving a byte */
+			calc->linkport.assistlastbyte = calc->linkport.assistin;
+			calc->linkport.assistflags &= ~TILEM_LINK_ASSIST_READ_BUSY;
+			calc->linkport.assistflags |= TILEM_LINK_ASSIST_READ_BYTE;
+			assist_clear_timeout(calc);
+		}
+		else {
+			assist_set_timeout(calc); /* other device must
+						     send next bit
+						     within 2
+						     seconds */
+		}
+
+		dbus_set_lines(calc, 0); /* indicate we're ready to
+					    receive */
+		break;
+
+	case 1:
+		/* other device sent a zero; acknowledge it */
+		calc->linkport.assistflags |= TILEM_LINK_ASSIST_READ_BUSY;
+		dbus_set_lines(calc, 2);
+		assist_set_timeout(calc); /* other device must bring
+					     both lines high again
+					     within 2 seconds */
+		break;
+
+	case 2:
+		/* same as above, but other device sent a one */
+		calc->linkport.assistflags |= TILEM_LINK_ASSIST_READ_BUSY;
+		dbus_set_lines(calc, 1);
+		assist_set_timeout(calc); /* other device must bring
+					     both lines high again
+					     within 2 seconds */
+		break;
+
+	case 3:
+		/* illegal line state; flag error */
+		calc->linkport.assistflags &= ~TILEM_LINK_ASSIST_READ_BUSY;
+		calc->linkport.assistflags |= TILEM_LINK_ASSIST_READ_ERROR;
+		dbus_set_lines(calc, 0);
+		assist_clear_timeout(calc);
+		dbus_interrupt(calc, TILEM_INTERRUPT_LINK_ERROR,
+			       TILEM_LINK_MODE_INT_ON_ERROR);
+		break;
+	}
+}
+
+static void graylink_update_write(TilemCalc* calc)
+{
+	switch (calc->linkport.lines) {
+	case 0:
+		if (calc->linkport.extlines == 0 && calc->linkport.graylinkoutbits > 1) {
+			/* Ready to send next bit */
+			if (calc->linkport.graylinkout & 1)
+				dbus_set_extlines(calc, 2);
+			else
+				dbus_set_extlines(calc, 1);
+			calc->linkport.graylinkout >>= 1;
+			calc->linkport.graylinkoutbits--;
+		}
+		else if (calc->linkport.extlines == 0) {
+			/* Finished sending a byte */
+			calc->linkport.graylinkoutbits = 0;
+			tilem_z80_stop(calc, TILEM_STOP_LINK_WRITE_BYTE);
+		}
+		break;
+
+	case 1:
+	case 2:
+		if (calc->linkport.extlines == (calc->linkport.lines ^ 3))
+			/* Other device acknowledged our bit */
+			dbus_set_extlines(calc, 0);
+		break;
+
+	case 3:
+		/* illegal line state; flag error */
+		dbus_set_extlines(calc, 0);
+		calc->linkport.graylinkoutbits = 0;
+		tilem_z80_stop(calc, TILEM_STOP_LINK_ERROR);
+		break;
+	}
+}
+
+static void graylink_update_read(TilemCalc* calc)
+{
+	switch (calc->linkport.lines) {
+	case 0:
+		/* Finished receiving a bit */
+		if (calc->linkport.extlines == 1) {
+			calc->linkport.graylinkin >>= 1;
+			calc->linkport.graylinkin |= 0x80;
+			calc->linkport.graylinkinbits++;
+		}
+		else if (calc->linkport.extlines == 2) {
+			calc->linkport.graylinkin >>= 1;
+			calc->linkport.graylinkinbits++;
+		}
+
+		if (calc->linkport.graylinkinbits >= 8) {
+			/* finished receiving a byte */
+			tilem_z80_stop(calc, TILEM_STOP_LINK_READ_BYTE);
+		}
+
+		dbus_set_extlines(calc, 0);
+		break;
+
+	case 1:
+		/* other device sent a zero; acknowledge it */
+		dbus_set_extlines(calc, 2);
+		break;
+
+	case 2:
+		/* same as above, but other device sent a one */
+		dbus_set_extlines(calc, 1);
+		break;
+
+	case 3:
+		/* illegal line state; flag error */
+		dbus_set_extlines(calc, 0);
+		calc->linkport.graylinkinbits = 0;
+		tilem_z80_stop(calc, TILEM_STOP_LINK_ERROR);
+		break;
+	}
+}
+
+static void dbus_update(TilemCalc* calc)
+{
+	byte oldlines;
+
+	do {
+		if (calc->linkport.linkemu == TILEM_LINK_EMULATOR_GRAY) {
+			if (calc->linkport.graylinkoutbits) {
+				graylink_update_write(calc);
+			}
+			else if (calc->linkport.graylinkinbits != 8) {
+				graylink_update_read(calc);
+			}
+		}
+
+		oldlines = calc->linkport.lines;
+		if (calc->linkport.assistflags & TILEM_LINK_ASSIST_WRITE_BUSY) {
+			assist_update_write(calc);
+		}
+		else if (calc->linkport.mode & TILEM_LINK_MODE_ASSIST
+			 && !(calc->linkport.assistflags & TILEM_LINK_ASSIST_READ_BYTE)) {
+			assist_update_read(calc);
+		}
+	} while (oldlines != calc->linkport.lines);
+
+	if ((calc->linkport.assistflags & TILEM_LINK_ASSIST_READ_BYTE)
+	    && (calc->linkport.mode & TILEM_LINK_MODE_INT_ON_READ))
+		calc->z80.interrupts |= TILEM_INTERRUPT_LINK_READ;
+	else
+		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_READ;
+
+	if (!(calc->linkport.assistflags & (TILEM_LINK_ASSIST_READ_BUSY
+					    | TILEM_LINK_ASSIST_WRITE_BUSY))
+	    && (calc->linkport.mode & TILEM_LINK_MODE_INT_ON_IDLE))
+		calc->z80.interrupts |= TILEM_INTERRUPT_LINK_IDLE;
+	else
+		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_IDLE;
+}
+
+void tilem_linkport_reset(TilemCalc* calc)
+{
+	dbus_set_lines(calc, 0);
+	assist_clear_timeout(calc);
+	calc->linkport.mode = 0;
+	calc->linkport.assistflags = 0;
+	calc->linkport.assistin = 0;
+	calc->linkport.assistinbits = 0;
+	calc->linkport.assistout = 0;
+	calc->linkport.assistoutbits = 0;
+	calc->linkport.assistlastbyte = 0;
+}
+
+byte tilem_linkport_get_lines(TilemCalc* calc)
+{
+	//dbus_update(calc);
+	return (~calc->linkport.lines & ~calc->linkport.extlines & 3);
+}
+
+void tilem_linkport_set_lines(TilemCalc* calc, byte lines)
+{
+	if (!(calc->linkport.mode & TILEM_LINK_MODE_ASSIST)
+	    && !(calc->linkport.assistflags & TILEM_LINK_ASSIST_WRITE_BUSY))
+		dbus_set_lines(calc, lines & 3);
+
+	dbus_update(calc);
+}
+
+byte tilem_linkport_read_byte(TilemCalc* calc)
+{
+	byte value = calc->linkport.assistin;
+	calc->linkport.assistflags &= ~(TILEM_LINK_ASSIST_READ_BYTE
+			     | TILEM_LINK_ASSIST_READ_BUSY);
+	calc->linkport.assistinbits = 0;
+	dbus_update(calc);
+	return value;
+}
+
+void tilem_linkport_write_byte(TilemCalc* calc, byte data)
+{
+	if (calc->linkport.assistflags & (TILEM_LINK_ASSIST_READ_BUSY
+			       | TILEM_LINK_ASSIST_WRITE_BUSY))
+		return;
+
+	dbus_set_lines(calc, 0);
+	calc->linkport.assistout = data;
+	calc->linkport.assistoutbits = 8;
+	calc->linkport.assistflags |= TILEM_LINK_ASSIST_WRITE_BUSY;
+	dbus_update(calc);
+}
+
+unsigned int tilem_linkport_get_assist_flags(TilemCalc* calc)
+{
+	//dbus_update(calc);
+	return calc->linkport.assistflags;
+}
+
+void tilem_linkport_set_mode(TilemCalc* calc, unsigned int mode)
+{
+	if ((mode ^ calc->linkport.mode) & TILEM_LINK_MODE_ASSIST) {
+		dbus_set_lines(calc, 0);
+		calc->linkport.assistflags &= ~(TILEM_LINK_ASSIST_READ_BUSY
+				     | TILEM_LINK_ASSIST_WRITE_BUSY);
+		assist_clear_timeout(calc);
+	}
+
+	if (!(mode & TILEM_LINK_MODE_INT_ON_ACTIVE))
+		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_ACTIVE;
+	if (!(mode & TILEM_LINK_MODE_INT_ON_ERROR))
+		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_ERROR;
+
+	calc->linkport.mode = mode;
+
+	dbus_update(calc);
+}
+
+
+/* External BlackLink emulation */
+
+void tilem_linkport_blacklink_set_lines(TilemCalc* calc, byte lines)
+{
+	dbus_set_extlines(calc, lines & 3);
+	dbus_update(calc);
+}
+
+byte tilem_linkport_blacklink_get_lines(TilemCalc* calc)
+{
+	dbus_update(calc);
+	return (~calc->linkport.lines & ~calc->linkport.extlines & 3);
+}
+
+
+/* External GrayLink emulation */
+
+void tilem_linkport_graylink_reset(TilemCalc* calc)
+{
+	calc->linkport.graylinkin = 0;
+	calc->linkport.graylinkinbits = 0;
+	calc->linkport.graylinkout = 0;
+	calc->linkport.graylinkoutbits = 0;
+	dbus_set_extlines(calc, 0);
+	dbus_update(calc);
+}
+
+int tilem_linkport_graylink_ready(TilemCalc* calc)
+{
+	if (calc->linkport.graylinkoutbits
+	    || calc->linkport.graylinkinbits)
+		return 0;
+	else
+		return 1;
+}
+
+int tilem_linkport_graylink_send_byte(TilemCalc* calc, byte value)
+{
+	if (!tilem_linkport_graylink_ready(calc))
+		return -1;
+
+	dbus_set_extlines(calc, 0);
+
+	/* set to 9 because we want to wait for the calc to bring both
+	   link lines low before we send the first bit, and also after
+	   we send the last bit */
+	calc->linkport.graylinkoutbits = 9;
+
+	calc->linkport.graylinkout = value;
+	dbus_update(calc);
+	return 0;
+}
+
+int tilem_linkport_graylink_get_byte(TilemCalc* calc)
+{
+	dbus_update(calc);
+	if (calc->linkport.graylinkinbits != 8)
+		return -1;
+
+	calc->linkport.graylinkinbits = 0;
+	return calc->linkport.graylinkin;
+}
diff --git a/tool/tilem-src/emu/md5.c b/tool/tilem-src/emu/md5.c
new file mode 100644
index 0000000..14f8996
--- /dev/null
+++ b/tool/tilem-src/emu/md5.c
@@ -0,0 +1,86 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include "tilem.h"
+
+void tilem_md5_assist_reset(TilemCalc* calc)
+{
+	int i;
+
+	for (i = 0; i < 6; i++)
+		calc->md5assist.regs[i] = 0;
+	calc->md5assist.shift = 0;
+	calc->md5assist.mode = 0;
+}
+
+dword tilem_md5_assist_get_value(TilemCalc* calc)
+{
+	/* Return the result of a complete MD5 operation:
+	   b + ((a + f(b,c,d) + X + T) <<< s) */
+	dword a, b, c, d, x, t, result;
+	byte mode, s;
+
+	mode = calc->md5assist.mode;
+	a = calc->md5assist.regs[TILEM_MD5_REG_A];
+	b = calc->md5assist.regs[TILEM_MD5_REG_B];
+	c = calc->md5assist.regs[TILEM_MD5_REG_C];
+	d = calc->md5assist.regs[TILEM_MD5_REG_D];
+	x = calc->md5assist.regs[TILEM_MD5_REG_X];
+	t = calc->md5assist.regs[TILEM_MD5_REG_T];
+	s = calc->md5assist.shift;
+
+	switch (mode) {
+	case TILEM_MD5_FUNC_FF:
+		/* F(X,Y,Z) = XY v not(X) Z */
+		result = (b & c) | ((~b) & d);
+		break;
+
+	case TILEM_MD5_FUNC_GG:
+		/* G(X,Y,Z) = XZ v Y not(Z) */
+		result = (b & d) | (c & (~d));
+		break;
+
+	case TILEM_MD5_FUNC_HH:
+		/* H(X,Y,Z) = X xor Y xor Z */
+		result = b ^ c ^ d;
+		break;
+
+	case TILEM_MD5_FUNC_II:
+		/* I(X,Y,Z) = Y xor (X v not(Z)) */
+		result = c ^ (b | (~d));
+		break;
+
+	default:
+		tilem_internal(calc, "Invalid MD5 mode %d", mode);
+		return 0;
+	}
+
+	result += a + x + t;
+	result &= 0xffffffff;
+	result = (result << s) | (result >> (32 - s));
+	result += b;
+
+	return result;
+}
diff --git a/tool/tilem-src/emu/monolcd.c b/tool/tilem-src/emu/monolcd.c
new file mode 100644
index 0000000..6c6f173
--- /dev/null
+++ b/tool/tilem-src/emu/monolcd.c
@@ -0,0 +1,148 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "tilem.h"
+
+TilemLCDBuffer* tilem_lcd_buffer_new()
+{
+	return tilem_new0(TilemLCDBuffer, 1);
+}
+
+void tilem_lcd_buffer_free(TilemLCDBuffer *buf)
+{
+	tilem_free(buf->data);
+	tilem_free(buf->tmpbuf);
+	tilem_free(buf);
+}
+
+void tilem_lcd_get_frame(TilemCalc * restrict calc,
+                         TilemLCDBuffer * restrict buf)
+{
+	byte * restrict bp;
+	byte * restrict op;
+	int dwidth = calc->hw.lcdwidth;
+	int dheight = calc->hw.lcdheight;
+	unsigned int size;
+	int bwidth = ((calc->hw.lcdwidth + 7) / 8);
+	int i, j;
+
+	if (TILEM_UNLIKELY(buf->height != dheight
+	                   || buf->rowstride != bwidth * 8)) {
+		/* reallocate data buffer */
+		tilem_free(buf->data);
+		buf->data = tilem_new_atomic(byte, dwidth * bwidth * 8);
+		buf->rowstride = bwidth * 8;
+		buf->height = dheight;
+	}
+
+	size = bwidth * dheight * sizeof(byte);
+	if (TILEM_UNLIKELY(buf->tmpbufsize < size)) {
+		/* reallocate temp buffer */
+		tilem_free(buf->tmpbuf);
+		buf->tmpbuf = tilem_malloc_atomic(size);
+		buf->tmpbufsize = size;
+	}
+
+	buf->width = dwidth;
+
+	buf->stamp = calc->z80.lastlcdwrite;
+
+	if (!calc->lcd.active || (calc->z80.halted && !calc->poweronhalt)) {
+		/* screen is turned off */
+		buf->contrast = 0;
+		return;
+	}
+
+	buf->contrast = calc->lcd.contrast;
+
+	bp = buf->tmpbuf;
+	op = buf->data;
+	(*calc->hw.get_lcd)(calc, bp);
+
+	for (i = 0; i < bwidth * dheight; i++) {
+		for (j = 0; j < 8; j++) {
+			*op = (*bp << j) & 0x80;
+			op++;
+		}
+		bp++;
+	}
+}
+
+/* Do the same thing as tilem_lcd_get_frame, but output is only 0 and 1 */
+void tilem_lcd_get_frame1(TilemCalc * restrict calc,
+                         TilemLCDBuffer * restrict buf)
+{
+	byte * restrict bp;
+	byte * restrict op;
+	int dwidth = calc->hw.lcdwidth;
+	int dheight = calc->hw.lcdheight;
+	unsigned int size;
+	int bwidth = ((calc->hw.lcdwidth + 7) / 8);
+	int i, j;
+
+	if (TILEM_UNLIKELY(buf->height != dheight
+	                   || buf->rowstride != bwidth * 8)) {
+		/* reallocate data buffer */
+		tilem_free(buf->data);
+		buf->data = tilem_new_atomic(byte, dwidth * bwidth * 8);
+		buf->rowstride = bwidth * 8;
+		buf->height = dheight;
+	}
+
+	size = bwidth * dheight * sizeof(byte);
+	if (TILEM_UNLIKELY(buf->tmpbufsize < size)) {
+		/* reallocate temp buffer */
+		tilem_free(buf->tmpbuf);
+		buf->tmpbuf = tilem_malloc_atomic(size);
+		buf->tmpbufsize = size;
+	}
+
+	buf->width = dwidth;
+
+	buf->stamp = calc->z80.lastlcdwrite;
+
+	if (!calc->lcd.active || (calc->z80.halted && !calc->poweronhalt)) {
+		/* screen is turned off */
+		buf->contrast = 0;
+		return;
+	}
+
+	buf->contrast = calc->lcd.contrast;
+
+	bp = buf->tmpbuf;
+	op = buf->data;
+	(*calc->hw.get_lcd)(calc, bp);
+
+	for (i = 0; i < bwidth * dheight; i++) {
+		for (j = 0; j < 8; j++) {
+			*op = (*bp << j) & 0x80;
+			if(*op != 0)
+				*op = 1;
+			op++;
+		}
+		bp++;
+	}
+}
diff --git a/tool/tilem-src/emu/rom.c b/tool/tilem-src/emu/rom.c
new file mode 100644
index 0000000..9cc24c9
--- /dev/null
+++ b/tool/tilem-src/emu/rom.c
@@ -0,0 +1,118 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "tilem.h"
+
+static int find_string(const char *str, FILE *romfile,
+		       dword start, dword limit)
+{
+	char buf[256];
+	int pos = 0;
+	int len, i;
+
+	len = strlen(str);
+
+	fseek(romfile, (long int) start, SEEK_SET);
+
+	for (i = 0; i < len-1; i++) {
+		buf[pos] = fgetc(romfile);
+		pos = (pos+1)%256;
+		limit--;
+	}
+
+	while (limit > 0 && !feof(romfile) && !ferror(romfile)) {
+		buf[pos] = fgetc(romfile);
+		pos = (pos+1)%256;
+		limit--;
+
+		for (i = 0; i < len; i++) {
+			if (str[i] != buf[(pos + 256 - len + i)%256])
+				break;
+		}
+		if (i == len)
+			return 1;
+	}
+	return 0;
+}
+
+char tilem_guess_rom_type(FILE* romfile)
+{
+	unsigned long initpos;
+	dword size;
+	char result;
+
+	initpos = ftell(romfile);
+
+	fseek(romfile, 0L, SEEK_END);
+	size = ftell(romfile);
+
+	if (size >= 0x8000 && size < 0x9000) {
+		/* 32k: TI-81 (old or new) */
+		result = TILEM_CALC_TI81;
+	}
+	else if (size >= 0x20000 && size < 0x2C000) {
+		/* 128k: TI-82 or TI-86 */
+		if (find_string("CATALOG", romfile, 0, 0x20000))
+			result = TILEM_CALC_TI85;
+		else
+			result = TILEM_CALC_TI82;
+	}
+	else if (size >= 0x40000 && size < 0x4C000) {
+		/* 256k: TI-83 (or a variant) or TI-86 */
+		if (!find_string("TI82", romfile, 0, 0x40000))
+			result = TILEM_CALC_TI86;
+		else if (find_string("Termin\x96", romfile, 0, 0x40000))
+			result = TILEM_CALC_TI76;
+		else
+			result = TILEM_CALC_TI83;
+	}
+	else if (size >= 0x80000 && size < 0x8C000) {
+		/* 512k: TI-83 Plus or TI-73 */
+		if (find_string("TI-83 Plus", romfile, 0, 8 * 0x4000))
+			result = TILEM_CALC_TI83P;
+		else
+			result = TILEM_CALC_TI73;
+	}
+	else if (size >= 0x100000 && size < 0x124000) {
+		/* 1024k: TI-84 Plus */
+		result = TILEM_CALC_TI84P;
+	}
+	else if (size >= 0x200000 && size < 0x224000) {
+		/* 2048k: TI-83 Plus SE, TI-84 Plus SE */
+		if (find_string("\xed\xef", romfile, 0x1FC000, 0x4000))
+			result = TILEM_CALC_TI84P_NSPIRE;
+		else if (find_string("Operating", romfile, 0x1FC000, 0x4000))
+			result = TILEM_CALC_TI84P_SE;
+		else
+			result = TILEM_CALC_TI83P_SE;
+	}
+	else {
+		result = 0;
+	}
+
+	fseek(romfile, initpos, SEEK_SET);
+	return result;
+}
diff --git a/tool/tilem-src/emu/scancodes.h b/tool/tilem-src/emu/scancodes.h
new file mode 100644
index 0000000..7662a18
--- /dev/null
+++ b/tool/tilem-src/emu/scancodes.h
@@ -0,0 +1,85 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_SCANCODES_H
+#define _TILEM_SCANCODES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum {
+	TILEM_KEY_DOWN     = 0x01,
+	TILEM_KEY_LEFT     = 0x02,
+	TILEM_KEY_RIGHT    = 0x03,
+	TILEM_KEY_UP       = 0x04,
+	TILEM_KEY_ENTER    = 0x09,
+	TILEM_KEY_ADD      = 0x0A,
+	TILEM_KEY_SUB      = 0x0B,
+	TILEM_KEY_MUL      = 0x0C,
+	TILEM_KEY_DIV      = 0x0D,
+	TILEM_KEY_POWER    = 0x0E,
+	TILEM_KEY_CLEAR    = 0x0F,
+	TILEM_KEY_CHS      = 0x11,
+	TILEM_KEY_3        = 0x12,
+	TILEM_KEY_6        = 0x13,
+	TILEM_KEY_9        = 0x14,
+	TILEM_KEY_RPAREN   = 0x15,
+	TILEM_KEY_TAN      = 0x16,
+	TILEM_KEY_VARS     = 0x17,
+	TILEM_KEY_DECPNT   = 0x19,
+	TILEM_KEY_2        = 0x1A,
+	TILEM_KEY_5        = 0x1B,
+	TILEM_KEY_8        = 0x1C,
+	TILEM_KEY_LPAREN   = 0x1D,
+	TILEM_KEY_COS      = 0x1E,
+	TILEM_KEY_PRGM     = 0x1F,
+	TILEM_KEY_STAT     = 0x20,
+	TILEM_KEY_0        = 0x21,
+	TILEM_KEY_1        = 0x22,
+	TILEM_KEY_4        = 0x23,
+	TILEM_KEY_7        = 0x24,
+	TILEM_KEY_COMMA    = 0x25,
+	TILEM_KEY_SIN      = 0x26,
+	TILEM_KEY_MATRIX   = 0x27,
+	TILEM_KEY_GRAPHVAR = 0x28,
+	TILEM_KEY_ON       = 0x29,
+	TILEM_KEY_STORE    = 0x2A,
+	TILEM_KEY_LN       = 0x2B,
+	TILEM_KEY_LOG      = 0x2C,
+	TILEM_KEY_SQUARE   = 0x2D,
+	TILEM_KEY_RECIP    = 0x2E,
+	TILEM_KEY_MATH     = 0x2F,
+	TILEM_KEY_ALPHA    = 0x30,
+	TILEM_KEY_GRAPH    = 0x31,
+	TILEM_KEY_TRACE    = 0x32,
+	TILEM_KEY_ZOOM     = 0x33,
+	TILEM_KEY_WINDOW   = 0x34,
+	TILEM_KEY_YEQU     = 0x35,
+	TILEM_KEY_2ND      = 0x36,
+	TILEM_KEY_MODE     = 0x37,
+	TILEM_KEY_DEL      = 0x38
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tool/tilem-src/emu/state.c b/tool/tilem-src/emu/state.c
new file mode 100644
index 0000000..2447866
--- /dev/null
+++ b/tool/tilem-src/emu/state.c
@@ -0,0 +1,892 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "tilem.h"
+#include "z80.h"
+
+static void set_hw_reg(TilemCalc* calc, const char* name, dword value)
+{
+	int i;
+
+	for (i = 0; i < calc->hw.nhwregs; i++) {
+		if (!strcmp(name, calc->hw.hwregnames[i])) {
+			calc->hwregs[i] = value;
+			return;
+		}
+	}
+
+	tilem_warning(calc, "Unknown hwreg %s", name);
+}
+
+static const char* get_timer_name(TilemCalc* calc, int id)
+{
+	if (id == TILEM_TIMER_LCD_DELAY)
+		return "lcddelay";
+	else if (id == TILEM_TIMER_FLASH_DELAY)
+		return "flashdelay";
+	else if (id == TILEM_TIMER_LINK_ASSIST)
+		return "linkassist";
+	else if (id == TILEM_TIMER_USER1)
+		return "user1";
+	else if (id == TILEM_TIMER_USER2)
+		return "user2";
+	else if (id == TILEM_TIMER_USER3)
+		return "user3";
+	else if (id <= TILEM_NUM_SYS_TIMERS)
+		abort();
+
+	id -= TILEM_NUM_SYS_TIMERS + 1;
+	if (id < calc->hw.nhwtimers)
+		return calc->hw.hwtimernames[id];
+	else
+		return NULL;
+}
+
+static void set_ptimer(TilemCalc* calc, const char* name, dword value,
+		       dword period, int rt)
+{
+	int i;
+	const char* tname;
+
+	for (i = 1; i <= calc->z80.ntimers; i++) {
+		tname = get_timer_name(calc, i);
+		if (tname && !strcmp(name, tname)) {
+			tilem_z80_set_timer(calc, i, value, period, rt);
+			return;
+		}
+	}
+
+	tilem_warning(calc, "Unknown timer %s", name);
+}
+
+static int load_old_sav_file(TilemCalc* calc, FILE* savfile)
+{
+	byte b[76];
+	dword regs[19];
+	int i, le, be, c;
+	unsigned int pageA, pageB;
+
+	/* Read memory mapping */
+
+	if (fread(calc->mempagemap, 1, 4, savfile) < 4)
+		return 1;
+
+	/* Read CPU registers */
+
+	if (fread(b, 1, 76, savfile) < 76)
+		return 1;
+
+	be = le = 0;
+
+	/* determine if file is in big-endian or little-endian
+	   format */
+
+	for (i = 0; i < 19; i++) {
+		if (b[i * 4] || b[i * 4 + 1])
+			le++;
+		if (b[i * 4 + 2] || b[i * 4 + 3])
+			be++;
+	}
+
+	if (le > be) {
+		for (i = 0; i < 19; i++) {
+			regs[i] = b[i * 4] + (b[i * 4 + 1] << 8);
+		}
+	}
+	else {
+		for (i = 0; i < 19; i++) {
+			regs[i] = b[i * 4 + 3] + (b[i * 4 + 2] << 8);
+		}
+	}
+
+	calc->z80.r.af.d = regs[0];
+	calc->z80.r.bc.d = regs[1];
+	calc->z80.r.de.d = regs[2];
+	calc->z80.r.hl.d = regs[3];
+	calc->z80.r.ix.d = regs[4];
+	calc->z80.r.iy.d = regs[5];
+	calc->z80.r.pc.d = regs[6];
+	calc->z80.r.sp.d = regs[7];
+	calc->z80.r.af2.d = regs[8];
+	calc->z80.r.bc2.d = regs[9];
+	calc->z80.r.de2.d = regs[10];
+	calc->z80.r.hl2.d = regs[11];
+	calc->z80.r.iff1 = regs[12] ? 1 : 0;
+	calc->z80.r.iff2 = regs[13] ? 1 : 0;
+	calc->z80.r.im = regs[15];
+	calc->z80.r.ir.b.h = regs[16];
+	calc->z80.r.ir.b.l = regs[17];
+	calc->z80.r.r7 = regs[18] & 0x80;
+
+	if (calc->hw.model_id == '2' || calc->hw.model_id == '3') {
+		if (fread(b, 1, 5, savfile) < 5)
+			return 1;
+
+		if (calc->hw.model_id == '3')
+			set_hw_reg(calc, "rom_bank", calc->mempagemap[1] & 0x08);
+		calc->hw.z80_out(calc, 0x02, b[4]);
+	}
+
+	/* Read RAM contents: old save files for TI-82/83/85 store RAM
+	   pages in logical rather than physical order */
+
+	if (calc->hw.model_id == '2' || calc->hw.model_id == '3'
+	    || calc->hw.model_id == '5') {
+		if (fread(calc->mem + calc->hw.romsize + 0x4000, 1,
+			  0x4000, savfile) < 0x4000)
+			return 1;
+		if (fread(calc->mem + calc->hw.romsize, 1,
+			  0x4000, savfile) < 0x4000)
+			return 1;
+	}
+	else {
+		if (fread(calc->mem + calc->hw.romsize, 1,
+			  calc->hw.ramsize, savfile) < calc->hw.ramsize)
+			return 1;
+	}
+
+	/* Read LCD contents */
+
+	if (calc->hw.flags & TILEM_CALC_HAS_T6A04) {
+		calc->lcd.rowstride = 12; /* old save files only
+					     support the visible
+					     portion of the screen */
+		if (fread(calc->lcdmem, 1, 768, savfile) < 768)
+			return 1;
+	}
+
+	/* Read additional HW state */
+
+	switch (calc->hw.model_id) {
+	case '1':
+		break;
+
+	case '2':
+	case '3':
+		if ((c = fgetc(savfile)) != EOF)
+			calc->lcd.mode = c;
+		if ((c = fgetc(savfile)) != EOF)
+			calc->lcd.x = c;
+		if ((c = fgetc(savfile)) != EOF)
+			calc->lcd.y = c;
+		break;
+
+	case '5':
+		pageA = calc->mempagemap[1];
+		if (pageA >= 0x08)
+			pageA += 0x38;
+		calc->hw.z80_out(calc, 0x05, pageA);
+
+		if ((c = fgetc(savfile)) != EOF)
+			calc->hw.z80_out(calc, 0x06, c);
+		break;
+
+	case '6':
+		pageA = calc->mempagemap[1];
+		pageB = calc->mempagemap[2];
+		if (pageA >= 0x10)
+			pageA += 0x30;
+		if (pageB >= 0x10)
+			pageB += 0x30;
+
+		calc->hw.z80_out(calc, 0x05, pageA);
+		calc->hw.z80_out(calc, 0x06, pageB);
+		break;
+
+	default:		/* TI-73/83+ series */
+		if ((c = fgetc(savfile)) != EOF)
+			calc->lcd.mode = c;
+		if ((c = fgetc(savfile)) != EOF)
+			calc->lcd.x = c;
+		if ((c = fgetc(savfile)) != EOF)
+			calc->lcd.y = c;
+		if ((c = fgetc(savfile)) != EOF)
+			calc->lcd.inc = c;
+
+		if ((c = fgetc(savfile)) == EOF)
+			c = 0;
+		if (c) {
+			pageA = calc->mempagemap[2];
+			pageB = calc->mempagemap[3];
+			calc->hw.z80_out(calc, 0x04, 0x77);
+		}
+		else {
+			pageA = calc->mempagemap[1];
+			pageB = calc->mempagemap[2];
+			calc->hw.z80_out(calc, 0x04, 0x76);
+		}
+
+		if (pageA >= (calc->hw.romsize >> 14))
+			pageA = ((pageA & 0x1f) | calc->hw.rampagemask);
+		if (pageB >= (calc->hw.romsize >> 14))
+			pageB = ((pageB & 0x1f) | calc->hw.rampagemask);
+
+		calc->hw.z80_out(calc, 0x06, pageA);
+		calc->hw.z80_out(calc, 0x07, pageB);
+
+		if ((c = fgetc(savfile)) != EOF)
+			calc->flash.state = c;
+		if ((c = fgetc(savfile)) != EOF)
+			calc->flash.unlock = c;
+
+		if ((c = fgetc(savfile)) != EOF)
+			calc->hw.z80_out(calc, 0x20, c);
+		if ((c = fgetc(savfile)) != EOF)
+			set_hw_reg(calc, "port21", c);
+		if ((c = fgetc(savfile)) != EOF)
+			set_hw_reg(calc, "port22", c);
+		if ((c = fgetc(savfile)) != EOF)
+			set_hw_reg(calc, "port23", c);
+		if ((c = fgetc(savfile)) != EOF)
+			calc->hw.z80_out(calc, 0x27, c);
+		if ((c = fgetc(savfile)) != EOF)
+			calc->hw.z80_out(calc, 0x28, c);
+		break;
+	}
+
+	calc->poweronhalt = calc->lcd.active = 1;
+
+	return 0;
+}
+
+static int read_sav_line(FILE* savfile, char **buf)
+{
+	int c, n, na;
+
+	tilem_free(*buf);
+
+	na = 100;
+	*buf = tilem_malloc_atomic(na);
+	n = 0;
+
+	while ((c = fgetc(savfile)) != EOF) {
+		if (c == '\r' || c == '\n')
+			break;
+
+		n++;
+		if (n >= na) {
+			na = n * 2;
+			*buf = tilem_realloc(*buf, na);
+		}
+
+		if (c == '#')
+			c = 0;
+		(*buf)[n - 1] = c;
+	}
+
+	if (n == 0 && c == EOF) {
+		tilem_free(*buf);
+		*buf = NULL;
+		return 0;
+	}
+	else {
+		(*buf)[n] = 0;
+		return 1;
+	}
+}
+
+static int parse_sav_definition(char* line, char** value)
+{
+	char *p;
+
+	p = strchr(line, '=');
+	if (!p)
+		return 0;
+
+	while (p != line && p[-1] == ' ')
+		p--;
+	*p = 0;
+	p++;
+	while (*p == ' ' || *p == '=')
+		p++;
+	*value = p;
+	return 1;
+}
+
+static int load_new_sav_file(TilemCalc* calc, FILE* savfile)
+{
+	char *buf = NULL;
+	char *p, *q;
+	dword value, length;
+	byte *data;
+	int ok = 0;
+	byte digit;
+	int firstdigit;
+	dword period;
+	int rt;
+
+	while (read_sav_line(savfile, &buf)) {
+		if (!parse_sav_definition(buf, &p))
+			continue;
+
+		if (*p == '{') {
+			p++;
+			if (!strcmp(buf, "RAM")) {
+				length = calc->hw.ramsize;
+				data = calc->ram;
+			}
+			else if (!strcmp(buf, "LCD")) {
+				length = calc->hw.lcdmemsize;
+				data = calc->lcdmem;
+			}
+			else {
+				length = 0;
+				data = NULL;
+			}
+
+			value = 0;
+			firstdigit = 1;
+
+			while (*p != '}') {
+				if (*p == 0 || *p == '#') {
+					if (!read_sav_line(savfile, &buf))
+						return 1;
+					p = buf;
+					continue;
+				}
+
+				if (*p >= '0' && *p <= '9') {
+					digit = *p - '0';
+					p++;
+				}
+				else if (*p >= 'A' && *p <= 'F') {
+					digit = *p + 10 - 'A';
+					p++;
+				}
+				else if (*p >= 'a' && *p <= 'f') {
+					digit = *p + 10 - 'a';
+					p++;
+				}
+				else {
+					p++;
+					continue;
+				}
+
+				if (firstdigit) {
+					value = digit << 4;
+					firstdigit = 0;
+				}
+				else {
+					value |= digit;
+					if (length != 0) {
+						*data = value;
+						data++;
+						length--;
+					}
+					firstdigit = 1;
+				}
+			}
+
+			continue;
+		}
+
+		if (!strcmp(buf, "MODEL")) {
+			q = p;
+			while (*q >= ' ')
+				q++;
+			*q = 0;
+			if (strcmp(p, calc->hw.name)) {
+				tilem_free(buf);
+				return 1;
+			}
+			ok = 1;
+			continue;
+		}
+
+		value = strtol(p, &q, 16);
+
+		/* Persistent timers */
+		if (!strncmp(buf, "timer:", 6)) {
+			while (*q == ' ')
+				q++;
+			if (*q != ',')
+				continue;
+			q++;
+			while (*q == ' ')
+				q++;
+			period = strtol(q, &q, 16);
+
+			while (*q == ' ')
+				q++;
+			if (*q != ',')
+				continue;
+			q++;
+			while (*q == ' ')
+				q++;
+			rt = strtol(q, &q, 16);
+
+			set_ptimer(calc, buf + 6, value, period, rt);
+			continue;
+		}
+
+		/* Z80 */
+		if (!strcmp(buf, "af")) calc->z80.r.af.d = value;
+		else if (!strcmp(buf, "bc")) calc->z80.r.bc.d = value;
+		else if (!strcmp(buf, "de")) calc->z80.r.de.d = value;
+		else if (!strcmp(buf, "hl")) calc->z80.r.hl.d = value;
+		else if (!strcmp(buf, "af'")) calc->z80.r.af2.d = value;
+		else if (!strcmp(buf, "bc'")) calc->z80.r.bc2.d = value;
+		else if (!strcmp(buf, "de'")) calc->z80.r.de2.d = value;
+		else if (!strcmp(buf, "hl'")) calc->z80.r.hl2.d = value;
+		else if (!strcmp(buf, "ix")) calc->z80.r.ix.d = value;
+		else if (!strcmp(buf, "iy")) calc->z80.r.iy.d = value;
+		else if (!strcmp(buf, "pc")) calc->z80.r.pc.d = value;
+		else if (!strcmp(buf, "sp")) calc->z80.r.sp.d = value;
+		else if (!strcmp(buf, "ir")) {
+			calc->z80.r.ir.d = value;
+			calc->z80.r.r7 = value & 0x80;
+		}
+		else if (!strcmp(buf, "wz")) calc->z80.r.wz.d = value;
+		else if (!strcmp(buf, "wz'")) calc->z80.r.wz2.d = value;
+		else if (!strcmp(buf, "iff1")) calc->z80.r.iff1 = value;
+		else if (!strcmp(buf, "iff2")) calc->z80.r.iff2 = value;
+		else if (!strcmp(buf, "im")) calc->z80.r.im = value;
+		else if (!strcmp(buf, "interrupts"))
+			calc->z80.interrupts = value;
+		else if (!strcmp(buf, "clockspeed"))
+			calc->z80.clockspeed = value;
+		else if (!strcmp(buf, "halted")) calc->z80.halted = value;
+
+		/* LCD */
+		else if (!strcmp(buf, "lcd.active"))
+			calc->lcd.active = value;
+		else if (!strcmp(buf, "lcd.addr"))
+			calc->lcd.addr = value;
+		else if (!strcmp(buf, "lcd.rowshift"))
+			calc->lcd.rowshift = value;
+		else if (!strcmp(buf, "lcd.contrast"))
+			calc->lcd.contrast = value;
+		else if (!strcmp(buf, "lcd.inc"))
+			calc->lcd.inc = value;
+		else if (!strcmp(buf, "lcd.mode"))
+			calc->lcd.mode = value;
+		else if (!strcmp(buf, "lcd.x"))
+			calc->lcd.x = value;
+		else if (!strcmp(buf, "lcd.y"))
+			calc->lcd.y = value;
+		else if (!strcmp(buf, "lcd.nextbyte"))
+			calc->lcd.nextbyte = value;
+		else if (!strcmp(buf, "lcd.rowstride"))
+			calc->lcd.rowstride = value;
+		else if (!strcmp(buf, "lcd.busy"))
+			calc->lcd.busy = value;
+
+		/* Link port */
+		else if (!strcmp(buf, "linkport.lines"))
+			calc->linkport.lines = value;
+		else if (!strcmp(buf, "linkport.mode"))
+			calc->linkport.mode = value;
+		else if (!strcmp(buf, "linkport.assistflags"))
+			calc->linkport.assistflags = value;
+		else if (!strcmp(buf, "linkport.assistin"))
+			calc->linkport.assistin = value;
+		else if (!strcmp(buf, "linkport.assistinbits"))
+			calc->linkport.assistinbits = value;
+		else if (!strcmp(buf, "linkport.assistout"))
+			calc->linkport.assistout = value;
+		else if (!strcmp(buf, "linkport.assistoutbits"))
+			calc->linkport.assistoutbits = value;
+		else if (!strcmp(buf, "linkport.assistlastbyte"))
+			calc->linkport.assistlastbyte = value;
+
+		/* Keypad */
+		else if (!strcmp(buf, "keypad.group"))
+			calc->keypad.group = value;
+		else if (!strcmp(buf, "keypad.onkeyint"))
+			calc->keypad.onkeyint = value;
+
+		/* MD5 assist */
+		else if (!strcmp(buf, "md5assist.a"))
+			calc->md5assist.regs[0] = value;
+		else if (!strcmp(buf, "md5assist.b"))
+			calc->md5assist.regs[1] = value;
+		else if (!strcmp(buf, "md5assist.c"))
+			calc->md5assist.regs[2] = value;
+		else if (!strcmp(buf, "md5assist.d"))
+			calc->md5assist.regs[3] = value;
+		else if (!strcmp(buf, "md5assist.x"))
+			calc->md5assist.regs[4] = value;
+		else if (!strcmp(buf, "md5assist.t"))
+			calc->md5assist.regs[5] = value;
+		else if (!strcmp(buf, "md5assist.shift"))
+			calc->md5assist.shift = value;
+		else if (!strcmp(buf, "md5assist.mode"))
+			calc->md5assist.mode = value;
+
+		/* Programmable timers */
+		else if (!strcmp(buf, "usertimer0.frequency"))
+			calc->usertimers[0].frequency = value;
+		else if (!strcmp(buf, "usertimer0.loopvalue"))
+			calc->usertimers[0].loopvalue = value;
+		else if (!strcmp(buf, "usertimer0.status"))
+			calc->usertimers[0].status = value;
+		else if (!strcmp(buf, "usertimer1.frequency"))
+			calc->usertimers[1].frequency = value;
+		else if (!strcmp(buf, "usertimer1.loopvalue"))
+			calc->usertimers[1].loopvalue = value;
+		else if (!strcmp(buf, "usertimer1.status"))
+			calc->usertimers[1].status = value;
+		else if (!strcmp(buf, "usertimer2.frequency"))
+			calc->usertimers[2].frequency = value;
+		else if (!strcmp(buf, "usertimer2.loopvalue"))
+			calc->usertimers[2].loopvalue = value;
+		else if (!strcmp(buf, "usertimer2.status"))
+			calc->usertimers[2].status = value;
+
+		/* Main power */
+		else if (!strcmp(buf, "poweronhalt"))
+			calc->poweronhalt = value;
+
+		/* Battery */
+		else if (!strcmp(buf, "battery"))
+			calc->battery = value;
+
+		/* Memory */
+		else if (!strcmp(buf, "mempagemap0"))
+			calc->mempagemap[0] = value;
+		else if (!strcmp(buf, "mempagemap1"))
+			calc->mempagemap[1] = value;
+		else if (!strcmp(buf, "mempagemap2"))
+			calc->mempagemap[2] = value;
+		else if (!strcmp(buf, "mempagemap3"))
+			calc->mempagemap[3] = value;
+		else if (!strcmp(buf, "flash.unlock"))
+			calc->flash.unlock = value;
+		else if (!strcmp(buf, "flash.state"))
+			calc->flash.state = value;
+		else if (!strcmp(buf, "flash.busy"))
+			calc->flash.busy = value;
+		else if (!strcmp(buf, "flash.progaddr"))
+			calc->flash.progaddr = value;
+		else if (!strcmp(buf, "flash.progbyte"))
+			calc->flash.progbyte = value;
+		else if (!strcmp(buf, "flash.toggles"))
+			calc->flash.toggles = value;
+		else if (!strcmp(buf, "flash.overridegroup"))
+			calc->flash.overridegroup = value;
+
+		else
+			set_hw_reg(calc, buf, value);
+	}
+
+	tilem_free(buf);
+
+	return !ok;
+}
+
+int tilem_calc_load_state(TilemCalc* calc, FILE* romfile, FILE* savfile)
+{
+	int b;
+	int savtype = 0;
+
+	if (romfile) {
+		if (fread(calc->mem, 1, calc->hw.romsize, romfile)
+		    != calc->hw.romsize)
+			return 1;
+	}
+
+	tilem_calc_reset(calc);
+
+	if (savfile) {
+		/* first byte of old save files is always zero */
+		b = fgetc(savfile);
+		fseek(savfile, 0L, SEEK_SET);
+
+		if (b == 0) {
+			if (load_old_sav_file(calc, savfile)) {
+				tilem_calc_reset(calc);
+				return 1;
+			}
+			else
+				savtype = 1;
+		}
+		else {
+			if (load_new_sav_file(calc, savfile)) {
+				tilem_calc_reset(calc);
+				return 1;
+			}
+			else
+				savtype = 2;
+		}
+	}
+
+	if (calc->hw.stateloaded)
+		(*calc->hw.stateloaded)(calc, savtype);
+
+	return 0;
+}
+
+char tilem_get_sav_type(FILE* savfile)
+{
+	int b;
+	char *buf = NULL, *p, *q;
+	const TilemHardware **models;
+	int nmodels, i;
+	char id = 0;
+
+	tilem_get_supported_hardware(&models, &nmodels);
+
+	/* first byte of old save files is always zero */
+	b = fgetc(savfile);
+	fseek(savfile, 0L, SEEK_SET);
+	if (b == 0)
+		return 0; /* old files give no way to detect model */
+
+	while (read_sav_line(savfile, &buf)) {
+		if (parse_sav_definition(buf, &p)
+		    && !strcmp(buf, "MODEL")) {
+			q = p;
+			while (*q >= ' ')
+				q++;
+			*q = 0;
+
+			for (i = 0; i < nmodels; i++)
+				if (!strcmp(p, models[i]->name))
+					id = models[i]->model_id;
+
+			break;
+		}
+	}
+
+	fseek(savfile, 0L, SEEK_SET);
+	tilem_free(buf);
+	return id;
+}
+
+int tilem_calc_save_state(TilemCalc* calc, FILE* romfile, FILE* savfile)
+{
+	dword i;
+	dword t;
+	int j;
+	const char* tname;
+	unsigned int rowstride;
+
+	if (romfile) {
+		if (fwrite(calc->mem, 1, calc->hw.romsize, romfile)
+		    != calc->hw.romsize)
+			return 1;
+	}
+
+	if (savfile) {
+		fprintf(savfile, "# Tilem II State File\n# Version: %s\n",
+			PACKAGE_VERSION);
+		fprintf(savfile, "MODEL = %s\n", calc->hw.name);
+
+		fprintf(savfile, "\n## CPU ##\n");
+		fprintf(savfile, "af = %04X\n", calc->z80.r.af.w.l);
+		fprintf(savfile, "bc = %04X\n", calc->z80.r.bc.w.l);
+		fprintf(savfile, "de = %04X\n", calc->z80.r.de.w.l);
+		fprintf(savfile, "hl = %04X\n", calc->z80.r.hl.w.l);
+		fprintf(savfile, "af' = %04X\n", calc->z80.r.af2.w.l);
+		fprintf(savfile, "bc' = %04X\n", calc->z80.r.bc2.w.l);
+		fprintf(savfile, "de' = %04X\n", calc->z80.r.de2.w.l);
+		fprintf(savfile, "hl' = %04X\n", calc->z80.r.hl2.w.l);
+		fprintf(savfile, "ix = %04X\n", calc->z80.r.ix.w.l);
+		fprintf(savfile, "iy = %04X\n", calc->z80.r.iy.w.l);
+		fprintf(savfile, "pc = %04X\n", calc->z80.r.pc.w.l);
+		fprintf(savfile, "sp = %04X\n", calc->z80.r.sp.w.l);
+		fprintf(savfile, "ir = %04X\n",
+			((calc->z80.r.ir.w.l & ~0x80) | calc->z80.r.r7));
+		fprintf(savfile, "wz = %04X\n", calc->z80.r.wz.w.l);
+		fprintf(savfile, "wz' = %04X\n", calc->z80.r.wz2.w.l);
+		fprintf(savfile, "iff1 = %X\n", calc->z80.r.iff1);
+		fprintf(savfile, "iff2 = %X\n", calc->z80.r.iff2);
+		fprintf(savfile, "im = %X\n", calc->z80.r.im);
+		fprintf(savfile, "interrupts = %08X\n", calc->z80.interrupts);
+		fprintf(savfile, "clockspeed = %X\n", calc->z80.clockspeed);
+		fprintf(savfile, "halted = %X\n", calc->z80.halted);
+
+		fprintf(savfile, "\n## LCD Driver ##\n");
+		fprintf(savfile, "lcd.active = %X\n",
+			calc->lcd.active);
+		fprintf(savfile, "lcd.contrast = %X\n",
+			calc->lcd.contrast);
+		fprintf(savfile, "lcd.rowstride = %X\n",
+			calc->lcd.rowstride);
+		if (calc->hw.flags & TILEM_CALC_HAS_T6A04) {
+			fprintf(savfile, "lcd.rowshift = %X\n",
+				calc->lcd.rowshift);
+			fprintf(savfile, "lcd.inc = %X\n",
+				calc->lcd.inc);
+			fprintf(savfile, "lcd.mode = %X\n",
+				calc->lcd.mode);
+			fprintf(savfile, "lcd.x = %02X\n",
+				calc->lcd.x);
+			fprintf(savfile, "lcd.y = %02X\n",
+				calc->lcd.y);
+			fprintf(savfile, "lcd.nextbyte = %02X\n",
+				calc->lcd.nextbyte);
+			fprintf(savfile, "lcd.busy = %X\n",
+				calc->lcd.busy);
+		}
+		fprintf(savfile, "lcd.addr = %X\n", calc->lcd.addr);
+
+		if (calc->hw.flags & TILEM_CALC_HAS_LINK) {
+			fprintf(savfile, "\n## Link Port ##\n");
+			fprintf(savfile, "linkport.lines = %X\n",
+				calc->linkport.lines);
+			fprintf(savfile, "linkport.mode = %08X\n",
+				calc->linkport.mode);
+		}
+		if (calc->hw.flags & TILEM_CALC_HAS_LINK_ASSIST) {
+			fprintf(savfile, "linkport.assistflags = %08X\n",
+				calc->linkport.assistflags);
+			fprintf(savfile, "linkport.assistin = %02X\n",
+				calc->linkport.assistin);
+			fprintf(savfile, "linkport.assistinbits = %X\n",
+				calc->linkport.assistinbits);
+			fprintf(savfile, "linkport.assistout = %02X\n",
+				calc->linkport.assistout);
+			fprintf(savfile, "linkport.assistoutbits = %X\n",
+				calc->linkport.assistoutbits);
+			fprintf(savfile, "linkport.assistlastbyte = %02X\n",
+				calc->linkport.assistlastbyte);
+		}
+
+		fprintf(savfile, "\n## Keypad ##\n");
+		fprintf(savfile, "keypad.group = %X\n", calc->keypad.group);
+		fprintf(savfile, "keypad.onkeyint = %X\n",
+			calc->keypad.onkeyint);
+
+		fprintf(savfile, "\n## Memory mapping ##\n");
+		fprintf(savfile, "mempagemap0 = %X\n", calc->mempagemap[0]);
+		fprintf(savfile, "mempagemap1 = %X\n", calc->mempagemap[1]);
+		fprintf(savfile, "mempagemap2 = %X\n", calc->mempagemap[2]);
+		fprintf(savfile, "mempagemap3 = %X\n", calc->mempagemap[3]);
+
+		fprintf(savfile, "\n## Power ##\n");
+		fprintf(savfile, "poweronhalt = %X\n", calc->poweronhalt);
+		fprintf(savfile, "battery = %X\n", calc->battery);
+
+		if (calc->hw.flags & TILEM_CALC_HAS_FLASH) {
+			fprintf(savfile, "\n## Flash ##\n");
+			fprintf(savfile, "flash.unlock = %X\n",
+				calc->flash.unlock);
+			fprintf(savfile, "flash.state = %X\n",
+				calc->flash.state);
+			fprintf(savfile, "flash.busy = %X\n",
+				calc->flash.busy);
+			fprintf(savfile, "flash.progaddr = %X\n",
+				calc->flash.progaddr);
+			fprintf(savfile, "flash.progbyte = %X\n",
+				calc->flash.progbyte);
+			fprintf(savfile, "flash.toggles = %X\n",
+				calc->flash.toggles);
+			fprintf(savfile, "flash.overridegroup = %X\n",
+				calc->flash.overridegroup);
+		}
+
+		if (calc->hw.flags & TILEM_CALC_HAS_MD5_ASSIST) {
+			fprintf(savfile, "\n## MD5 assist ##\n");
+			fprintf(savfile, "md5assist.a = %X\n",
+				calc->md5assist.regs[0]);
+			fprintf(savfile, "md5assist.b = %X\n",
+				calc->md5assist.regs[1]);
+			fprintf(savfile, "md5assist.c = %X\n",
+				calc->md5assist.regs[2]);
+			fprintf(savfile, "md5assist.d = %X\n",
+				calc->md5assist.regs[3]);
+			fprintf(savfile, "md5assist.x = %X\n",
+				calc->md5assist.regs[4]);
+			fprintf(savfile, "md5assist.t = %X\n",
+				calc->md5assist.regs[5]);
+			fprintf(savfile, "md5assist.shift = %X\n",
+				calc->md5assist.shift);
+			fprintf(savfile, "md5assist.mode = %X\n",
+				calc->md5assist.mode);
+		}
+
+		for (j = 0; j < calc->hw.nusertimers; j++) {
+			fprintf(savfile,
+				"\n## Programmable timer %d ##\n", j);
+			fprintf(savfile, "usertimer%d.frequency = %X\n",
+				j, calc->usertimers[j].frequency);
+			fprintf(savfile, "usertimer%d.loopvalue = %X\n",
+				j, calc->usertimers[j].loopvalue);
+			fprintf(savfile, "usertimer%d.status = %X\n",
+				j, calc->usertimers[j].status);
+		}
+
+		fprintf(savfile, "\n## Model-specific ##\n");
+		for (j = 0; j < calc->hw.nhwregs; j++) {
+			fprintf(savfile, "%s = %X\n", calc->hw.hwregnames[j],
+				calc->hwregs[j]);
+		}
+
+		fprintf(savfile, "\n## Timers ##\n");
+		for (j = calc->z80.timer_cpu; j;
+		     j = calc->z80.timers[j].next) {
+			tname = get_timer_name(calc, j);
+			if (tname) {
+				t = tilem_z80_get_timer_clocks(calc, j);
+				fprintf(savfile, "timer:%s = %X, %X, 0\n",
+					tname, t, calc->z80.timers[j].period);
+			}
+		}
+		for (j = calc->z80.timer_rt; j;
+		     j = calc->z80.timers[j].next) {
+			tname = get_timer_name(calc, j);
+			if (tname) {
+				t = tilem_z80_get_timer_microseconds(calc, j);
+				fprintf(savfile, "timer:%s = %X, %X, 1\n",
+					tname, t, calc->z80.timers[j].period);
+			}
+		}
+
+		fprintf(savfile, "\n## RAM contents ##\n");
+		fprintf(savfile, "RAM = {\n");
+		for (i = 0; i < calc->hw.ramsize; i++) {
+			if (i % 256 == 0) {
+				fprintf(savfile, "# %02X:%04X\n",
+					(i >> 14), (i & 0x3fff));
+			}
+
+			fprintf(savfile, "%02X",
+				calc->mem[i + calc->hw.romsize]);
+			if (i % 32 == 31)
+				fprintf(savfile, "\n");
+		}
+		fprintf(savfile, "}\n## End of RAM contents ##\n");
+
+		if (calc->hw.lcdmemsize) {
+			fprintf(savfile, "\n## LCD contents ##\n");
+			fprintf(savfile, "LCD = {\n");
+			rowstride = calc->lcd.rowstride;
+			if (rowstride == 0)
+				rowstride = 32;
+
+			for (i = 0; i < calc->hw.lcdmemsize; i++) {
+				fprintf(savfile, "%02X", calc->lcdmem[i]);
+				if (i % rowstride == (rowstride - 1))
+					fprintf(savfile, "\n");
+			}
+			fprintf(savfile, "}\n## End of LCD contents ##\n");
+		}
+	}
+
+	return 0;
+}
diff --git a/tool/tilem-src/emu/tilem.h b/tool/tilem-src/emu/tilem.h
new file mode 100644
index 0000000..01878ad
--- /dev/null
+++ b/tool/tilem-src/emu/tilem.h
@@ -0,0 +1,950 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_H
+#define _TILEM_H
+
+#include "tilemint.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Basic integer types */
+typedef uint8_t byte;
+typedef uint16_t word;
+typedef uint32_t dword;
+typedef uint64_t qword;
+
+/* Structure types */
+typedef struct _TilemHardware TilemHardware;
+typedef struct _TilemCalc TilemCalc;
+
+/* Useful macros */
+#if __GNUC__ >= 3
+# define TILEM_ATTR_PURE __attribute__((__pure__))
+# define TILEM_ATTR_UNUSED __attribute__((__unused__))
+# define TILEM_ATTR_MALLOC __attribute__((__malloc__))
+# define TILEM_ATTR_PRINTF(x,y) __attribute__((__format__(__printf__,x,y)))
+# define TILEM_LIKELY(xxx) (__builtin_expect((xxx), 1))
+# define TILEM_UNLIKELY(xxx) (__builtin_expect((xxx), 0))
+#else
+# define TILEM_ATTR_PURE
+# define TILEM_ATTR_UNUSED
+# define TILEM_ATTR_MALLOC
+# define TILEM_ATTR_PRINTF(x,y)
+# define TILEM_LIKELY(xxx) (xxx)
+# define TILEM_UNLIKELY(xxx) (xxx)
+#endif
+
+#define TILEM_DWORD_TO_PTR(xxx) ((void*)(uintptr_t)(xxx))
+#define TILEM_PTR_TO_DWORD(xxx) ((dword)(uintptr_t)(xxx))
+
+/* Memory allocation */
+void* tilem_malloc(size_t size) TILEM_ATTR_MALLOC;
+void* tilem_malloc0(size_t size) TILEM_ATTR_MALLOC;
+void* tilem_malloc_atomic(size_t size) TILEM_ATTR_MALLOC;
+void* tilem_try_malloc(size_t size) TILEM_ATTR_MALLOC;
+void* tilem_try_malloc0(size_t size) TILEM_ATTR_MALLOC;
+void* tilem_try_malloc_atomic(size_t size) TILEM_ATTR_MALLOC;
+void* tilem_realloc(void* ptr, size_t size) TILEM_ATTR_MALLOC;
+void tilem_free(void* ptr);
+#define tilem_new(ttt, nnn) ((ttt*) tilem_malloc((nnn) * sizeof(ttt)));
+#define tilem_new0(ttt, nnn) ((ttt*) tilem_malloc0((nnn) * sizeof(ttt)));
+#define tilem_new_atomic(ttt, nnn) ((ttt*) tilem_malloc_atomic((nnn) * sizeof(ttt)));
+
+#define tilem_try_new(ttt, nnn) ((ttt*) tilem_try_malloc((nnn) * sizeof(ttt)));
+#define tilem_try_new0(ttt, nnn) ((ttt*) tilem_try_malloc0((nnn) * sizeof(ttt)));
+#define tilem_try_new_atomic(ttt, nnn) ((ttt*) tilem_try_malloc_atomic((nnn) * sizeof(ttt)));
+#define tilem_renew(ttt, ppp, nnn) ((ttt*) tilem_realloc((ppp), (nnn) * sizeof(ttt)))
+
+/* Message/error logging */
+
+/* Write an informative message.  This can be used to notify the user
+   of major occurences, such as changes in the Flash protection.
+   These messages can occur regularly in normal operation and are
+   provided chiefly to aid in debugging. */
+void tilem_message(TilemCalc* calc, const char* msg, ...)
+	TILEM_ATTR_PRINTF(2, 3);
+
+/* Write a warning message.  These messages occur when the calculator
+   software (either the OS or a user program) performs an invalid
+   operation; these messages often indicate a bug in the calculator
+   software, but are otherwise harmless. */
+void tilem_warning(TilemCalc* calc, const char* msg, ...)
+	TILEM_ATTR_PRINTF(2, 3);
+
+/* Write a warning about an internal error.  These messages should
+   never occur and indicate a bug in TilEm. */
+void tilem_internal(TilemCalc* calc, const char* msg, ...)
+	TILEM_ATTR_PRINTF(2, 3);
+
+
+/* Z80 CPU */
+
+/* This union allows us to manipulate register pairs as either byte or
+   word values.  It may need to be modified for really unusual host
+   CPUs. */
+typedef union _TilemZ80Reg {
+#ifdef WORDS_BIGENDIAN
+	struct {
+		byte h3, h2, h, l;
+	} b;
+
+	struct {
+		word h, l;
+	} w;
+
+	dword d;
+#else
+	struct {
+		byte l, h, h2, h3;
+	} b;
+
+	struct {
+		word l, h;
+	} w;
+
+	dword d;
+#endif
+} TilemZ80Reg;
+
+typedef struct _TilemZ80Regs {
+	TilemZ80Reg af, bc, de, hl;
+	TilemZ80Reg ix, iy, pc, sp;
+	TilemZ80Reg ir, wz, wz2;
+	TilemZ80Reg af2, bc2, de2, hl2;
+	int iff1, iff2, im;
+	byte r7;
+} TilemZ80Regs;
+
+/* Breakpoint types */
+enum {
+	TILEM_BREAK_MEM_READ = 1, /* Break after reading from memory */
+	TILEM_BREAK_MEM_EXEC,	  /* Break prior to executing from memory */
+	TILEM_BREAK_MEM_WRITE,	  /* Break after writing to memory */
+	TILEM_BREAK_PORT_READ,	  /* Break after reading from port */
+	TILEM_BREAK_PORT_WRITE,	  /* Break after writing to port */
+	TILEM_BREAK_EXECUTE,	  /* Break after executing opcode */
+
+	TILEM_BREAK_TYPE_MASK = 0xffff,
+
+	TILEM_BREAK_PHYSICAL = 0x10000, /* Use physical addresses */
+	TILEM_BREAK_DISABLED = 0x20000  /* Disabled breakpoint */
+};
+
+/* Emulation flags */
+enum {
+	TILEM_Z80_BREAK_INVALID = 1,      /* Break on invalid
+					     instructions */
+	TILEM_Z80_BREAK_UNDOCUMENTED = 2, /* Break on undocumented
+					     instructions */
+	TILEM_Z80_SKIP_UNDOCUMENTED  = 4, /* Ignore undocumented
+					     instructions entirely
+					     (act as two NOPs) */
+	TILEM_Z80_RESET_UNDOCUMENTED = 8, /* Reset CPU following
+	                                     undocumented instructions */
+	TILEM_Z80_BREAK_EXCEPTIONS = 16,  /* Break on hardware exceptions */
+	TILEM_Z80_IGNORE_EXCEPTIONS = 32  /* Ignore hardware exceptions */
+};
+
+/* Reasons for stopping emulation */
+enum {
+	TILEM_STOP_TIMEOUT = 0,            /* stopped due to timeout */
+	TILEM_STOP_BREAKPOINT = 1,         /* stopped due to breakpoint */
+	TILEM_STOP_INVALID_INST = 2,       /* invalid instruction */
+	TILEM_STOP_UNDOCUMENTED_INST = 4,  /* undocumented instruction */
+	TILEM_STOP_EXCEPTION = 8,	   /* hardware exception */
+	TILEM_STOP_LINK_STATE = 16,        /* blacklink state change */
+	TILEM_STOP_LINK_READ_BYTE = 32,    /* graylink finished reading byte */
+	TILEM_STOP_LINK_WRITE_BYTE = 64,   /* graylink finished writing byte */
+	TILEM_STOP_LINK_ERROR = 128        /* graylink encountered error */
+};
+
+/* Types of interrupt */
+enum {
+	TILEM_INTERRUPT_ON_KEY = 1, /* ON key pressed */
+	TILEM_INTERRUPT_TIMER1 = 2, /* Main interrupt timer */
+	TILEM_INTERRUPT_TIMER2 = 4, /* Alt. interrupt timer (83/83+) */
+	TILEM_INTERRUPT_USER_TIMER1 = 8, /* Programmable timers (83+SE) */
+	TILEM_INTERRUPT_USER_TIMER2 = 16,
+	TILEM_INTERRUPT_USER_TIMER3 = 32,
+	TILEM_INTERRUPT_LINK_ACTIVE = 512, /* Link port state changed */
+	TILEM_INTERRUPT_LINK_READ = 1024,  /* Link assist read a byte */
+	TILEM_INTERRUPT_LINK_IDLE = 2048,  /* Link assist is idle */
+	TILEM_INTERRUPT_LINK_ERROR = 4096  /* Link assist failed */
+};
+
+/* Types of hardware exception */
+enum {
+	TILEM_EXC_RAM_EXEC = 1,    /* Executing at invalid RAM address */
+	TILEM_EXC_FLASH_EXEC = 2,  /* Executing at invalid Flash address */
+	TILEM_EXC_FLASH_WRITE = 4, /* Writing to invalid Flash address */
+	TILEM_EXC_INSTRUCTION = 8  /* Invalid instruction */
+};
+
+/* Constant hardware timer IDs */
+enum {
+	TILEM_TIMER_NONE = 0,
+	TILEM_TIMER_LCD_DELAY,
+	TILEM_TIMER_FLASH_DELAY,
+	TILEM_TIMER_LINK_ASSIST,
+	TILEM_TIMER_USER1,
+	TILEM_TIMER_USER2,
+	TILEM_TIMER_USER3,
+	TILEM_TIMER_HW
+};
+
+#define TILEM_NUM_SYS_TIMERS (TILEM_TIMER_HW - 1)
+
+/* Type of a timer callback function.  Second arg is the callback data
+   passed to tilem_z80_add_timer(). */
+typedef void (*TilemZ80TimerFunc)(TilemCalc*, void*);
+
+/* Type of a breakpoint test function.  Second arg is the memory
+   address (or opcode in the case of TILEM_BREAK_EXECUTE breakpoints.)
+   Third arg is the callback data passed to
+   tilem_z80_add_breakpoint(). */
+typedef int (*TilemZ80BreakpointFunc)(TilemCalc*, dword, void*);
+
+typedef struct _TilemZ80Timer TilemZ80Timer;
+typedef struct _TilemZ80Breakpoint TilemZ80Breakpoint;
+
+typedef struct _TilemZ80 {
+	TilemZ80Regs r;
+	unsigned int interrupts; /* Currently active interrupts */
+	int clockspeed;		/* Current CPU speed (kHz) */
+	int halted;
+	unsigned int exception;
+	dword clock;
+	dword lastwrite;
+	dword lastlcdwrite;
+
+	unsigned int emuflags;
+
+	int ntimers;
+	TilemZ80Timer* timers;
+	int timer_cpu;	       /* Sorted list of timers (CPU-based) */
+	int timer_rt;	       /* Sorted list of timers (realtime) */
+	int timer_free;	       /* List of free timer structs */
+
+	int nbreakpoints;
+	TilemZ80Breakpoint* breakpoints;
+	int breakpoint_mr;	/* Memory read breakpoints */
+	int breakpoint_mx;	/* Memory exec breakpoints */
+	int breakpoint_mw;	/* Memory write breakpoints */
+	int breakpoint_pr;	/* Port read breakpoints */
+	int breakpoint_pw;	/* Port write breakpoints */
+	int breakpoint_op;	/* Opcode breakpoints */
+	int breakpoint_mpr;	/* Physical mem read breakpoints */
+	int breakpoint_mpx;	/* Physical mem exec breakpoints */
+	int breakpoint_mpw;	/* Physical mem write breakpoints */
+	int breakpoint_disabled; /* Disabled breakpoints */
+	int breakpoint_free;	/* List of free bp structs */
+
+	int stopping;
+	dword stop_reason;
+	dword stop_mask;
+	int stop_breakpoint;
+} TilemZ80;
+
+/* Reset CPU */
+void tilem_z80_reset(TilemCalc* calc);
+
+/* Halt simulation */
+void tilem_z80_stop(TilemCalc* calc, dword reason);
+
+/* Set CPU speed (kHz) */
+void tilem_z80_set_speed(TilemCalc* calc, int speed);
+
+/* Raise a hardware exception */
+void tilem_z80_exception(TilemCalc* calc, unsigned type);
+
+/* Add a timer with the given callback function and data.  The
+   callback function will be called after 'count' time units, and
+   every 'period' time units thereafter.  If rt = 0, the time units
+   are CPU clock cycles; if rt = 1, time units are microseconds.
+
+   Note that if a timer is set in response to a memory read or write,
+   the length of the delay may be off by as much as 19 clock cycles;
+   the precise timings for these are not (yet) properly emulated.
+   Timers set in response to port I/O events will be accurate to the
+   nearest CPU clock cycle.
+
+   The timer is considered to have "fired" as soon as the specified
+   amount of time has elapsed.  The callback function, however, may
+   not be called until after the instruction finishes.  If multiple
+   timers fire during the same instruction, the order in which the
+   callback functions will be called is undefined.
+
+   If you create a timer using this function (either repeating or
+   non-repeating), you must call tilem_z80_remove_timer() when the
+   timer is no longer needed.
+*/
+int tilem_z80_add_timer(TilemCalc* calc, dword count, dword period,
+			int rt, TilemZ80TimerFunc func, void* data);
+
+/* Change settings for an existing timer.  Arguments are the same as
+   above.  If count = 0, the timer is disabled. */
+void tilem_z80_set_timer(TilemCalc* calc, int id, dword count,
+			 dword period, int rt);
+
+/* Change period for an existing timer without affecting the current
+   interval. */
+void tilem_z80_set_timer_period(TilemCalc* calc, int id, dword period);
+
+/* Delete a timer. */
+void tilem_z80_remove_timer(TilemCalc* calc, int id);
+
+/* Check whether a timer is currently running. */
+int tilem_z80_timer_running(TilemCalc* calc, int id)
+	TILEM_ATTR_PURE;
+
+/* Get the number of clock ticks from now until the next time the
+   given timer fires.  (This may be negative, if the timer has already
+   fired during this instruction.)  NOTE: If the timer is disabled,
+   the return value is undefined. */
+int tilem_z80_get_timer_clocks(TilemCalc* calc, int id)
+	TILEM_ATTR_PURE;
+
+/* Get the number of microseconds from now until the next time the
+   given timer fires. */
+int tilem_z80_get_timer_microseconds(TilemCalc* calc, int id)
+	TILEM_ATTR_PURE;
+
+/* Add a breakpoint.  The breakpoint will be triggered if the address,
+   ANDed with the given mask, falls between the given start and end
+   inclusive.  If a callback function is specified it acts as an
+   additional filter, to determine whether the simulation should be
+   halted. */
+int tilem_z80_add_breakpoint(TilemCalc* calc, int type,
+			     dword start, dword end, dword mask,
+			     TilemZ80BreakpointFunc func, void* data);
+
+/* Remove the given breakpoint. */
+void tilem_z80_remove_breakpoint(TilemCalc* calc, int id);
+
+/* Enable the given breakpoint. */
+void tilem_z80_enable_breakpoint(TilemCalc* calc, int id);
+
+/* Disable the given breakpoint. */
+void tilem_z80_disable_breakpoint(TilemCalc* calc, int id);
+
+/* Check whether the given breakpoint is currently enabled. */
+int tilem_z80_breakpoint_enabled(TilemCalc* calc, int id);
+
+/* Get the type of the given breakpoint. */
+int tilem_z80_get_breakpoint_type(TilemCalc* calc, int id);
+
+/* Get the start address of the given breakpoint. */
+dword tilem_z80_get_breakpoint_address_start(TilemCalc* calc, int id);
+
+/* Get the start address of the given breakpoint. */
+dword tilem_z80_get_breakpoint_address_end(TilemCalc* calc, int id);
+
+/* Get the start address of the given breakpoint. */
+dword tilem_z80_get_breakpoint_address_mask(TilemCalc* calc, int id);
+
+/* Get the callback/filter function associated to the given breakpoint. */
+TilemZ80BreakpointFunc tilem_z80_get_breakpoint_callback(TilemCalc* calc,
+							 int id);
+
+/* Get the data associated to the given breakpoint. */
+void* tilem_z80_get_breakpoint_data(TilemCalc* calc, int id);
+
+/* Set the type of the given breakpoint. */
+void tilem_z80_set_breakpoint_type(TilemCalc* calc, int id, int type);
+
+/* Set the start address of the given breakpoint. */
+void tilem_z80_set_breakpoint_address_start(TilemCalc* calc, int id,
+					    dword start);
+
+/* Set the start address of the given breakpoint. */
+void tilem_z80_set_breakpoint_address_end(TilemCalc* calc, int id, dword end);
+
+/* Set the start address of the given breakpoint. */
+void tilem_z80_set_breakpoint_address_mask(TilemCalc* calc, int id, dword mask);
+
+/* Set the callback/filter function associated to the given breakpoint. */
+void tilem_z80_set_breakpoint_callback(TilemCalc* calc, int id,
+				       TilemZ80BreakpointFunc func);
+
+/* Set the data associated to the given breakpoint. */
+void tilem_z80_set_breakpoint_data(TilemCalc* calc, int id, void* data);
+
+
+/* Run the simulated CPU for the given number of clock
+   ticks/microseconds, or until a breakpoint is hit or
+   tilem_z80_stop() is called. */
+dword tilem_z80_run(TilemCalc* calc, int clocks, int* remaining);
+dword tilem_z80_run_time(TilemCalc* calc, int microseconds, int* remaining);
+
+
+/* LCD driver */
+
+/* Emulation flags */
+enum {
+	TILEM_LCD_REQUIRE_DELAY = 1,	  /* Emulate required delay
+					     between commands */
+	TILEM_LCD_REQUIRE_LONG_DELAY = 2  /* Require extra-long delay */
+};
+
+typedef struct _TilemLCD {
+	/* Common settings */
+	byte active;		/* LCD driver active */
+	byte contrast;		/* Contrast value (0-63) */
+	int rowstride;		/* Number of bytes per row */
+	unsigned int emuflags;
+
+	/* T6A43 internal driver */
+	word addr;		/* Memory address */
+
+	/* T6A04 external driver */
+	byte mode;		/* I/O mode (0 = 6bit, 1 = 8bit) */
+	byte inc;		/* Increment mode (4-7) */
+	byte nextbyte;		/* Output register */
+	int x, y;		/* Current position */
+	int rowshift;		/* Starting row for display */
+	byte busy;
+} TilemLCD;
+
+/* Reset LCD driver */
+void tilem_lcd_reset(TilemCalc* calc);
+
+/* Get LCD driver status (port 10 input) */
+byte tilem_lcd_t6a04_status(TilemCalc* calc);
+
+/* Send command to LCD driver (port 10 output) */
+void tilem_lcd_t6a04_control(TilemCalc* calc, byte val);
+
+/* Read data from LCD driver (port 11 input) */
+byte tilem_lcd_t6a04_read(TilemCalc* calc);
+
+/* Write data to LCD driver (port 11 output) */
+void tilem_lcd_t6a04_write(TilemCalc* calc, byte val);
+
+/* Get screen image (T6A04 style) */
+void tilem_lcd_t6a04_get_data(TilemCalc* calc, byte* data);
+
+/* Get screen image (T6A43 style) */
+void tilem_lcd_t6a43_get_data(TilemCalc* calc, byte* data);
+
+/* Callback for TILEM_TIMER_LCD_DELAY */
+void tilem_lcd_delay_timer(TilemCalc* calc, void* data);
+
+
+
+/* DBUS link port driver */
+
+/* Link port / assist mode flags */
+enum {
+	TILEM_LINK_MODE_ASSIST        = 1,  /* Enable link assist */
+	TILEM_LINK_MODE_NO_TIMEOUT    = 2,  /* Assist doesn't time out (xp) */
+	TILEM_LINK_MODE_INT_ON_ACTIVE = 4,  /* Interrupt on state change */
+	TILEM_LINK_MODE_INT_ON_READ   = 8,  /* Interrupt on asst. read */
+	TILEM_LINK_MODE_INT_ON_IDLE   = 16, /* Interrupt when asst. idle */
+	TILEM_LINK_MODE_INT_ON_ERROR  = 32  /* Interrupt on asst. error */
+};
+
+/* Link port state flags */
+enum {
+	TILEM_LINK_ASSIST_READ_BYTE   = 1, /* Assisted read finished */
+	TILEM_LINK_ASSIST_READ_BUSY   = 2, /* Assisted read in progress */
+	TILEM_LINK_ASSIST_READ_ERROR  = 4, /* Assisted read failed */
+	TILEM_LINK_ASSIST_WRITE_BUSY  = 8, /* Assisted write in progress */
+	TILEM_LINK_ASSIST_WRITE_ERROR = 16 /* Assisted write failed */
+};
+
+/* Link emulation mode */
+enum {
+	TILEM_LINK_EMULATOR_NONE = 0,  /* Link port disconnected */
+	TILEM_LINK_EMULATOR_BLACK = 1, /* Connected to virtual BlackLink
+					  (exit emulation on state change) */
+	TILEM_LINK_EMULATOR_GRAY = 2   /* Connected to virtual GrayLink
+					  (auto send/receive bytes) */
+};
+
+typedef struct _TilemLinkport {
+	byte lines, extlines;	/* Link line state for TI/PC
+				   0 = both lines high
+				   1 = red wire low
+				   2 = white wire low
+				   3 = both wires low */
+
+	unsigned int mode;	/* Mode flags */
+
+	/* Internal link assist */
+	unsigned int assistflags; /* Assist state */
+	byte assistin;		  /* Input buffer (recv from PC) */
+	byte assistinbits;	  /* Input bit count */
+	byte assistout;		  /* Output buffer (send to PC) */
+	byte assistoutbits;	  /* Output bit count */
+	byte assistlastbyte;	  /* Last byte received */
+
+	/* External link emulator */
+	byte linkemu;
+	byte graylinkin;	/* Input buffer (recv from TI) */
+	byte graylinkinbits;	/* Input bit count */
+	byte graylinkout;	/* Output buffer (send to TI) */
+	byte graylinkoutbits;	/* Output bit count */
+} TilemLinkport;
+
+/* Reset link port */
+void tilem_linkport_reset(TilemCalc* calc);
+
+/* Read link port lines */
+byte tilem_linkport_get_lines(TilemCalc* calc);
+
+/* Set link port lines */
+void tilem_linkport_set_lines(TilemCalc* calc, byte lines);
+
+/* Read from, and clear, link assist input buffer */
+byte tilem_linkport_read_byte(TilemCalc* calc);
+
+/* Write to link assist output buffer */
+void tilem_linkport_write_byte(TilemCalc* calc, byte data);
+
+/* Get assist state */
+unsigned int tilem_linkport_get_assist_flags(TilemCalc* calc);
+
+/* Set link port mode */
+void tilem_linkport_set_mode(TilemCalc* calc, unsigned int mode);
+
+/* Set line states for virtual BlackLink */
+void tilem_linkport_blacklink_set_lines(TilemCalc* calc, byte lines);
+
+/* Get line states from virtual BlackLink */
+byte tilem_linkport_blacklink_get_lines(TilemCalc* calc);
+
+/* Reset GrayLink */
+void tilem_linkport_graylink_reset(TilemCalc* calc);
+
+/* Check if GrayLink is ready to send data */
+int tilem_linkport_graylink_ready(TilemCalc* calc);
+
+/* Send a byte via virtual GrayLink */
+int tilem_linkport_graylink_send_byte(TilemCalc* calc, byte value);
+
+/* Get byte received by virtual GrayLink (-1 = none available) */
+int tilem_linkport_graylink_get_byte(TilemCalc* calc);
+
+/* Callback for TILEM_TIMER_LINK_ASSIST */
+void tilem_linkport_assist_timer(TilemCalc* calc, void* data);
+
+
+/* Keypad */
+
+typedef struct _TilemKeypad {
+	byte group;
+	byte onkeydown;
+	byte onkeyint;
+	byte keysdown[8];
+} TilemKeypad;
+
+/* Reset keypad */
+void tilem_keypad_reset(TilemCalc* calc);
+
+/* Set current group (port 1 output) */
+void tilem_keypad_set_group(TilemCalc* calc, byte group);
+
+/* Read keys from current group (port 1 input) */
+byte tilem_keypad_read_keys(TilemCalc* calc);
+
+/* Press a key */
+void tilem_keypad_press_key(TilemCalc* calc, int scancode);
+
+/* Release a key */
+void tilem_keypad_release_key(TilemCalc* calc, int scancode);
+
+
+/* Flash */
+
+/* Emulation flags */
+enum {
+	TILEM_FLASH_REQUIRE_DELAY = 1 /* Require delay after
+					 program/erase */
+};
+
+typedef struct _TilemFlashSector {
+	dword start;
+	dword size;
+	byte protectgroup;
+} TilemFlashSector;
+
+typedef struct _TilemFlash {
+	byte unlock;
+	byte state;
+	unsigned int emuflags;
+	byte busy;
+	dword progaddr;
+	byte progbyte;
+	byte toggles;
+	byte overridegroup;
+} TilemFlash;
+
+/* Reset Flash */
+void tilem_flash_reset(TilemCalc* calc);
+
+/* Read a byte from the Flash chip */
+byte tilem_flash_read_byte(TilemCalc* calc, dword pa);
+
+/* Erase a Flash sector */
+void tilem_flash_erase_address(TilemCalc* calc, dword pa);
+
+/* Write a byte to the Flash chip */
+void tilem_flash_write_byte(TilemCalc* calc, dword pa, byte v);
+
+/* Callback for TILEM_TIMER_FLASH_DELAY */
+void tilem_flash_delay_timer(TilemCalc* calc, void* data);
+
+
+/* MD5 assist */
+
+enum {
+	TILEM_MD5_REG_A = 0,	/* initial 'a' value */
+	TILEM_MD5_REG_B = 1,	/* 'b' value */
+	TILEM_MD5_REG_C = 2,	/* 'c' value */
+	TILEM_MD5_REG_D = 3,	/* 'd' value */
+	TILEM_MD5_REG_X = 4,	/* 'X' (or 'T') value */
+	TILEM_MD5_REG_T = 5	/* 'T' (or 'X') value */
+};
+
+enum {
+	TILEM_MD5_FUNC_FF = 0,
+	TILEM_MD5_FUNC_GG = 1,
+	TILEM_MD5_FUNC_HH = 2,
+	TILEM_MD5_FUNC_II = 3
+};
+
+typedef struct _TilemMD5Assist {
+	dword regs[6];
+	byte shift;
+	byte mode;
+} TilemMD5Assist;
+
+/* Reset MD5 assist */
+void tilem_md5_assist_reset(TilemCalc* calc);
+
+/* Get output value */
+dword tilem_md5_assist_get_value(TilemCalc* calc);
+
+
+/* Programmable timers */
+
+#define TILEM_MAX_USER_TIMERS 3
+
+enum {
+	TILEM_USER_TIMER_LOOP        = 1,   /* loop when counter
+					       reaches 0 */
+	TILEM_USER_TIMER_INTERRUPT   = 2,   /* generate interrupt when
+					       finished */
+	TILEM_USER_TIMER_OVERFLOW    = 4,   /* timer has expired at
+					       least twice since last
+					       mode setting */
+	TILEM_USER_TIMER_FINISHED    = 256, /* timer has expired at
+					       least once since last
+					       mode setting (port 4
+					       status bit) */
+	TILEM_USER_TIMER_NO_HALT_INT = 512  /* suppress interrupt if
+					       CPU is halted */
+};
+
+typedef struct _TilemUserTimer {
+	byte frequency;
+	byte loopvalue;
+	unsigned int status;
+} TilemUserTimer;
+
+/* Reset timers */
+void tilem_user_timers_reset(TilemCalc* calc);
+
+/* Set frequency control register */
+void tilem_user_timer_set_frequency(TilemCalc* calc, int n, byte value);
+
+/* Set status flags */
+void tilem_user_timer_set_mode(TilemCalc* calc, int n, byte mode);
+
+/* Start timer */
+void tilem_user_timer_start(TilemCalc* calc, int n, byte value);
+
+/* Get timer value */
+byte tilem_user_timer_get_value(TilemCalc* calc, int n);
+
+/* Callback function */
+void tilem_user_timer_expired(TilemCalc* calc, void* data);
+
+
+/* Calculators */
+
+/* Model IDs */
+enum {
+	TILEM_CALC_TI73 = '7',	       /* TI-73 / TI-73 Explorer */
+	TILEM_CALC_TI76 = 'f',	       /* TI-76.fr */
+	TILEM_CALC_TI81 = '1',	       /* TI-81 */
+	TILEM_CALC_TI82 = '2',	       /* TI-82 */
+	TILEM_CALC_TI83 = '3',	       /* TI-83 / TI-82 STATS [.fr] */
+	TILEM_CALC_TI83P = 'p',	       /* TI-83 Plus */
+	TILEM_CALC_TI83P_SE = 's',     /* TI-83 Plus Silver Edition */
+	TILEM_CALC_TI84P = '4',	       /* TI-84 Plus */
+	TILEM_CALC_TI84P_SE = 'z',     /* TI-84 Plus Silver Edition */
+	TILEM_CALC_TI84P_NSPIRE = 'n', /* TI-Nspire 84 Plus emulator */
+	TILEM_CALC_TI85 = '5',	       /* TI-85 */
+	TILEM_CALC_TI86 = '6'	       /* TI-86 */
+};
+
+/* Calculator flags */
+enum {
+	TILEM_CALC_HAS_LINK        = 1,  /* Has link port */
+	TILEM_CALC_HAS_LINK_ASSIST = 2,  /* Has hardware link assist */
+	TILEM_CALC_HAS_USB         = 4,  /* Has USB controller */
+	TILEM_CALC_HAS_FLASH       = 8,  /* Has (writable) Flash */
+	TILEM_CALC_HAS_T6A04       = 16, /* Has separate LCD driver */
+	TILEM_CALC_HAS_MD5_ASSIST  = 32  /* Has hardware MD5 assist */
+};
+
+/* Calculator hardware description */
+struct _TilemHardware {
+	char model_id;	      /* Single character identifying model */
+	const char* name;     /* Short name (e.g. ti83p) */
+	const char* desc;     /* Full name (e.g. TI-83 Plus) */
+
+	unsigned int flags;
+
+	int lcdwidth, lcdheight; /* Size of LCD */
+	dword romsize, ramsize;	 /* Size of ROM and RAM */
+	dword lcdmemsize;	 /* Size of external LCD memory */
+	byte rampagemask;	 /* Bit mask used for RAM page */
+
+	int nflashsectors;
+	const TilemFlashSector* flashsectors;
+
+	int nusertimers;
+
+	int nhwregs;		 /* Number of hardware registers */
+	const char** hwregnames; /* Harware register names */
+
+	int nhwtimers;		 /* Number of hardware timers */
+	const char** hwtimernames; /* Hardware timer names*/
+
+	const char** keynames;
+
+	/* Reset calculator */
+	void	(*reset)	(TilemCalc*);
+
+	/* Reinitialize after loading state */
+	void    (*stateloaded)  (TilemCalc*, int);
+
+	/* Z80 ports and memory */
+	byte	(*z80_in)	(TilemCalc*, dword);
+	void	(*z80_out) 	(TilemCalc*, dword, byte);
+	void	(*z80_wrmem)	(TilemCalc*, dword, byte);
+	byte	(*z80_rdmem)	(TilemCalc*, dword);
+	byte	(*z80_rdmem_m1)	(TilemCalc*, dword);
+
+	/* Evaluate a non-standard instruction */
+	void    (*z80_instr)    (TilemCalc*, dword);
+
+	/* Persistent timer callback */
+	void    (*z80_ptimer)   (TilemCalc*, int);
+
+	/* Retrieve LCD contents */
+	void    (*get_lcd)      (TilemCalc*, byte*);
+
+	/* Convert physical <-> logical addresses */
+	dword	(*mem_ltop)	(TilemCalc*, dword);
+	dword	(*mem_ptol)	(TilemCalc*, dword);
+};
+
+/* Current state of the calculator */
+struct _TilemCalc {
+	TilemHardware hw;
+
+	TilemZ80 z80;
+	byte* mem;
+	byte* ram;
+	byte* lcdmem;
+	byte mempagemap[4];
+
+	TilemLCD lcd;
+	TilemLinkport linkport;
+	TilemKeypad keypad;
+	TilemFlash flash;
+	TilemMD5Assist md5assist;
+	TilemUserTimer usertimers[TILEM_MAX_USER_TIMERS];
+
+	byte poweronhalt;	/* System power control.  If this is
+				   zero, turn off LCD, timers,
+				   etc. when CPU halts */
+
+	byte battery;		/* Battery level (units of 0.1 V) */
+
+	dword* hwregs;
+};
+
+/* Get a list of supported hardware models */
+void tilem_get_supported_hardware(const TilemHardware*** models,
+				  int* nmodels);
+
+/* Create a new calculator.  This function returns NULL if
+   insufficient memory is available. */
+TilemCalc* tilem_calc_new(char id);
+
+/* Make an exact copy of an existing calculator (including both
+   internal and external state.)  Be careful when using this in
+   conjunction with custom timer/breakpoint callback functions.  This
+   function returns NULL if insufficient memory is available. */
+TilemCalc* tilem_calc_copy(TilemCalc* calc);
+
+/* Free a calculator that was previously created by tilem_calc_new()
+   or tilem_calc_copy(). */
+void tilem_calc_free(TilemCalc* calc);
+
+/* Reset calculator (essentially, remove and replace batteries.) */
+void tilem_calc_reset(TilemCalc* calc);
+
+/* Load calculator state from ROM and/or save files. */
+int tilem_calc_load_state(TilemCalc* calc, FILE* romfile, FILE* savfile);
+
+/* Save calculator state to ROM and/or save files. */
+int tilem_calc_save_state(TilemCalc* calc, FILE* romfile, FILE* savfile);
+
+
+/* LCD image conversion/scaling */
+
+/* Scaling algorithms */
+enum {
+	TILEM_SCALE_FAST = 0,	/* Fast scaling (nearest neighbor) -
+				   looks lousy unless the scaling
+				   factor is fairly large */
+	TILEM_SCALE_SMOOTH	/* Smooth scaling - slower and looks
+				   better at small sizes; note that
+				   this falls back to using the "fast"
+				   algorithm if we are scaling up by
+				   an integer factor */
+};
+
+/* Buffer representing a snapshot of the LCD state */
+typedef struct _TilemLCDBuffer {
+	byte width;             /* Width of LCD */
+	byte height;            /* Height of LCD */
+	byte rowstride;         /* Offset between rows in buffer */
+	byte contrast;          /* Contrast value (0-63) */
+	dword stamp;            /* Timestamp */
+	dword tmpbufsize;       /* Size of temporary buffer */
+	byte *data;             /* Image data (rowstride*height bytes) */
+	void *tmpbuf;           /* Temporary buffer used for scaling */
+} TilemLCDBuffer;
+
+/* Create new TilemLCDBuffer. */
+TilemLCDBuffer* tilem_lcd_buffer_new(void)
+	TILEM_ATTR_MALLOC;
+
+/* Free  a TilemLCDBuffer. */
+void tilem_lcd_buffer_free(TilemLCDBuffer *buf);
+
+/* Convert current LCD memory contents to a TilemLCDBuffer (i.e., a
+   monochrome snapshot.) */
+void tilem_lcd_get_frame(TilemCalc * restrict calc,
+                         TilemLCDBuffer * restrict buf);
+
+/* Convert current LCD memory contents to a TilemLCDBuffer (i.e., a
+   monochrome snapshot.) 
+   Output is only 0 and 1 */
+void tilem_lcd_get_frame1(TilemCalc * restrict calc,
+                         TilemLCDBuffer * restrict buf);
+
+/* Convert and scale image to an 8-bit indexed image buffer.  IMGWIDTH
+   and IMGHEIGHT are the width and height of the output image,
+   ROWSTRIDE the number of bytes from the start of one row to the next
+   (often equal to IMGWIDTH), and SCALETYPE the scaling algorithm to
+   use. */
+void tilem_draw_lcd_image_indexed(TilemLCDBuffer * restrict frm,
+                                  byte * restrict buffer,
+                                  int imgwidth, int imgheight,
+                                  int rowstride, int scaletype);
+
+/* Convert and scale image to a 24-bit RGB or 32-bit RGBA image
+   buffer.  IMGWIDTH and IMGHEIGHT are the width and height of the
+   output image, ROWSTRIDE the number of bytes from the start of one
+   row to the next (often equal to 3 * IMGWIDTH), PIXBYTES the number
+   of bytes per pixel (3 or 4), and SCALETYPE the scaling algorithm to
+   use.  PALETTE is an array of 256 color values. */
+void tilem_draw_lcd_image_rgb(TilemLCDBuffer * restrict frm,
+                              byte * restrict buffer,
+                              int imgwidth, int imgheight, int rowstride,
+                              int pixbytes, const dword * restrict palette,
+                              int scaletype);
+
+/* Calculate a color palette for use with the above functions.
+   RLIGHT, GLIGHT, BLIGHT are the RGB components (0 to 255) of the
+   lightest possible color; RDARK, GDARK, BDARK are the RGB components
+   of the darkest possible color.  GAMMA is the gamma value for the
+   output device (2.2 for most current computer displays and image
+   file formats.) */
+dword* tilem_color_palette_new(int rlight, int glight, int blight,
+                               int rdark, int gdark, int bdark,
+                               double gamma);
+
+/* Calculate a color palette, as above, and convert it to a packed
+   array of bytes (R, G, B) */
+byte* tilem_color_palette_new_packed(int rlight, int glight, int blight,
+                                     int rdark, int gdark, int bdark,
+                                     double gamma);
+
+
+/* Grayscale LCD simulation */
+
+typedef struct _TilemGrayLCD TilemGrayLCD;
+
+/* Create a new LCD and attach to a calculator.  Sampling for
+   grayscale is done across WINDOWSIZE frames, with samples taken
+   every SAMPLEINT microseconds. */
+TilemGrayLCD* tilem_gray_lcd_new(TilemCalc *calc, int windowsize,
+                                 int sampleint);
+
+/* Detach and free an LCD. */
+void tilem_gray_lcd_free(TilemGrayLCD *glcd);
+
+/* Generate a grayscale image for the next frame, based on current
+   calculator state.  This function also updates the frame counter and
+   internal state; for proper grayscale behavior, this function needs
+   to be called at regular intervals. */
+void tilem_gray_lcd_get_frame(TilemGrayLCD * restrict glcd,
+                              TilemLCDBuffer * restrict frm);
+
+
+/* Miscellaneous functions */
+
+/* Guess calculator type for a ROM file */
+char tilem_guess_rom_type(FILE* romfile);
+
+/* Get calculator type for a SAV file */
+char tilem_get_sav_type(FILE* savfile);
+
+/* Check validity of calculator certificate; repair if necessary */
+void tilem_calc_fix_certificate(TilemCalc* calc, byte* cert,
+                                int app_start, int app_end,
+                                unsigned exptab_offset);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tool/tilem-src/emu/tilemint.h b/tool/tilem-src/emu/tilemint.h
new file mode 100644
index 0000000..0e95956
--- /dev/null
+++ b/tool/tilem-src/emu/tilemint.h
@@ -0,0 +1,12 @@
+#ifndef _TILEMINT_H
+#define _TILEMINT_H
+
+#ifdef HAVE_STDINTS1_H
+# include <stdints1.h>
+#elif defined(HAVE_STDINTS2_H)
+# include <stdints2.h>
+#else
+# include <stdint.h>
+#endif
+
+#endif
diff --git a/tool/tilem-src/emu/timers.c b/tool/tilem-src/emu/timers.c
new file mode 100644
index 0000000..967aa73
--- /dev/null
+++ b/tool/tilem-src/emu/timers.c
@@ -0,0 +1,231 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include "tilem.h"
+
+void tilem_user_timers_reset(TilemCalc* calc)
+{
+	int i;
+
+	for (i = 0; i < TILEM_MAX_USER_TIMERS; i++) {
+		tilem_z80_set_timer(calc, TILEM_TIMER_USER1 + i, 0, 0, 0);
+		calc->usertimers[i].frequency = 0;
+		calc->usertimers[i].loopvalue = 0;
+		calc->usertimers[i].status = 0;
+	}
+}
+
+static inline dword get_duration(int fvalue, int nticks)
+{
+	qword t=0;
+
+	if (fvalue & 0x80) {
+		if (fvalue & 0x20)
+			return 64 * nticks;
+		else if (fvalue & 0x10)
+			return 32 * nticks;
+		else if (fvalue & 0x08)
+			return 16 * nticks;
+		else if (fvalue & 0x04)
+			return 8 * nticks;
+		else if (fvalue & 0x02)
+			return 4 * nticks;
+		else if (fvalue & 0x01)
+			return 2 * nticks;
+		else
+			return nticks;
+	}
+	else if (fvalue & 0x40) {
+		switch (fvalue & 7) {
+		case 0:
+			t = 3000000;
+			break;
+		case 1:
+			t = 33000000;
+			break;
+		case 2:
+			t = 328000000;
+			break;
+		case 3:
+			t = 0xC3530D40; //3277000000;
+			break;
+		case 4:
+			t = 1000000;
+			break;
+		case 5:
+			t = 16000000;
+			break;
+		case 6:
+			t = 256000000;
+			break;
+		case 7:
+			t = 0xF4240000; //4096000000;
+			break;
+		}
+
+		return ((t * nticks + 16384) / 32768);
+	}
+
+	return 0;
+}
+
+static dword get_normal_duration(const TilemUserTimer* tmr)
+{
+	if (tmr->loopvalue)
+		return get_duration(tmr->frequency, tmr->loopvalue);
+	else
+		return get_duration(tmr->frequency, 256);
+}
+
+static dword get_overflow_duration(const TilemUserTimer* tmr)
+{
+	return get_duration(tmr->frequency, 256);
+}
+
+void tilem_user_timer_set_frequency(TilemCalc* calc, int n, byte value)
+{
+	TilemUserTimer* tmr = &calc->usertimers[n];
+
+	/* writing to frequency control port stops timer; set
+	   loopvalue to the current value of the counter */
+	tmr->loopvalue = tilem_user_timer_get_value(calc, n);
+	tilem_z80_set_timer(calc, TILEM_TIMER_USER1 + n, 0, 0, 0);
+	tmr->frequency = value;
+}
+
+void tilem_user_timer_set_mode(TilemCalc* calc, int n, byte mode)
+{
+	TilemUserTimer* tmr = &calc->usertimers[n];
+
+	/* setting mode clears "finished" and "overflow" flags */
+	tmr->status = ((tmr->status & TILEM_USER_TIMER_NO_HALT_INT)
+		       | (mode & 3));
+
+	calc->z80.interrupts &= ~(TILEM_INTERRUPT_USER_TIMER1 << n);
+
+	/* if timer is currently running, it will not overflow the
+	   next time it expires -> set period to normal */
+	if ((mode & TILEM_USER_TIMER_LOOP) || tmr->loopvalue == 0) {
+		tilem_z80_set_timer_period(calc, TILEM_TIMER_USER1 + n,
+					   get_normal_duration(tmr));
+	}
+	else {
+		tilem_z80_set_timer_period(calc, TILEM_TIMER_USER1 + n, 0);
+	}
+}
+
+void tilem_user_timer_start(TilemCalc* calc, int n, byte value)
+{
+	TilemUserTimer* tmr = &calc->usertimers[n];
+	dword count, period;
+
+	tmr->loopvalue = value;
+
+	/* if a valid frequency is set, then writing to value port
+	   starts timer */
+
+	count = get_normal_duration(tmr);
+	if (!count)
+		return;
+
+	if (!value) {
+		/* input value 0 means loop indefinitely */
+		period = get_overflow_duration(tmr);
+	}
+	else if (tmr->status & TILEM_USER_TIMER_FINISHED) {
+		/* timer has already expired once -> it will overflow
+		   the next time it expires (note that this happens
+		   even if the loop flag isn't set) */
+		period = get_overflow_duration(tmr);
+	}
+	else if (!(tmr->status & TILEM_USER_TIMER_LOOP)) {
+		/* don't loop */
+		period = 0;
+	}
+	else {
+		/* timer hasn't expired yet; second iteration starts
+		   from the same counter value as the first */
+		period = count;
+	}
+
+	tilem_z80_set_timer(calc, TILEM_TIMER_USER1 + n, count, period,
+			    (calc->usertimers[n].frequency & 0x80 ? 0 : 1));
+}
+
+byte tilem_user_timer_get_value(TilemCalc* calc, int n)
+{
+	TilemUserTimer* tmr = &calc->usertimers[n];
+	dword period;
+
+	if (!tilem_z80_timer_running(calc, TILEM_TIMER_USER1 + n))
+		return tmr->loopvalue;
+
+	period = get_overflow_duration(tmr);
+
+	if (tmr->frequency & 0x80) {
+		dword t = tilem_z80_get_timer_clocks
+			(calc, TILEM_TIMER_USER1 + n);
+		return ((t * 256) / period) % 256;
+	}
+	else {
+		qword t = tilem_z80_get_timer_microseconds
+			(calc, TILEM_TIMER_USER1 + n);
+		return ((t * 256) / period) % 256;
+	}
+}
+
+void tilem_user_timer_expired(TilemCalc* calc, void* data)
+{
+	int n = TILEM_PTR_TO_DWORD(data);
+	TilemUserTimer* tmr = &calc->usertimers[n];
+
+	/* input value 0 means loop indefinitely (don't set flags) */
+	if (!tmr->loopvalue) {
+		return;
+	}
+
+	/* if timer has already finished, set "overflow" flag */
+	if (tmr->status & TILEM_USER_TIMER_FINISHED) {
+		tmr->status |= TILEM_USER_TIMER_OVERFLOW;
+	}
+
+	/* set "finished" flag */
+	tmr->status |= TILEM_USER_TIMER_FINISHED;
+
+	/* generate interrupt if appropriate */
+	if ((tmr->status & TILEM_USER_TIMER_INTERRUPT)
+	    && (!(tmr->status & TILEM_USER_TIMER_NO_HALT_INT)
+		|| !calc->z80.halted)) {
+		calc->z80.interrupts |= (TILEM_INTERRUPT_USER_TIMER1 << n);
+	}
+
+	if (tmr->status & TILEM_USER_TIMER_LOOP) {
+		/* timer will overflow the next time it expires
+		   (unless user writes to the mode port before
+		   then) */
+		tilem_z80_set_timer_period(calc, TILEM_TIMER_USER1 + n,
+					   get_overflow_duration(tmr));
+	}
+}
diff --git a/tool/tilem-src/emu/x1/x1.h b/tool/tilem-src/emu/x1/x1.h
new file mode 100644
index 0000000..eecb19e
--- /dev/null
+++ b/tool/tilem-src/emu/x1/x1.h
@@ -0,0 +1,53 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_X1_H
+#define _TILEM_X1_H
+
+enum {
+	HW_VERSION,		/* HW version: 0 = unknown
+				   1 = original HW (1990-1992)
+				   2 = revised HW (1992-1996) */
+	PORT2,			/* memory mapping control (new HW) */
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* mapping mode, timer control */
+	PORT5,			/* memory mapping bank A (old HW) */
+	PORT6,			/* memory mapping bank B (old HW) */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES { "hw_version", "port2", "port3", "port4", "port5", "port6" }
+
+#define TIMER_INT (TILEM_NUM_SYS_TIMERS + 1)
+#define NUM_HW_TIMERS 1
+
+#define HW_TIMER_NAMES { "int" }
+
+void x1_reset(TilemCalc* calc);
+byte x1_z80_in(TilemCalc* calc, dword port);
+void x1_z80_out(TilemCalc* calc, dword port, byte value);
+void x1_z80_ptimer(TilemCalc* calc, int id);
+void x1_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte x1_z80_rdmem(TilemCalc* calc, dword addr);
+dword x1_mem_ltop(TilemCalc* calc, dword addr);
+dword x1_mem_ptol(TilemCalc* calc, dword addr);
+void x1_get_lcd(TilemCalc* calc, byte* data);
+
+#endif
diff --git a/tool/tilem-src/emu/x1/x1_init.c b/tool/tilem-src/emu/x1/x1_init.c
new file mode 100644
index 0000000..962b4b1
--- /dev/null
+++ b/tool/tilem-src/emu/x1/x1_init.c
@@ -0,0 +1,50 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x1.h"
+
+void x1_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x08;
+	calc->hwregs[PORT2] = 0x00;
+	calc->hwregs[PORT5] = 0x00;
+	calc->hwregs[PORT6] = 0x00;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x01;
+	calc->mempagemap[2] = 0x00; 
+	calc->mempagemap[3] = 0x02;
+
+	if (calc->hwregs[HW_VERSION] != 2)
+		calc->lcd.rowstride = 12;
+
+	tilem_z80_set_speed(calc, 2000);
+
+	/* FIXME: measure actual frequency */
+	tilem_z80_set_timer(calc, TIMER_INT, 6000, 6000, 1);
+}
diff --git a/tool/tilem-src/emu/x1/x1_io.c b/tool/tilem-src/emu/x1/x1_io.c
new file mode 100644
index 0000000..b46977d
--- /dev/null
+++ b/tool/tilem-src/emu/x1/x1_io.c
@@ -0,0 +1,237 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x1.h"
+
+byte x1_z80_in(TilemCalc* calc, dword port)
+{
+	byte v;
+
+	if (calc->hwregs[HW_VERSION] == 1)
+		port &= 7;
+	
+	switch(port&0x1f) {
+	case 0x01:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+		return(calc->hwregs[PORT2]);
+
+	case 0x03:
+		v = (calc->keypad.onkeydown ? 0x00: 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+
+		return(v);
+
+	case 0x05:
+		return(calc->hwregs[PORT5]);
+
+	case 0x06:
+		return(calc->hwregs[PORT6]);
+
+	case 0x10:
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+		return(tilem_lcd_t6a04_read(calc));
+	}
+
+	tilem_warning(calc, "Input from port %x", port);
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	int pageA, pageB;
+
+	switch (calc->hwregs[HW_VERSION]) {
+	case 1:
+		if (calc->hwregs[PORT5] & 0x40)
+			pageA = 2;
+		else if (calc->hwregs[PORT5] & 1)
+			pageA = 1;
+		else
+			pageA = 0;
+
+		if (calc->hwregs[PORT6] & 0x40)
+			pageB = 2;
+		else if (calc->hwregs[PORT6] & 1)
+			pageB = 1;
+		else
+			pageB = 0;
+		break;
+
+	case 2:
+		if (calc->hwregs[PORT2] & 0x40)
+			pageA = 2;
+		else if (calc->hwregs[PORT2] & 1)
+			pageA = 1;
+		else
+			pageA = 0;
+
+		if (calc->hwregs[PORT2] & 0x80)
+			pageB = 2;
+		else if (calc->hwregs[PORT2] & 8)
+			pageB = 1;
+		else
+			pageB = 0;
+		break;
+
+	default:
+		/* unknown HW version - ignore all output values and
+		   use standard mapping */
+		pageA = 1;
+		pageB = 0;
+	}
+
+	calc->mempagemap[1] = pageA;
+	calc->mempagemap[2] = pageB;
+	calc->mempagemap[3] = 2;
+}
+
+void x1_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	if (!calc->hwregs[HW_VERSION] && calc->z80.r.pc.d < 0x8000) {
+		/* detect version */
+		if (port == 0x05 || port == 0x06) {
+			calc->hwregs[HW_VERSION] = 1;
+			setup_mapping(calc);
+		}
+		else if (port == 0x10 || port == 0x11) {
+			calc->lcd.rowstride = 15;
+			calc->hwregs[HW_VERSION] = 2;
+			setup_mapping(calc);
+		}
+	}
+
+	if (calc->hwregs[HW_VERSION] == 1)
+		port &= 7;
+
+	switch(port&0x1f) {
+	case 0x00:
+		calc->lcd.addr = ((value & 0x1f) << 8);
+		calc->z80.lastlcdwrite = calc->z80.clock;
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x02:
+		calc->hwregs[PORT2] = value;
+		if (calc->hwregs[HW_VERSION] != 2)
+			calc->lcd.contrast = 16 + (value & 0x1f);
+		setup_mapping(calc);
+		break;
+
+	case 0x03:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02)) {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+		}
+
+		calc->hwregs[PORT3] = value;
+		calc->lcd.active = calc->poweronhalt = ((value & 8) >> 3);
+		break;
+
+	case 0x04:
+		calc->hwregs[PORT4] = value;
+
+		if (calc->hwregs[HW_VERSION] == 1) {
+			switch (value & 0x18) {
+			case 0x00:
+				calc->lcd.rowstride = 10;
+				break;
+
+			case 0x08:
+				calc->lcd.rowstride = 12;
+				break;
+
+			case 0x10:
+				calc->lcd.rowstride = 16;
+				break;
+
+			case 0x18:
+				calc->lcd.rowstride = 20;
+				break;
+			}
+			calc->z80.lastlcdwrite = calc->z80.clock;
+		}
+		break;
+
+	case 0x05:
+		calc->hwregs[PORT5] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x06:
+		calc->hwregs[PORT6] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x10:
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+	}
+
+	return;
+}
+
+void x1_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+	}
+}
+
+void x1_get_lcd(TilemCalc* calc, byte* data)
+{
+	if (calc->hwregs[HW_VERSION] == 2)
+		tilem_lcd_t6a04_get_data(calc, data);
+	else
+		tilem_lcd_t6a43_get_data(calc, data);
+}
diff --git a/tool/tilem-src/emu/x1/x1_memory.c b/tool/tilem-src/emu/x1/x1_memory.c
new file mode 100644
index 0000000..34c860e
--- /dev/null
+++ b/tool/tilem-src/emu/x1/x1_memory.c
@@ -0,0 +1,71 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x1.h"
+
+void x1_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+
+	if (pa >= 0x8000) {
+		*(calc->mem + 0x8000 + (pa & 0x1fff)) = v;
+
+		if ((((pa - 0x8000 - calc->lcd.addr) >> 6)
+		     < (unsigned) calc->lcd.rowstride)
+		    && calc->hwregs[HW_VERSION] < 2)
+			calc->z80.lastlcdwrite = calc->z80.clock;
+	}
+}
+
+byte x1_z80_rdmem(TilemCalc* calc, dword A)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+
+	if (pa >= 0x8000)
+		return (*(calc->mem + 0x8000 + (pa & 0x1fff)));
+	else
+		return (*(calc->mem + (pa & 0x7fff)));
+}
+
+dword x1_mem_ltop(TilemCalc* calc, dword A)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+
+	if (pa >= 0x8000)
+		return (0x8000 + (pa & 0x1fff));
+	else
+		return (pa);
+}
+
+dword x1_mem_ptol(TilemCalc* calc TILEM_ATTR_UNUSED, dword A)
+{
+	if (A & 0x8000)
+		return (0xE000 + (A & 0x1fff));
+	else
+		return (A);
+}
diff --git a/tool/tilem-src/emu/x1/x1_subcore.c b/tool/tilem-src/emu/x1/x1_subcore.c
new file mode 100644
index 0000000..2f82856
--- /dev/null
+++ b/tool/tilem-src/emu/x1/x1_subcore.c
@@ -0,0 +1,56 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x1.h"
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+static const char* keynames[64] = {
+	"Down", "Left", "Right", "Up", 0, 0, 0, 0,
+	"Enter", "Add", "Sub", "Mul", "Div", "Power", "Clear", 0,
+	"Chs", "3", "6", "9", "RParen", "Tan", "Vars", 0,
+	"DecPnt", "2", "5", "8", "LParen", "Cos", "Prgm", "Mode",
+	"0", "1", "4", "7", "EE", "Sin", "Matrix", "Graphvar",
+	"On", "Store", "Ln", "Log", "Square", "Recip", "Math", "Alpha",
+	"Graph", "Trace", "Zoom", "Range", "YEqu", "2nd", "Ins", "Del",
+	0, 0, 0, 0, 0, 0, 0, 0};
+
+const TilemHardware hardware_ti81 = {
+	'1', "ti81", "TI-81", TILEM_CALC_HAS_T6A04,
+	96, 64,	0x8000, 0x2000, 15 * 64, 0x40,
+	0, NULL, 0,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	keynames,
+	x1_reset, NULL,
+	x1_z80_in, x1_z80_out,
+	x1_z80_wrmem, x1_z80_rdmem, x1_z80_rdmem, NULL,
+	x1_z80_ptimer, x1_get_lcd,
+	x1_mem_ltop, x1_mem_ptol };
diff --git a/tool/tilem-src/emu/x2/x2.h b/tool/tilem-src/emu/x2/x2.h
new file mode 100644
index 0000000..cfa6093
--- /dev/null
+++ b/tool/tilem-src/emu/x2/x2.h
@@ -0,0 +1,51 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_X2_H
+#define _TILEM_X2_H
+
+enum {
+	HW_VERSION,		/* HW version: 0 = unknown
+				   1 = original HW (1993-2000)
+				   2 = revised HW (2001-2003) */
+	PORT0,			/* port 0 (link port control) */
+	PORT2,			/* port 2 (memory mapping) */
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* mapping mode + timer speed */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES { "hw_version", "port0", "port2", "port3", "port4" }
+
+#define TIMER_INT (TILEM_NUM_SYS_TIMERS + 1)
+#define NUM_HW_TIMERS 1
+
+#define HW_TIMER_NAMES { "int" }
+
+void x2_reset(TilemCalc* calc);
+byte x2_z80_in(TilemCalc* calc, dword port);
+void x2_z80_out(TilemCalc* calc, dword port, byte value);
+void x2_z80_ptimer(TilemCalc* calc, int id);
+void x2_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte x2_z80_rdmem(TilemCalc* calc, dword addr);
+dword x2_mem_ltop(TilemCalc* calc, dword addr);
+dword x2_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/x2/x2_init.c b/tool/tilem-src/emu/x2/x2_init.c
new file mode 100644
index 0000000..1e83119
--- /dev/null
+++ b/tool/tilem-src/emu/x2/x2_init.c
@@ -0,0 +1,46 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x2.h"
+
+void x2_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT2] = 0xF8;
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x00;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x00;
+	calc->mempagemap[2] = 0x09; 
+	calc->mempagemap[3] = 0x08;
+
+	tilem_z80_set_speed(calc, 6000);
+
+	/* FIXME: measure actual frequency */
+	tilem_z80_set_timer(calc, TIMER_INT, 1000, 9259, 1);
+}
diff --git a/tool/tilem-src/emu/x2/x2_io.c b/tool/tilem-src/emu/x2/x2_io.c
new file mode 100644
index 0000000..f8e3121
--- /dev/null
+++ b/tool/tilem-src/emu/x2/x2_io.c
@@ -0,0 +1,207 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x2.h"
+
+byte x2_z80_in(TilemCalc* calc, dword port)
+{
+	static const byte battlevel[4] = { 33, 39, 36, 43 };
+	byte v, b;
+
+	switch(port&0xff) {
+	case 0x00:
+		v = tilem_linkport_get_lines(calc);
+
+		if (calc->hwregs[HW_VERSION] == 1) {
+			/* HW version 1 uses separate PCR/PDR, as the
+			   TI-85 does. */
+			b = (calc->hwregs[PORT0] >> 4) | 0xf0;
+			return ((calc->hwregs[PORT0] & b) | (v & ~b));
+		}
+		else {
+			/* HW version 2 uses a TI-83-like interface.
+			   (Do the same if version is unknown, because
+			   most code written for HW version 1 will
+			   work fine with these values.) */
+			return (0xc0 | (v * 5));
+		}
+
+	case 0x01:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+		return(calc->hwregs[PORT2]);
+
+	case 0x03:
+		v = (calc->keypad.onkeydown ? 0x00: 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+
+		return(v);
+
+	case 0x10:
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+		return(tilem_lcd_t6a04_read(calc));
+
+	case 0x14:
+		/* FIXME: determine value of this port on old
+		   hardware, and the values of bits 1-7.  (As on
+		   TI-83, probably mirrors port 0 in both cases.) */
+		b = battlevel[calc->hwregs[PORT4] >> 6];
+		return(calc->battery >= b ? 1 : 0);
+	}
+
+	tilem_warning(calc, "Input from port %x", port);
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB;
+
+	/* FIXME: this is all rather hypothetical and untested, but it
+	   makes sense based on how the TI-83 works */
+
+	if (calc->hwregs[PORT2] & 0x40) {
+		pageA = (0x08 | (calc->hwregs[PORT2] & 1));
+	}
+	else {
+		pageA = (calc->hwregs[PORT2] & 7);
+	}
+
+	if (calc->hwregs[PORT2] & 0x80) {
+		pageB = (0x08 | ((calc->hwregs[PORT2] >> 3) & 1));
+	}
+	else {
+		pageB = ((calc->hwregs[PORT2] >> 3) & 7);
+	}
+
+	if (calc->hwregs[PORT4] & 1) {
+		calc->mempagemap[1] = (pageA & ~1);
+		calc->mempagemap[2] = (pageA | 1);
+		calc->mempagemap[3] = pageB;
+	}
+	else {
+		calc->mempagemap[1] = pageA;
+		calc->mempagemap[2] = pageB;
+		calc->mempagemap[3] = 0x08;
+	}
+}
+
+void x2_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	switch(port&0xff) {
+	case 0x00:
+		calc->hwregs[PORT0] = value;
+
+		if (calc->hwregs[HW_VERSION] == 1) {
+			/* HW version 1 */
+			value = (((value >> 6) & (value >> 2))
+				 | ((value >> 4) & ~value));
+		}
+		else if (calc->hwregs[HW_VERSION] == 0) {
+			/* HW version unknown: auto-detect */
+			if ((value & 0xc3) == 0xc0) {
+				value = ((value >> 2) | (value >> 4));
+			}
+		}
+
+		tilem_linkport_set_lines(calc, value);
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x02:
+		calc->hwregs[PORT2] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x03:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02)) {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+		}
+
+		calc->poweronhalt = ((value & 8) >> 3);
+		calc->hwregs[PORT3] = value;
+		break;
+
+	case 0x04:
+		calc->hwregs[PORT4] = value;
+
+		/* Detect hardware version */
+		if (calc->z80.r.pc.d < 0x4000) {
+			if (value & 0x10)
+				calc->hwregs[HW_VERSION] = 1;
+			else
+				calc->hwregs[HW_VERSION] = 2;
+		}
+
+		/* FIXME: implement changing interrupt frequencies --
+		   somebody needs to measure them.  Also check if bit
+		   4 works as on 83. */
+
+		setup_mapping(calc);
+		break;
+
+	case 0x10:
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+	}
+
+	return;
+}
+
+void x2_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/x2/x2_memory.c b/tool/tilem-src/emu/x2/x2_memory.c
new file mode 100644
index 0000000..9829f4f
--- /dev/null
+++ b/tool/tilem-src/emu/x2/x2_memory.c
@@ -0,0 +1,70 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x2.h"
+
+#include <stdlib.h>
+
+void x2_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+
+	if (pa >= 0x28000)
+		abort();
+
+	if (pa >= 0x20000) {
+		*(calc->mem + pa) = v;
+	}
+}
+
+
+byte x2_z80_rdmem(TilemCalc* calc, dword A)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+	return (*(calc->mem + pa));
+}
+
+dword x2_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword x2_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		if (calc->mempagemap[i] == page) {
+			return ((i << 14) | (A & 0x3fff));
+		}
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/x2/x2_subcore.c b/tool/tilem-src/emu/x2/x2_subcore.c
new file mode 100644
index 0000000..bdb19fe
--- /dev/null
+++ b/tool/tilem-src/emu/x2/x2_subcore.c
@@ -0,0 +1,57 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x2.h"
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+static const char* keynames[64] = {
+	"Down", "Left", "Right", "Up", 0, 0, 0, 0,
+	"Enter", "Add", "Sub", "Mul", "Div", "Power", "Clear", 0,
+	"Chs", "3", "6", "9", "RParen", "Tan", "Vars", 0,
+	"DecPnt", "2", "5", "8", "LParen", "Cos", "Prgm", "Stat",
+	"0", "1", "4", "7", "Comma", "Sin", "Matrix", "Graphvar",
+	"On", "Store", "Ln", "Log", "Square", "Recip", "Math", "Alpha",
+	"Graph", "Trace", "Zoom", "Window", "YEqu", "2nd", "Mode", "Del",
+	0, 0, 0, 0, 0, 0, 0, 0};
+
+const TilemHardware hardware_ti82 = {
+	'2', "ti82", "TI-82",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_T6A04),
+	96, 64, 8 * 0x4000, 0x8000, 15 * 64, 0x40,
+	0, NULL, 0,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	keynames,
+	x2_reset, NULL,
+	x2_z80_in, x2_z80_out,
+	x2_z80_wrmem, x2_z80_rdmem, x2_z80_rdmem, NULL,
+	x2_z80_ptimer, tilem_lcd_t6a04_get_data,
+	x2_mem_ltop, x2_mem_ptol };
diff --git a/tool/tilem-src/emu/x3/x3.h b/tool/tilem-src/emu/x3/x3.h
new file mode 100644
index 0000000..78cbd78
--- /dev/null
+++ b/tool/tilem-src/emu/x3/x3.h
@@ -0,0 +1,50 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_X3_H
+#define _TILEM_X3_H
+
+enum {
+	PORT2,			/* port 2 (memory mapping) */
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* mapping mode + timer speed */
+	ROM_BANK,		/* current ROM bank for port 2 mapping */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES { "port2", "port3", "port4", "rom_bank" }
+
+#define TIMER_INT1 (TILEM_NUM_SYS_TIMERS + 1)
+#define TIMER_INT2A (TILEM_NUM_SYS_TIMERS + 2)
+#define TIMER_INT2B (TILEM_NUM_SYS_TIMERS + 3)
+#define NUM_HW_TIMERS 3
+
+#define HW_TIMER_NAMES { "int1", "int2a", "int2b" }
+
+void x3_reset(TilemCalc* calc);
+byte x3_z80_in(TilemCalc* calc, dword port);
+void x3_z80_out(TilemCalc* calc, dword port, byte value);
+void x3_z80_ptimer(TilemCalc* calc, int id);
+void x3_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte x3_z80_rdmem(TilemCalc* calc, dword addr);
+dword x3_mem_ltop(TilemCalc* calc, dword addr);
+dword x3_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/x3/x3_init.c b/tool/tilem-src/emu/x3/x3_init.c
new file mode 100644
index 0000000..84703a8
--- /dev/null
+++ b/tool/tilem-src/emu/x3/x3_init.c
@@ -0,0 +1,48 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x3.h"
+
+void x3_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT2] = 0xF8;
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x00;
+	calc->hwregs[ROM_BANK] = 0x00;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x00;
+	calc->mempagemap[2] = 0x11;
+	calc->mempagemap[3] = 0x10;
+
+	tilem_z80_set_speed(calc, 6000);
+
+	tilem_z80_set_timer(calc, TIMER_INT1, 1600, 9259, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2A, 1300, 9259, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2B, 1000, 9259, 1);
+}
diff --git a/tool/tilem-src/emu/x3/x3_io.c b/tool/tilem-src/emu/x3/x3_io.c
new file mode 100644
index 0000000..9db41c2
--- /dev/null
+++ b/tool/tilem-src/emu/x3/x3_io.c
@@ -0,0 +1,210 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x3.h"
+
+byte x3_z80_in(TilemCalc* calc, dword port)
+{
+	static const byte battlevel[4] = { 33, 39, 36, 43 };
+	byte v, b;
+	
+	switch(port&0x1f) {
+	case 0x00:
+	case 0x04:
+	case 0x08:
+	case 0x0C:
+		v = tilem_linkport_get_lines(calc);
+		return((calc->hwregs[ROM_BANK] << 1) | (v * 5));
+
+	case 0x01:
+	case 0x05:
+	case 0x09:
+	case 0x0D:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+	case 0x06:
+	case 0x0A:
+	case 0x0E:
+	case 0x16:
+	case 0x1E:
+		return(calc->hwregs[PORT2]);
+
+	case 0x03:
+	case 0x07:
+	case 0x0B:
+	case 0x0F:
+	case 0x17:
+	case 0x1F:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+
+		return(v);
+
+	case 0x10:
+	case 0x12:
+	case 0x18:
+	case 0x1A:
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+	case 0x13:
+	case 0x19:
+	case 0x1B:
+		return(tilem_lcd_t6a04_read(calc));
+
+	case 0x14:
+	case 0x1C:
+		b = battlevel[calc->hwregs[PORT4] >> 6];
+		v = tilem_linkport_get_lines(calc);
+		return((calc->battery >= b ? 1 : 0)
+		       | (calc->hwregs[ROM_BANK] << 1) | (v << 2));
+
+	case 0x15:
+	case 0x1D:
+		return(tilem_keypad_read_keys(calc) & ~1);
+	}
+
+	tilem_warning(calc, "Input from port %x", port);
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB;
+
+	if (calc->hwregs[PORT2] & 0x40) {
+		pageA = (0x10 | (calc->hwregs[PORT2] & 1));
+	}
+	else {
+		pageA = (calc->hwregs[ROM_BANK] | (calc->hwregs[PORT2] & 7));
+	}
+
+	if (calc->hwregs[PORT2] & 0x80) {
+		pageB = (0x10 | ((calc->hwregs[PORT2] >> 3) & 1));
+	}
+	else {
+		pageB = (calc->hwregs[ROM_BANK] | ((calc->hwregs[PORT2] >> 3) & 7));
+	}
+
+	if (calc->hwregs[PORT4] & 1) {
+		calc->mempagemap[1] = (pageA & ~1);
+		calc->mempagemap[2] = (pageA | 1);
+		calc->mempagemap[3] = pageB;
+	}
+	else {
+		calc->mempagemap[1] = pageA;
+		calc->mempagemap[2] = pageB;
+		calc->mempagemap[3] = 0x10;
+	}
+}
+
+void x3_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	static const int tmrvalues[8] = { 1667, 1852, 3889, 4321,
+					  6111, 6790, 8333, 9259 };
+	int t;
+
+	switch(port&0x1f) {
+	case 0x00:
+		calc->hwregs[ROM_BANK] = ((value & 0x10) >> 1);
+		tilem_linkport_set_lines(calc, value);
+		setup_mapping(calc);
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x02:
+		calc->hwregs[PORT2] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x03:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+
+		if (!(value & 0x04))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER2;
+
+		calc->poweronhalt = ((value & 8) >> 3);
+		calc->hwregs[PORT3] = value;
+		break;
+
+	case 0x04:
+		calc->hwregs[PORT4] = value;
+
+		t = tmrvalues[((value >> 4) & 1) | (value & 6)];
+		tilem_z80_set_timer_period(calc, TIMER_INT1, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2A, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2B, t);
+
+		setup_mapping(calc);
+		break;
+
+	case 0x10:
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+	}
+
+	return;
+}
+
+void x3_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT1:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+
+	case TIMER_INT2A:
+	case TIMER_INT2B:
+		if (calc->hwregs[PORT3] & 0x04)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER2;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/x3/x3_memory.c b/tool/tilem-src/emu/x3/x3_memory.c
new file mode 100644
index 0000000..ede1da5
--- /dev/null
+++ b/tool/tilem-src/emu/x3/x3_memory.c
@@ -0,0 +1,64 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x3.h"
+
+void x3_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+
+	if (pa >= 0x40000) {
+		*(calc->mem + pa) = v;
+	}
+}
+
+byte x3_z80_rdmem(TilemCalc* calc, dword A)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+	return (*(calc->mem + pa));
+}
+
+dword x3_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword x3_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		if (calc->mempagemap[i] == page) {
+			return ((i << 14) | (A & 0x3fff));
+		}
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/x3/x3_subcore.c b/tool/tilem-src/emu/x3/x3_subcore.c
new file mode 100644
index 0000000..a0c95c2
--- /dev/null
+++ b/tool/tilem-src/emu/x3/x3_subcore.c
@@ -0,0 +1,71 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x3.h"
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+static const char* keynames[64] = {
+	"Down", "Left", "Right", "Up", 0, 0, 0, 0,
+	"Enter", "Add", "Sub", "Mul", "Div", "Power", "Clear", 0,
+	"Chs", "3", "6", "9", "RParen", "Tan", "Vars", 0,
+	"DecPnt", "2", "5", "8", "LParen", "Cos", "Prgm", "Stat",
+	"0", "1", "4", "7", "Comma", "Sin", "Matrix", "Graphvar",
+	"On", "Store", "Ln", "Log", "Square", "Recip", "Math", "Alpha",
+	"Graph", "Trace", "Zoom", "Window", "YEqu", "2nd", "Mode", "Del",
+	0, 0, 0, 0, 0, 0, 0, 0};
+
+const TilemHardware hardware_ti83 = {
+	'3', "ti83", "TI-83 / TI-82 STATS",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_T6A04),
+	96, 64, 16 * 0x4000, 0x8000, 15 * 64, 0x40,
+	0, NULL, 0,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	keynames,
+	x3_reset, NULL,
+	x3_z80_in, x3_z80_out,
+	x3_z80_wrmem, x3_z80_rdmem, x3_z80_rdmem, NULL,
+	x3_z80_ptimer, tilem_lcd_t6a04_get_data,
+	x3_mem_ltop, x3_mem_ptol };
+
+const TilemHardware hardware_ti76 = {
+	'f', "ti76", "TI-76.fr",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_T6A04),
+	96, 64, 16 * 0x4000, 0x8000, 15 * 64, 0x40,
+	0, NULL, 0,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	keynames,
+	x3_reset, NULL,
+	x3_z80_in, x3_z80_out,
+	x3_z80_wrmem, x3_z80_rdmem, x3_z80_rdmem, NULL,
+	x3_z80_ptimer, tilem_lcd_t6a04_get_data,
+	x3_mem_ltop, x3_mem_ptol };
diff --git a/tool/tilem-src/emu/x4/x4.h b/tool/tilem-src/emu/x4/x4.h
new file mode 100644
index 0000000..9ca55dd
--- /dev/null
+++ b/tool/tilem-src/emu/x4/x4.h
@@ -0,0 +1,105 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_X4_H
+#define _TILEM_X4_H
+
+enum {
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* interrupt timer speed */
+	PORT5,			/* memory mapping bank C */
+	PORT6,			/* memory mapping bank A */
+	PORT7,			/* memory mapping bank B */
+	PORT8,			/* link assist mode flags */
+	PORT9,			/* unknown (link assist settings?) */
+	PORTA,			/* unknown (timeout value?) */
+	PORTB,			/* unknown (timeout value?) */
+	PORTC,			/* unknown (timeout value?) */
+	PORTD,			/* unknown */
+	PORTE,			/* unknown */
+	PORTF,			/* unknown */
+
+	PORT20,			/* CPU speed control */
+	PORT21,			/* hardware type / RAM no-exec control */
+	PORT22,			/* Flash no-exec lower limit */
+	PORT23,			/* Flash no-exec upper limit */
+	PORT25,			/* RAM no-exec lower limit */
+	PORT26,			/* RAM no-exec upper limit */
+	PORT27,			/* bank C forced-page-0 limit */
+	PORT28,			/* bank B forced-page-1 limit */
+	PORT29,			/* LCD port delay (6 MHz) */
+	PORT2A,			/* LCD port delay (mode 1) */
+	PORT2B,			/* LCD port delay (mode 2) */
+	PORT2C,			/* LCD port delay (mode 3) */
+	PORT2D,			/* unknown */
+	PORT2E,			/* memory delay */
+	PORT2F,			/* Duration of LCD wait timer */
+
+	CLOCK_MODE,		/* clock mode */
+	CLOCK_INPUT,		/* clock input register */
+	CLOCK_DIFF,		/* clock value minus actual time */
+
+	RAM_READ_DELAY,
+	RAM_WRITE_DELAY,
+	RAM_EXEC_DELAY,
+	FLASH_READ_DELAY,
+	FLASH_WRITE_DELAY,
+	FLASH_EXEC_DELAY,
+	LCD_PORT_DELAY,
+	NO_EXEC_RAM_MASK,
+	NO_EXEC_RAM_LOWER,
+	NO_EXEC_RAM_UPPER,
+
+	LCD_WAIT,		/* LCD wait timer active */
+	PROTECTSTATE,		/* port protection state */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES \
+	{ "port3", "port4", "port5", "port6", "port7", "port8", "port9", \
+	  "portA", "portB", "portC", "portD", "portE", "portF", "port20", \
+	  "port21", "port22", "port23", "port25", "port26", "port27", \
+	  "port28", "port29", "port2A", "port2B", "port2C", "port2D", \
+	  "port2E", "port2F", "clock_mode", "clock_input", "clock_diff", \
+	  "ram_read_delay", "ram_write_delay", "ram_exec_delay", \
+	  "flash_read_delay", "flash_write_delay", "flash_exec_delay", \
+	  "lcd_port_delay", "no_exec_ram_mask", "no_exec_ram_lower", \
+	  "no_exec_ram_upper", "lcd_wait", "protectstate" }
+
+#define TIMER_INT1 (TILEM_NUM_SYS_TIMERS + 1)
+#define TIMER_INT2A (TILEM_NUM_SYS_TIMERS + 2)
+#define TIMER_INT2B (TILEM_NUM_SYS_TIMERS + 3)
+#define TIMER_LCD_WAIT (TILEM_NUM_SYS_TIMERS + 4)
+#define NUM_HW_TIMERS 4
+
+#define HW_TIMER_NAMES { "int1", "int2a", "int2b", "lcd_wait" }
+
+void x4_reset(TilemCalc* calc);
+void x4_stateloaded(TilemCalc* calc, int savtype);
+byte x4_z80_in(TilemCalc* calc, dword port);
+void x4_z80_out(TilemCalc* calc, dword port, byte value);
+void x4_z80_ptimer(TilemCalc* calc, int id);
+void x4_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte x4_z80_rdmem(TilemCalc* calc, dword addr);
+byte x4_z80_rdmem_m1(TilemCalc* calc, dword addr);
+dword x4_mem_ltop(TilemCalc* calc, dword addr);
+dword x4_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/x4/x4_init.c b/tool/tilem-src/emu/x4/x4_init.c
new file mode 100644
index 0000000..41f7477
--- /dev/null
+++ b/tool/tilem-src/emu/x4/x4_init.c
@@ -0,0 +1,87 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x4.h"
+
+void x4_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x07;
+	calc->hwregs[PORT6] = 0x3F;
+	calc->hwregs[PORT7] = 0x3F;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x3E;
+	calc->mempagemap[2] = 0x3F;
+	calc->mempagemap[3] = 0x3F;
+
+	calc->z80.r.pc.d = 0x8000;
+
+	calc->hwregs[PORT8] = 0x80;
+
+	calc->hwregs[PORT20] = 0;
+	calc->hwregs[PORT21] = 0;
+	calc->hwregs[PORT22] = 0x08;
+	calc->hwregs[PORT23] = 0x29;
+	calc->hwregs[PORT25] = 0x10;
+	calc->hwregs[PORT26] = 0x20;
+	calc->hwregs[PORT27] = 0;
+	calc->hwregs[PORT28] = 0;
+	calc->hwregs[PORT29] = 0x14;
+	calc->hwregs[PORT2A] = 0x27;
+	calc->hwregs[PORT2B] = 0x2F;
+	calc->hwregs[PORT2C] = 0x3B;
+	calc->hwregs[PORT2D] = 0x01;
+	calc->hwregs[PORT2E] = 0x44;
+	calc->hwregs[PORT2F] = 0x4A;
+
+	calc->hwregs[FLASH_READ_DELAY] = 0;
+	calc->hwregs[FLASH_WRITE_DELAY] = 0;
+	calc->hwregs[FLASH_EXEC_DELAY] = 0;
+	calc->hwregs[RAM_READ_DELAY] = 0;
+	calc->hwregs[RAM_WRITE_DELAY] = 0;
+	calc->hwregs[RAM_EXEC_DELAY] = 0;
+	calc->hwregs[LCD_PORT_DELAY] = 5;
+	calc->hwregs[NO_EXEC_RAM_MASK] = 0x7C00;
+	calc->hwregs[NO_EXEC_RAM_LOWER] = 0x4000;
+	calc->hwregs[NO_EXEC_RAM_UPPER] = 0x8000;
+
+	calc->hwregs[PROTECTSTATE] = 0;
+
+	tilem_z80_set_speed(calc, 6000);
+
+	tilem_z80_set_timer(calc, TIMER_INT1, 1600, 9277, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2A, 1300, 9277, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2B, 1000, 9277, 1);
+}
+
+void x4_stateloaded(TilemCalc* calc, int savtype TILEM_ATTR_UNUSED)
+{
+	tilem_calc_fix_certificate(calc, calc->mem + (0x3E * 0x4000L),
+	                           0x29, 0x0c, 0x1e50);
+}
diff --git a/tool/tilem-src/emu/x4/x4_io.c b/tool/tilem-src/emu/x4/x4_io.c
new file mode 100644
index 0000000..e4f086b
--- /dev/null
+++ b/tool/tilem-src/emu/x4/x4_io.c
@@ -0,0 +1,747 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <time.h>
+#include <tilem.h>
+
+#include "x4.h"
+
+static void set_lcd_wait_timer(TilemCalc* calc)
+{
+	static const int delaytime[8] = { 48, 112, 176, 240,
+					  304, 368, 432, 496 };
+	int i;
+
+	switch (calc->hwregs[PORT20] & 3) {
+	case 0:
+		return;
+	case 1:
+		i = (calc->hwregs[PORT2F] & 3);
+		break;
+	case 2:
+		i = ((calc->hwregs[PORT2F] >> 2) & 7);
+		break;
+	default:
+		i = ((calc->hwregs[PORT2F] >> 5) & 7);
+		break;
+	}
+
+	tilem_z80_set_timer(calc, TIMER_LCD_WAIT, delaytime[i], 0, 0);
+	calc->hwregs[LCD_WAIT] = 1;
+}
+
+byte x4_z80_in(TilemCalc* calc, dword port)
+{
+	/* FIXME: measure actual levels */
+	static const byte battlevel[4] = { 33, 39, 36, 43 };
+	byte v;
+	unsigned int f;
+	time_t curtime;
+
+	switch(port&0xff) {
+	case 0x00:
+		v = tilem_linkport_get_lines(calc);
+		v |= (calc->linkport.lines << 4);
+		return(v);
+
+	case 0x01:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+		v = battlevel[calc->hwregs[PORT4] >> 6];
+		return ((calc->battery >= v ? 0xe1 : 0xe0)
+			| (calc->hwregs[LCD_WAIT] ? 0 : 2)
+			| (calc->flash.unlock << 2));
+
+	case 0x03:
+		return(calc->hwregs[PORT3]);
+
+	case 0x04:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER2)
+			v |= 0x04;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ACTIVE)
+			v |= 0x10;
+
+		if (calc->usertimers[0].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x20;
+		if (calc->usertimers[1].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x40;
+		if (calc->usertimers[2].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x80;
+
+		return(v);
+
+	case 0x05:
+		return(calc->hwregs[PORT5] & 0x0f);
+
+	case 0x06:
+		return(calc->hwregs[PORT6]);
+
+	case 0x07:
+		return(calc->hwregs[PORT7]);
+
+	case 0x08:
+		return(calc->hwregs[PORT8]);
+
+	case 0x09:
+		f = tilem_linkport_get_assist_flags(calc);
+
+		if (f & (TILEM_LINK_ASSIST_READ_BUSY
+			 | TILEM_LINK_ASSIST_WRITE_BUSY))
+			v = 0x00;
+		else
+			v = 0x20;
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_READ)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_IDLE)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ERROR)
+			v |= 0x04;
+		if (f & TILEM_LINK_ASSIST_READ_BUSY)
+			v |= 0x08;
+		if (f & TILEM_LINK_ASSIST_READ_BYTE)
+			v |= 0x10;
+		if (f & (TILEM_LINK_ASSIST_READ_ERROR
+			 | TILEM_LINK_ASSIST_WRITE_ERROR))
+			v |= 0x40;
+		if (f & TILEM_LINK_ASSIST_WRITE_BUSY)
+			v |= 0x80;
+
+		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_ERROR;
+
+		return(v);
+
+	case 0x0A:
+		v = calc->linkport.assistlastbyte;
+		tilem_linkport_read_byte(calc);
+		return(v);
+
+	case 0x0E:
+		return(calc->hwregs[PORTE] & 3);
+
+	case 0x0F:
+		return(calc->hwregs[PORTF] & 3);
+
+	case 0x10:
+	case 0x12:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+	case 0x13:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		return(tilem_lcd_t6a04_read(calc));
+
+	case 0x15:
+		return(0x45);	/* ??? */
+
+	case 0x1C:
+		return(tilem_md5_assist_get_value(calc));
+
+	case 0x1D:
+		return(tilem_md5_assist_get_value(calc) >> 8);
+
+	case 0x1E:
+		return(tilem_md5_assist_get_value(calc) >> 16);
+
+	case 0x1F:
+		return(tilem_md5_assist_get_value(calc) >> 24);
+
+	case 0x20:
+		return(calc->hwregs[PORT20] & 3);
+
+	case 0x21:
+		return(calc->hwregs[PORT21] & 0x33);
+
+	case 0x22:
+		return(calc->hwregs[PORT22]);
+
+	case 0x23:
+		return(calc->hwregs[PORT23]);
+
+	case 0x25:
+		return(calc->hwregs[PORT25]);
+
+	case 0x26:
+		return(calc->hwregs[PORT26]);
+
+	case 0x27:
+		return(calc->hwregs[PORT27]);
+
+	case 0x28:
+		return(calc->hwregs[PORT28]);
+
+	case 0x29:
+		return(calc->hwregs[PORT29]);
+
+	case 0x2A:
+		return(calc->hwregs[PORT2A]);
+
+	case 0x2B:
+		return(calc->hwregs[PORT2B]);
+
+	case 0x2C:
+		return(calc->hwregs[PORT2C]);
+
+	case 0x2D:
+		return(calc->hwregs[PORT2D] & 3);
+
+	case 0x2E:
+		return(calc->hwregs[PORT2E]);
+
+	case 0x2F:
+		return(calc->hwregs[PORT2F]);
+
+	case 0x30:
+		return(calc->usertimers[0].frequency);
+	case 0x31:
+		return(calc->usertimers[0].status);
+	case 0x32:
+		return(tilem_user_timer_get_value(calc, 0));
+
+	case 0x33:
+		return(calc->usertimers[1].frequency);
+	case 0x34:
+		return(calc->usertimers[1].status);
+	case 0x35:
+		return(tilem_user_timer_get_value(calc, 1));
+
+	case 0x36:
+		return(calc->usertimers[2].frequency);
+	case 0x37:
+		return(calc->usertimers[2].status);
+	case 0x38:
+		return(tilem_user_timer_get_value(calc, 2));
+
+	case 0x39:
+		return(0xf0);	/* ??? */
+
+	case 0x40:
+		return calc->hwregs[CLOCK_MODE];
+
+	case 0x41:	
+		return calc->hwregs[CLOCK_INPUT]&0xff;
+
+	case 0x42:
+		return (calc->hwregs[CLOCK_INPUT]>>8)&0xff;
+
+	case 0x43:
+		return (calc->hwregs[CLOCK_INPUT]>>16)&0xff;
+
+	case 0x44:
+		return (calc->hwregs[CLOCK_INPUT]>>24)&0xff;
+
+	case 0x45:
+	case 0x46:
+	case 0x47:
+	case 0x48:
+		if (calc->hwregs[CLOCK_MODE] & 1) {
+			time(&curtime);
+		}
+		else {
+			curtime = 0;
+		}
+		curtime += calc->hwregs[CLOCK_DIFF];
+		return (curtime >> ((port - 0x45) * 8));
+
+	case 0x4C:
+		return(0x22);
+
+	case 0x4D:
+		/* USB port - not emulated, calculator should
+		   recognize that the USB cable is
+		   disconnected.
+
+		   Thanks go to Dan Englender for these
+		   values. */
+
+		return(0xA5);
+
+	case 0x55:
+		return(0x1F);
+
+	case 0x56:
+		return(0x00);
+
+	case 0x57:
+		return(0x50);
+
+	case 0x0B:
+	case 0x0C:
+	case 0x0D:
+	case 0x14:
+	case 0x16:
+	case 0x17:
+	case 0x18:
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+		return(0);
+	}
+
+	tilem_warning(calc, "Input from port %x", port);
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB, pageC;
+
+	if (calc->hwregs[PORT6] & 0x80)
+		pageA = (0x40 | (calc->hwregs[PORT6] & 7));
+	else
+		pageA = (calc->hwregs[PORT6] & 0x3f);
+
+	if (calc->hwregs[PORT7] & 0x80)
+		pageB = (0x40 | (calc->hwregs[PORT7] & 7));
+	else
+		pageB = (calc->hwregs[PORT7] & 0x3f);
+
+	pageC = (0x40 | (calc->hwregs[PORT5] & 7));
+
+	if (calc->hwregs[PORT4] & 1) {
+		calc->mempagemap[1] = (pageA & ~1);
+		calc->mempagemap[2] = (pageA | 1);
+		calc->mempagemap[3] = pageB;
+	}
+	else {
+		calc->mempagemap[1] = pageA;
+		calc->mempagemap[2] = pageB;
+		calc->mempagemap[3] = pageC;
+	}
+}
+
+static void setup_clockdelays(TilemCalc* calc)
+{
+	byte lcdport = calc->hwregs[PORT29 + (calc->hwregs[PORT20] & 3)];
+	byte memport = calc->hwregs[PORT2E];
+
+	if (!(lcdport & 1))
+		memport &= ~0x07;
+	if (!(lcdport & 2))
+		memport &= ~0x70;
+
+	calc->hwregs[FLASH_EXEC_DELAY] = (memport & 1);
+	calc->hwregs[FLASH_READ_DELAY] = ((memport >> 1) & 1);
+	calc->hwregs[FLASH_WRITE_DELAY] = ((memport >> 2) & 1);
+
+	calc->hwregs[RAM_EXEC_DELAY] = ((memport >> 4) & 1);
+	calc->hwregs[RAM_READ_DELAY] = ((memport >> 5) & 1);
+	calc->hwregs[RAM_WRITE_DELAY] = ((memport >> 6) & 1);
+
+	calc->hwregs[LCD_PORT_DELAY] = (lcdport >> 2);
+}
+
+void x4_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	static const int tmrvalues[4] = { 1953, 4395, 6836, 9277 };
+	int t, r;
+	unsigned int mode;
+	time_t curtime;
+
+	switch(port&0xff) {
+	case 0x00:
+		tilem_linkport_set_lines(calc, value);
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x03:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+
+		if (!(value & 0x04))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER2;
+
+		if (value & 0x06) {
+			calc->usertimers[0].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[1].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[2].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+		}
+		else {
+			calc->usertimers[0].status |= TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[1].status |= TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[2].status |= TILEM_USER_TIMER_NO_HALT_INT;
+		}
+
+		mode = calc->linkport.mode;
+		if (value & 0x10)
+			mode |= TILEM_LINK_MODE_INT_ON_ACTIVE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ACTIVE;
+
+		tilem_linkport_set_mode(calc, mode);
+
+		calc->poweronhalt = ((value & 8) >> 3);
+		calc->hwregs[PORT3] = value;
+		break;
+
+
+	case 0x04:
+		calc->hwregs[PORT4] = value;
+
+		t = tmrvalues[(value & 6) >> 1];
+		tilem_z80_set_timer_period(calc, TIMER_INT1, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2A, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2B, t);
+
+		setup_mapping(calc);
+		break;
+	
+	case 0x05:
+		calc->hwregs[PORT5] = value & 0x0f;
+		setup_mapping(calc);
+		break;
+
+	case 0x06:
+		calc->hwregs[PORT6] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x07:
+		calc->hwregs[PORT7] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x08:
+		calc->hwregs[PORT8] = value;
+
+		mode = calc->linkport.mode;
+
+		if (value & 0x01)
+			mode |= TILEM_LINK_MODE_INT_ON_READ;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_READ;
+
+		if (value & 0x02)
+			mode |= TILEM_LINK_MODE_INT_ON_IDLE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_IDLE;
+
+		if (value & 0x04)
+			mode |= TILEM_LINK_MODE_INT_ON_ERROR;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ERROR;
+
+		if (value & 0x80)
+			mode &= ~TILEM_LINK_MODE_ASSIST;
+		else
+			mode |= TILEM_LINK_MODE_ASSIST;
+
+		tilem_linkport_set_mode(calc, mode);
+		break;
+
+	case 0x09:
+		calc->hwregs[PORT9] = value;
+		break;
+
+	case 0x0A:
+		calc->hwregs[PORTA] = value;
+		break;
+
+	case 0x0B:
+		calc->hwregs[PORTB] = value;
+		break;
+
+	case 0x0C:
+		calc->hwregs[PORTC] = value;
+		break;
+
+
+	case 0x0D:
+		if (!(calc->hwregs[PORT8] & 0x80))
+			tilem_linkport_write_byte(calc, value);
+		break;
+
+	case 0x0E:
+		calc->hwregs[PORTE] = value;
+		break;
+
+	case 0x0F:
+		calc->hwregs[PORTF] = value;
+		break;
+
+	case 0x10:
+	case 0x12:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+	case 0x13:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+
+	case 0x14:
+		if (calc->hwregs[PROTECTSTATE] == 7) {
+			if (value & 1)
+				tilem_message(calc, "Flash unlocked");
+			else
+				tilem_message(calc, "Flash locked");
+			calc->flash.unlock = value&1;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 14");
+		}
+		break;
+
+	case 0x18:
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+	case 0x1C:
+	case 0x1D:
+		r = (port & 0xff) - 0x18;
+		calc->md5assist.regs[r] >>= 8;
+		calc->md5assist.regs[r] |= (value << 24);
+		break;
+
+	case 0x1E:
+		calc->md5assist.shift = value & 0x1f;
+		break;
+
+	case 0x1F:
+		calc->md5assist.mode = value & 3;
+		break;
+
+	case 0x20:
+		calc->hwregs[PORT20] = value;
+
+		if (value & 3) {
+			tilem_z80_set_speed(calc, 15000);
+		}
+		else {
+			tilem_z80_set_speed(calc, 6000);
+		}
+
+		setup_clockdelays(calc);
+		break;
+
+	case 0x21:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT21] = value;
+			t = (value >> 4) & 3;
+			calc->hwregs[NO_EXEC_RAM_MASK] = (0x8000 << t) - 0x400;
+			calc->flash.overridegroup = value & 3;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 21");
+		}
+		break;
+
+	case 0x22:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT22] = value;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 22");
+		}
+		break;
+
+	case 0x23:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT23] = value;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 23");
+		}
+		break;
+
+	case 0x25:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT25] = value;
+			calc->hwregs[NO_EXEC_RAM_LOWER] = value * 0x400;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 25");
+		}
+		break;
+
+	case 0x26:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT26] = value;
+			calc->hwregs[NO_EXEC_RAM_UPPER] = value * 0x400;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 26");
+		}
+		break;
+
+	case 0x27:
+		calc->hwregs[PORT27] = value;
+		break;
+
+	case 0x28:
+		calc->hwregs[PORT28] = value;
+		break;
+
+	case 0x29:
+		calc->hwregs[PORT29] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2A:
+		calc->hwregs[PORT2A] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2B:
+		calc->hwregs[PORT2B] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2C:
+		calc->hwregs[PORT2C] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2D:
+		calc->hwregs[PORT2D] = value;
+		break;
+
+	case 0x2E:
+		calc->hwregs[PORT2E] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2F:
+		calc->hwregs[PORT2F] = value;
+		break;
+
+	case 0x30:
+		tilem_user_timer_set_frequency(calc, 0, value);
+		break;
+	case 0x31:
+		tilem_user_timer_set_mode(calc, 0, value);
+		break;
+	case 0x32:
+		tilem_user_timer_start(calc, 0, value);
+		break;
+
+	case 0x33:
+		tilem_user_timer_set_frequency(calc, 1, value);
+		break;
+	case 0x34:
+		tilem_user_timer_set_mode(calc, 1, value);
+		break;
+	case 0x35:
+		tilem_user_timer_start(calc, 1, value);
+		break;
+
+	case 0x36:
+		tilem_user_timer_set_frequency(calc, 2, value);
+		break;
+	case 0x37:
+		tilem_user_timer_set_mode(calc, 2, value);
+		break;
+	case 0x38:
+		tilem_user_timer_start(calc, 2, value);
+		break;
+
+	case 0x40:
+		time(&curtime);
+
+		if ((calc->hwregs[CLOCK_MODE] & 1) != (value & 1)) {
+			if (value & 1)
+				calc->hwregs[CLOCK_DIFF] -= curtime;
+			else
+				calc->hwregs[CLOCK_DIFF] += curtime;
+		}
+
+		if (!(calc->hwregs[CLOCK_MODE] & 2) && (value & 2)) {
+			calc->hwregs[CLOCK_DIFF] = calc->hwregs[CLOCK_INPUT];
+			if (value & 1)
+				calc->hwregs[CLOCK_DIFF] -= curtime;
+		}
+		calc->hwregs[CLOCK_MODE] = value & 3;
+		break;
+
+	case 0x41:
+		calc->hwregs[CLOCK_INPUT] &= 0xffffff00;
+		calc->hwregs[CLOCK_INPUT] |= value;
+		break;
+
+	case 0x42:
+		calc->hwregs[CLOCK_INPUT] &= 0xffff00ff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 8);
+		break;
+
+	case 0x43:
+		calc->hwregs[CLOCK_INPUT] &= 0xff00ffff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 16);
+		break;
+
+	case 0x44:
+		calc->hwregs[CLOCK_INPUT] &= 0x00ffffff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 24);
+		break;
+	}
+
+	return;
+}
+
+void x4_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT1:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+
+	case TIMER_INT2A:
+	case TIMER_INT2B:
+		if (calc->hwregs[PORT3] & 0x04)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER2;
+		break;
+
+	case TIMER_LCD_WAIT:
+		calc->hwregs[LCD_WAIT] = 0;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/x4/x4_memory.c b/tool/tilem-src/emu/x4/x4_memory.c
new file mode 100644
index 0000000..f33827e
--- /dev/null
+++ b/tool/tilem-src/emu/x4/x4_memory.c
@@ -0,0 +1,207 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x4.h"
+
+void x4_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	unsigned long pa;
+	byte page;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x40;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x41;
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x100000) {
+		calc->z80.clock += calc->hwregs[FLASH_WRITE_DELAY];
+		tilem_flash_write_byte(calc, pa, v);
+	}
+	else if (pa < 0x120000) {
+		calc->z80.clock += calc->hwregs[RAM_WRITE_DELAY];
+		*(calc->mem+pa) = v;
+	}
+}
+
+static inline byte readbyte(TilemCalc* calc, dword pa)
+{
+	static const byte protectbytes[6] = {0x00,0x00,0xed,0x56,0xf3,0xd3};
+	int state = calc->hwregs[PROTECTSTATE];
+	byte value;
+
+	if (pa < 0x100000 && (calc->flash.state || calc->flash.busy))
+		value = tilem_flash_read_byte(calc, pa);
+	else
+		value = *(calc->mem + pa);
+
+	if (pa < 0xB0000 || pa >= 0x100000
+	    || (pa >= 0xC0000 && pa < 0xF0000))
+		calc->hwregs[PROTECTSTATE] = 0;
+	else if (state == 6)
+		calc->hwregs[PROTECTSTATE] = 7;
+	else if (state < 6 && value == protectbytes[state])
+		calc->hwregs[PROTECTSTATE] = state + 1;
+	else
+		calc->hwregs[PROTECTSTATE] = 0;
+
+	return (value);
+}
+
+byte x4_z80_rdmem(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x40;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x41;
+	}
+
+	if (TILEM_UNLIKELY(page == 0x3E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x100000)
+		calc->z80.clock += calc->hwregs[FLASH_READ_DELAY];
+	else
+		calc->z80.clock += calc->hwregs[RAM_READ_DELAY];
+
+	value = readbyte(calc, pa);
+	return (value);
+}
+
+byte x4_z80_rdmem_m1(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa, m;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x40;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x41;
+	}
+
+	if (TILEM_UNLIKELY(page == 0x3E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x100000) {
+		calc->z80.clock += calc->hwregs[FLASH_EXEC_DELAY];
+
+		if (TILEM_UNLIKELY(page >= calc->hwregs[PORT22]
+		                   && page <= calc->hwregs[PORT23])) {
+			tilem_warning(calc, "Executing in restricted Flash area");
+			tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+		}
+	}
+	else {
+		calc->z80.clock += calc->hwregs[RAM_EXEC_DELAY];
+
+		m = pa & calc->hwregs[NO_EXEC_RAM_MASK];
+		if (TILEM_UNLIKELY(m < calc->hwregs[NO_EXEC_RAM_LOWER]
+		                   || m > calc->hwregs[NO_EXEC_RAM_UPPER])) {
+			tilem_warning(calc, "Executing in restricted RAM area");
+			tilem_z80_exception(calc, TILEM_EXC_RAM_EXEC);
+		}
+	}
+
+	value = readbyte(calc, pa);
+
+	if (TILEM_UNLIKELY(value == 0xff && A == 0x0038)) {
+		tilem_warning(calc, "No OS installed");
+		tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+	}
+
+	return (value);
+}
+
+dword x4_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x40;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x41;
+	}
+
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword x4_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+
+	if (!page)
+		return (A & 0x3fff);
+
+	if (page == calc->mempagemap[1])
+		return (0x4000 | (A & 0x3fff));
+
+	if ((A & 0x3fff) < 64 * calc->hwregs[PORT28]) {
+		if (page == 0x41)
+			return (0x8000 | (A & 0x3fff));
+	}
+	else {
+		if (page == calc->mempagemap[2])
+			return (0x8000 | (A & 0x3fff));
+	}
+
+	if ((A & 0x3fff) >= (0x4000 - 64 * calc->hwregs[PORT27])) {
+		if (page == 0x40)
+			return (0xC000 | (A & 0x3fff));
+	}
+	else {
+		if (page == calc->mempagemap[3])
+			return (0xC000 | (A & 0x3fff));
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/x4/x4_subcore.c b/tool/tilem-src/emu/x4/x4_subcore.c
new file mode 100644
index 0000000..b393ecf
--- /dev/null
+++ b/tool/tilem-src/emu/x4/x4_subcore.c
@@ -0,0 +1,65 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x4.h"
+
+static const TilemFlashSector flashsectors[] = {
+	{0x000000, 0x10000, 0}, {0x010000, 0x10000, 0},
+	{0x020000, 0x10000, 0}, {0x030000, 0x10000, 0},
+	{0x040000, 0x10000, 0}, {0x050000, 0x10000, 0},
+	{0x060000, 0x10000, 0}, {0x070000, 0x10000, 0},
+	{0x080000, 0x10000, 0}, {0x090000, 0x10000, 0},
+	{0x0A0000, 0x10000, 0}, {0x0B0000, 0x10000, 1},
+	{0x0C0000, 0x10000, 0}, {0x0D0000, 0x10000, 0},
+	{0x0E0000, 0x10000, 0}, {0x0F0000, 0x08000, 0},
+	{0x0F8000, 0x02000, 0}, {0x0FA000, 0x02000, 0},
+	{0x0FC000, 0x04000, 1}};
+
+#define NUM_FLASH_SECTORS (sizeof(flashsectors) / sizeof(TilemFlashSector))
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+extern const char* xp_keynames[];
+
+TilemHardware hardware_ti84p = {
+	'4', "ti84p", "TI-84 Plus",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_LINK_ASSIST
+	 | TILEM_CALC_HAS_T6A04 | TILEM_CALC_HAS_FLASH
+	 | TILEM_CALC_HAS_MD5_ASSIST),
+	96, 64, 64 * 0x4000, 8 * 0x4000, 16 * 64, 0x80,
+	NUM_FLASH_SECTORS, flashsectors, 3,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	xp_keynames,
+	x4_reset, x4_stateloaded,
+	x4_z80_in, x4_z80_out,
+	x4_z80_wrmem, x4_z80_rdmem, x4_z80_rdmem_m1, NULL,
+	x4_z80_ptimer, tilem_lcd_t6a04_get_data,
+	x4_mem_ltop, x4_mem_ptol };
diff --git a/tool/tilem-src/emu/x5/x5.h b/tool/tilem-src/emu/x5/x5.h
new file mode 100644
index 0000000..2af3ed6
--- /dev/null
+++ b/tool/tilem-src/emu/x5/x5.h
@@ -0,0 +1,49 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_X5_H
+#define _TILEM_X5_H
+
+enum {
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* mapping mode, timer control */
+	PORT5,			/* memory mapping bank A */
+	PORT6,			/* memory mapping bank B */
+	PORT7,			/* link port control */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES { "port3", "port4", "port5", "port6", "port7" }
+
+#define TIMER_INT (TILEM_NUM_SYS_TIMERS + 1)
+#define NUM_HW_TIMERS 1
+
+#define HW_TIMER_NAMES { "int" }
+
+void x5_reset(TilemCalc* calc);
+byte x5_z80_in(TilemCalc* calc, dword port);
+void x5_z80_out(TilemCalc* calc, dword port, byte value);
+void x5_z80_ptimer(TilemCalc* calc, int id);
+void x5_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte x5_z80_rdmem(TilemCalc* calc, dword addr);
+dword x5_mem_ltop(TilemCalc* calc, dword addr);
+dword x5_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/x5/x5_init.c b/tool/tilem-src/emu/x5/x5_init.c
new file mode 100644
index 0000000..07ca164
--- /dev/null
+++ b/tool/tilem-src/emu/x5/x5_init.c
@@ -0,0 +1,49 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x5.h"
+
+void x5_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x16;
+	calc->hwregs[PORT5] = 0x00;
+	calc->hwregs[PORT6] = 0x40;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x00;
+	calc->mempagemap[2] = 0x08;
+	calc->mempagemap[3] = 0x08;
+
+	calc->lcd.addr = 0x3c00;
+
+	tilem_z80_set_speed(calc, 6000);
+
+	/* FIXME: measure actual frequency */
+	tilem_z80_set_timer(calc, TIMER_INT, 1000, 5000, 1);
+}
diff --git a/tool/tilem-src/emu/x5/x5_io.c b/tool/tilem-src/emu/x5/x5_io.c
new file mode 100644
index 0000000..cd48f55
--- /dev/null
+++ b/tool/tilem-src/emu/x5/x5_io.c
@@ -0,0 +1,183 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x5.h"
+
+byte x5_z80_in(TilemCalc* calc, dword port)
+{
+	byte v, b;
+	
+	switch(port&0xff) {
+	case 0x01:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x03:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+
+		return(v);
+
+	case 0x04:
+		return(0x01);
+
+	case 0x05:
+		return(calc->hwregs[PORT5]);
+
+	case 0x06:
+		return(calc->hwregs[PORT6]);
+
+	case 0x07:
+		v = tilem_linkport_get_lines(calc);
+		b = (calc->hwregs[PORT7] >> 4) | 0xf0;
+		return ((calc->hwregs[PORT7] & b) | (v & ~b));
+	}
+
+	tilem_warning(calc, "Input from port %x", port);
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB;
+
+	if (calc->hwregs[PORT5] & 0x40) {
+		pageA = (0x08 | (calc->hwregs[PORT5] & 1));
+	}
+	else {
+		pageA = (calc->hwregs[PORT5] & 7);
+	}
+
+	if (calc->hwregs[PORT6] & 0x40) {
+		pageB = (0x08 | (calc->hwregs[PORT6] & 1));
+	}
+	else {
+		pageB = (calc->hwregs[PORT6] & 7);
+	}
+
+	calc->mempagemap[1] = pageA;
+	calc->mempagemap[2] = pageB;
+	calc->mempagemap[3] = 0x08;	
+}
+
+void x5_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	switch(port&0xff) {
+	case 0x00:
+		calc->lcd.addr = ((value & 0x3f) << 8);
+		calc->z80.lastlcdwrite = calc->z80.clock;
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x02:
+		/* Contrast */
+		calc->lcd.contrast = 16 + (value & 0x1f);
+		break;
+
+	case 0x03:
+		/* Interrupts / LCD power */
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02)) {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+		}
+
+		calc->hwregs[PORT3] = value;
+		calc->lcd.active = calc->poweronhalt = ((value & 8) >> 3);
+		break;
+
+	case 0x04:
+		/* LCD control */
+		calc->hwregs[PORT4] = value;
+
+		switch (value & 0x18) {
+		case 0x00:
+			calc->lcd.rowstride = 10;
+			break;
+
+		case 0x08:
+			calc->lcd.rowstride = 12;
+			break;
+
+		case 0x10:
+			calc->lcd.rowstride = 16;
+			break;
+
+		case 0x18:
+			calc->lcd.rowstride = 20;
+			break;
+		}
+		calc->z80.lastlcdwrite = calc->z80.clock;
+		break;
+
+	case 0x05:
+		calc->hwregs[PORT5] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x06:
+		calc->hwregs[PORT6] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x07:
+		/* Link */
+		calc->hwregs[PORT7] = value;
+
+		value = (((value >> 6) & (value >> 2))
+			 | ((value >> 4) & ~value));
+
+		tilem_linkport_set_lines(calc, value);
+		break;
+	}
+
+	return;
+}
+
+void x5_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/x5/x5_memory.c b/tool/tilem-src/emu/x5/x5_memory.c
new file mode 100644
index 0000000..050e234
--- /dev/null
+++ b/tool/tilem-src/emu/x5/x5_memory.c
@@ -0,0 +1,68 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x5.h"
+
+void x5_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+
+	if (pa >= 0x20000) {
+		*(calc->mem + pa) = v;
+
+		if (((pa - 0x20000 - calc->lcd.addr) >> 6)
+		    < (unsigned) calc->lcd.rowstride)
+			calc->z80.lastlcdwrite = calc->z80.clock;
+	}
+}
+
+byte x5_z80_rdmem(TilemCalc* calc, dword A)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+	return (*(calc->mem + pa));
+}
+
+dword x5_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword x5_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		if (calc->mempagemap[i] == page) {
+			return ((i << 14) | (A & 0x3fff));
+		}
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/x5/x5_subcore.c b/tool/tilem-src/emu/x5/x5_subcore.c
new file mode 100644
index 0000000..d8a5232
--- /dev/null
+++ b/tool/tilem-src/emu/x5/x5_subcore.c
@@ -0,0 +1,58 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x5.h"
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+static const char* keynames[64] = {
+	"Down", "Left", "Right", "Up", 0, 0, 0, 0,
+	"Enter", "Add", "Sub", "Mul", "Div", "Power", "Clear", 0,
+	"Chs", "3", "6", "9", "RParen", "Tan", "Custom", 0,
+	"DecPnt", "2", "5", "8", "LParen", "Cos", "Prgm", "Del",
+	"0", "1", "4", "7", "EE", "Sin", "Stat", "XVar",
+	"On", "Store", "Comma", "Square", "Ln", "Log", "Graph", "Alpha",
+	"F5", "F4", "F3", "F2", "F1", "2nd", "Exit", "More",
+	0, 0, 0, 0, 0, 0, 0, 0};
+
+const TilemHardware hardware_ti85 = {
+	'5', "ti85", "TI-85",
+	TILEM_CALC_HAS_LINK,
+	128, 64, 8 * 0x4000, 0x8000, 0, 0x40,
+	0, NULL, 0,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	keynames,
+	x5_reset, NULL,
+	x5_z80_in, x5_z80_out,
+	x5_z80_wrmem, x5_z80_rdmem, x5_z80_rdmem, NULL,
+	x5_z80_ptimer, tilem_lcd_t6a43_get_data,
+	x5_mem_ltop, x5_mem_ptol };
+
diff --git a/tool/tilem-src/emu/x6/x6.h b/tool/tilem-src/emu/x6/x6.h
new file mode 100644
index 0000000..f55d3a9
--- /dev/null
+++ b/tool/tilem-src/emu/x6/x6.h
@@ -0,0 +1,49 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_X6_H
+#define _TILEM_X6_H
+
+enum {
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* mapping mode, timer control */
+	PORT5,			/* memory mapping bank A */
+	PORT6,			/* memory mapping bank B */
+	PORT7,			/* link port control */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES { "port3", "port4", "port5", "port6", "port7" }
+
+#define TIMER_INT (TILEM_NUM_SYS_TIMERS + 1)
+#define NUM_HW_TIMERS 1
+
+#define HW_TIMER_NAMES { "int" }
+
+void x6_reset(TilemCalc* calc);
+byte x6_z80_in(TilemCalc* calc, dword port);
+void x6_z80_out(TilemCalc* calc, dword port, byte value);
+void x6_z80_ptimer(TilemCalc* calc, int id);
+void x6_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte x6_z80_rdmem(TilemCalc* calc, dword addr);
+dword x6_mem_ltop(TilemCalc* calc, dword addr);
+dword x6_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/x6/x6_init.c b/tool/tilem-src/emu/x6/x6_init.c
new file mode 100644
index 0000000..27e6417
--- /dev/null
+++ b/tool/tilem-src/emu/x6/x6_init.c
@@ -0,0 +1,49 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x6.h"
+
+void x6_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x56;
+	calc->hwregs[PORT5] = 0x00;
+	calc->hwregs[PORT6] = 0x00;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x00;
+	calc->mempagemap[2] = 0x00;
+	calc->mempagemap[3] = 0x10 + 0x00;
+
+	calc->lcd.addr = 0x3c00;
+
+	tilem_z80_set_speed(calc, 6000);
+
+	/* FIXME: measure actual frequency */
+	tilem_z80_set_timer(calc, TIMER_INT, 1000, 5000, 1);
+}
diff --git a/tool/tilem-src/emu/x6/x6_io.c b/tool/tilem-src/emu/x6/x6_io.c
new file mode 100644
index 0000000..92a0d49
--- /dev/null
+++ b/tool/tilem-src/emu/x6/x6_io.c
@@ -0,0 +1,183 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x6.h"
+
+byte x6_z80_in(TilemCalc* calc, dword port)
+{
+	byte v, b;
+	
+	switch(port&0xff) {
+	case 0x01:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x03:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+
+		return(v);
+
+	case 0x04:
+		return(0x01);
+
+	case 0x05:
+		return(calc->hwregs[PORT5]);
+
+	case 0x06:
+		return(calc->hwregs[PORT6]);
+
+	case 0x07:
+		v = tilem_linkport_get_lines(calc);
+		b = (calc->hwregs[PORT7] >> 4) | 0xf0;
+		return (calc->hwregs[PORT7] & b) | (v & ~b);
+	}
+
+	tilem_warning(calc, "Input from port %x", port);
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB;
+
+	if (calc->hwregs[PORT5] & 0x40) {
+		pageA = (0x10 | (calc->hwregs[PORT5] & 7));
+	}
+	else {
+		pageA = (calc->hwregs[PORT5] & 0x0f);
+	}
+
+	if (calc->hwregs[PORT6] & 0x40) {
+		pageB = (0x10 | (calc->hwregs[PORT6] & 7));
+	}
+	else {
+		pageB = (calc->hwregs[PORT6] & 0x0f);
+	}
+
+	calc->mempagemap[1] = pageA;
+	calc->mempagemap[2] = pageB;
+	calc->mempagemap[3] = 0x10;
+}
+
+void x6_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	switch(port&0xff) {
+	case 0x00:
+		calc->lcd.addr = ((value & 0x3f) << 8);
+		calc->z80.lastlcdwrite = calc->z80.clock;
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x02:
+		/* contrast */
+		calc->lcd.contrast = 16 + (value & 0x1f);
+		break;
+
+	case 0x03:
+		/* Lcd Power */
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02)) {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+		}
+
+		calc->hwregs[PORT3] = value;
+		calc->lcd.active = calc->poweronhalt = ((value & 8) >> 3);
+		break;
+	
+	case 0x04:
+		/* LCD control */
+		calc->hwregs[PORT4] = value;
+
+		switch (value & 0x18) {
+		case 0x00:
+			calc->lcd.rowstride = 10;
+			break;
+
+		case 0x08:
+			calc->lcd.rowstride = 12;
+			break;
+
+		case 0x10:
+			calc->lcd.rowstride = 16;
+			break;
+
+		case 0x18:
+			calc->lcd.rowstride = 20;
+			break;
+		}
+		calc->z80.lastlcdwrite = calc->z80.clock;
+		break;
+
+	case 0x05:
+		calc->hwregs[PORT5] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x06:
+		calc->hwregs[PORT6] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x07:
+		/* Link */
+		calc->hwregs[PORT7] = value;
+
+		value = (((value >> 6) & (value >> 2))
+			 | ((value >> 4) & ~value));
+
+		tilem_linkport_set_lines(calc, value);
+		break;
+	}
+
+	return;
+}
+
+void x6_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/x6/x6_memory.c b/tool/tilem-src/emu/x6/x6_memory.c
new file mode 100644
index 0000000..f771a2f
--- /dev/null
+++ b/tool/tilem-src/emu/x6/x6_memory.c
@@ -0,0 +1,68 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x6.h"
+
+void x6_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+
+	if (pa >= 0x40000) {
+		*(calc->mem + pa) = v;
+
+		if (((pa - 0x40000 - calc->lcd.addr) >> 6)
+		    < (unsigned) calc->lcd.rowstride)
+			calc->z80.lastlcdwrite = calc->z80.clock;
+	}
+}
+
+byte x6_z80_rdmem(TilemCalc* calc, dword A)
+{
+	dword pa = 0x4000 * calc->mempagemap[(A)>>14] + (A & 0x3FFF);
+	return (*(calc->mem + pa));
+}
+
+dword x6_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword x6_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		if (calc->mempagemap[i] == page) {
+			return ((i << 14) | (A & 0x3fff));
+		}
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/x6/x6_subcore.c b/tool/tilem-src/emu/x6/x6_subcore.c
new file mode 100644
index 0000000..2a69455
--- /dev/null
+++ b/tool/tilem-src/emu/x6/x6_subcore.c
@@ -0,0 +1,57 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x6.h"
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+static const char* keynames[64] = {
+	"Down", "Left", "Right", "Up", 0, 0, 0, 0,
+	"Enter", "Add", "Sub", "Mul", "Div", "Power", "Clear", 0,
+	"Chs", "3", "6", "9", "RParen", "Tan", "Custom", 0,
+	"DecPnt", "2", "5", "8", "LParen", "Cos", "Prgm", "Del",
+	"0", "1", "4", "7", "EE", "Sin", "Table", "XVar",
+	"On", "Store", "Comma", "Square", "Ln", "Log", "Graph", "Alpha",
+	"F5", "F4", "F3", "F2", "F1", "2nd", "Exit", "More",
+	0, 0, 0, 0, 0, 0, 0, 0};
+
+const TilemHardware hardware_ti86 = {
+	'6', "ti86", "TI-86",
+	TILEM_CALC_HAS_LINK,
+	128, 64, 0x10 * 0x4000, 0x08 * 0x4000, 0, 0x40,
+	0, NULL, 0,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	keynames,
+	x6_reset, NULL,
+	x6_z80_in, x6_z80_out,
+	x6_z80_wrmem, x6_z80_rdmem, x6_z80_rdmem, NULL,
+	x6_z80_ptimer, tilem_lcd_t6a43_get_data,
+	x6_mem_ltop, x6_mem_ptol };
diff --git a/tool/tilem-src/emu/x7/x7.h b/tool/tilem-src/emu/x7/x7.h
new file mode 100644
index 0000000..53543ba
--- /dev/null
+++ b/tool/tilem-src/emu/x7/x7.h
@@ -0,0 +1,56 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_X7_H
+#define _TILEM_X7_H
+
+enum {
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* interrupt timer speed */
+	PORT6,			/* memory mapping bank A */
+	PORT7,			/* memory mapping bank B */
+	NOEXEC,			/* no-exec sector map */
+	PROTECTSTATE,		/* port protection state */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES					\
+	{ "port3", "port4", "port6", "port7", "noexec", \
+	  "protectstate" }
+
+#define TIMER_INT1 (TILEM_NUM_SYS_TIMERS + 1)
+#define TIMER_INT2A (TILEM_NUM_SYS_TIMERS + 2)
+#define TIMER_INT2B (TILEM_NUM_SYS_TIMERS + 3)
+#define NUM_HW_TIMERS 3
+
+#define HW_TIMER_NAMES { "int1", "int2a", "int2b" }
+
+void x7_reset(TilemCalc* calc);
+void x7_stateloaded(TilemCalc* calc, int savtype);
+byte x7_z80_in(TilemCalc* calc, dword port);
+void x7_z80_out(TilemCalc* calc, dword port, byte value);
+void x7_z80_ptimer(TilemCalc* calc, int id);
+void x7_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte x7_z80_rdmem(TilemCalc* calc, dword addr);
+byte x7_z80_rdmem_m1(TilemCalc* calc, dword addr);
+dword x7_mem_ltop(TilemCalc* calc, dword addr);
+dword x7_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/x7/x7_init.c b/tool/tilem-src/emu/x7/x7_init.c
new file mode 100644
index 0000000..1d2a62e
--- /dev/null
+++ b/tool/tilem-src/emu/x7/x7_init.c
@@ -0,0 +1,59 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x7.h"
+
+void x7_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x77;
+	calc->hwregs[PORT6] = 0x1F;
+	calc->hwregs[PORT7] = 0x1F;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x1E;
+	calc->mempagemap[2] = 0x1F;
+	calc->mempagemap[3] = 0x1F;
+
+	calc->z80.r.pc.d = 0x8000;
+
+	calc->hwregs[NOEXEC] = 0;
+	calc->hwregs[PROTECTSTATE] = 0;
+
+	tilem_z80_set_speed(calc, 6000);
+
+	tilem_z80_set_timer(calc, TIMER_INT1, 1600, 8474, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2A, 1300, 8474, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2B, 1000, 8474, 1);
+}
+
+void x7_stateloaded(TilemCalc* calc, int savtype TILEM_ATTR_UNUSED)
+{
+	tilem_calc_fix_certificate(calc, calc->mem + (0x1E * 0x4000L),
+	                           0x08, 0x13, 0x1f18);
+}
diff --git a/tool/tilem-src/emu/x7/x7_io.c b/tool/tilem-src/emu/x7/x7_io.c
new file mode 100644
index 0000000..aaca352
--- /dev/null
+++ b/tool/tilem-src/emu/x7/x7_io.c
@@ -0,0 +1,247 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x7.h"
+
+byte x7_z80_in(TilemCalc* calc, dword port)
+{
+	/* FIXME: measure actual levels */
+	static const byte battlevel[4] = { 33, 39, 36, 43 };
+	byte v;
+
+	/* FIXME: confirm that mirror ports are the same as on 83+
+	   - and figure out what, if anything, port 5 does */
+
+	switch(port&0x1f) {
+	case 0x00:
+	case 0x08:
+		v = tilem_linkport_get_lines(calc);
+		v |= (calc->linkport.lines << 4);
+		return(v);
+
+	case 0x01:
+	case 0x09:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+	case 0x0A:
+		v = battlevel[calc->hwregs[PORT4] >> 6];
+		return(calc->battery >= v ? 0x05 : 0x04);
+
+	case 0x03:
+	case 0x0B:
+		return(calc->hwregs[PORT3]);
+
+	case 0x04:
+	case 0x0C:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER2)
+			v |= 0x04;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ACTIVE)
+			v |= 0x10;
+
+		return(v);
+
+	case 0x06:
+	case 0x0E:
+		return(calc->hwregs[PORT6]);
+
+	case 0x07:
+	case 0x0F:
+		return(calc->hwregs[PORT7]);
+
+	case 0x10:
+	case 0x12:
+	case 0x18:
+	case 0x1A:
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+	case 0x13:
+	case 0x19:
+	case 0x1B:
+		return(tilem_lcd_t6a04_read(calc));
+	}
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB;
+
+	if (calc->hwregs[PORT6] & 0x40)
+		pageA = (0x20 | (calc->hwregs[PORT6] & 1));
+	else
+		pageA = (calc->hwregs[PORT6] & 0x1f);
+
+	if (calc->hwregs[PORT7] & 0x40)
+		pageB = (0x20 | (calc->hwregs[PORT7] & 1));
+	else
+		pageB = (calc->hwregs[PORT7] & 0x1f);
+
+	if (calc->hwregs[PORT4] & 1) {
+		/* FIXME: confirm this behavior (83+-like rather than
+		   83-like) */
+		calc->mempagemap[1] = (pageA & ~1);
+		calc->mempagemap[2] = pageA;
+		calc->mempagemap[3] = pageB;
+	}
+	else {
+		calc->mempagemap[1] = pageA;
+		calc->mempagemap[2] = pageB;
+		calc->mempagemap[3] = 0x20;
+	}
+}
+
+void x7_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	static const int tmrvalues[4] = { 1786, 4032, 5882, 8474 };
+	int t;
+	unsigned int mode;
+
+	switch(port&0x1f) {
+	case 0x00:
+	case 0x08:
+		tilem_linkport_set_lines(calc, value);
+		break;
+
+	case 0x01:
+	case 0x09:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x03:
+	case 0x0B:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+
+		if (!(value & 0x04))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER2;
+
+		mode = calc->linkport.mode;
+		if (value & 0x10)
+			mode |= TILEM_LINK_MODE_INT_ON_ACTIVE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ACTIVE;
+
+		tilem_linkport_set_mode(calc, mode);
+
+		calc->poweronhalt = ((value & 8) >> 3);
+		calc->hwregs[PORT3] = value;
+		break;
+
+	case 0x04:
+	case 0x0C:
+		calc->hwregs[PORT4] = value;
+
+		t = tmrvalues[(value & 6) >> 1];
+		tilem_z80_set_timer_period(calc, TIMER_INT1, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2A, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2B, t);
+
+		setup_mapping(calc);
+		break;
+
+	case 0x06:
+	case 0x0E:
+		calc->hwregs[PORT6] = value & 0x7f;
+		setup_mapping(calc);
+		break;
+
+	case 0x07:
+	case 0x0F:
+		calc->hwregs[PORT7] = value & 0x7f;
+		setup_mapping(calc);
+		break;
+
+	case 0x10:
+	case 0x12:
+	case 0x18:
+	case 0x1A:
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+	case 0x13:
+	case 0x19:
+	case 0x1B:
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+
+	case 0x14:
+	case 0x15:
+		if (calc->hwregs[PROTECTSTATE] == 7) {
+			if (value & 1)
+				tilem_message(calc, "Flash unlocked");
+			else
+				tilem_message(calc, "Flash locked");
+			calc->flash.unlock = value&1;
+		}
+		break;
+
+	case 0x16:
+	case 0x17:
+		if (calc->flash.unlock && calc->hwregs[PROTECTSTATE] == 7) {
+			tilem_message(calc, "No-exec mask set to %x", value);
+			calc->hwregs[NOEXEC] = ((value & 0x0f) << 2);
+		}
+		break;
+	}
+
+	return;
+}
+
+void x7_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT1:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+
+	case TIMER_INT2A:
+	case TIMER_INT2B:
+		if (calc->hwregs[PORT3] & 0x04)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER2;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/x7/x7_memory.c b/tool/tilem-src/emu/x7/x7_memory.c
new file mode 100644
index 0000000..d39dc0a
--- /dev/null
+++ b/tool/tilem-src/emu/x7/x7_memory.c
@@ -0,0 +1,132 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x7.h"
+
+void x7_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	unsigned long pa;
+
+	pa = (A & 0x3FFF) + 0x4000*calc->mempagemap[(A)>>14];
+
+	if (pa<0x80000)
+		tilem_flash_write_byte(calc, pa, v);
+
+	else if (pa < 0x88000)
+		*(calc->mem+pa) = v;
+}
+
+static inline byte readbyte(TilemCalc* calc, dword pa)
+{
+	static const byte protectbytes[6] = {0x00,0x00,0xed,0x56,0xf3,0xd3};
+	int state = calc->hwregs[PROTECTSTATE];
+	byte value;
+
+	if (pa < 0x80000 && (calc->flash.state || calc->flash.busy))
+		value = tilem_flash_read_byte(calc, pa);
+	else
+		value = *(calc->mem + pa);
+
+	if (pa < 0x70000 || pa >= 0x80000)
+		calc->hwregs[PROTECTSTATE] = 0;
+	else if (state == 6)
+		calc->hwregs[PROTECTSTATE] = 7;
+	else if (state < 6 && value == protectbytes[state])
+		calc->hwregs[PROTECTSTATE] = state + 1;
+	else
+		calc->hwregs[PROTECTSTATE] = 0;
+
+	return (value);
+}
+
+byte x7_z80_rdmem(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+	pa = 0x4000 * page + (A & 0x3FFF);
+ 
+	if (TILEM_UNLIKELY(page == 0x1E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	value = readbyte(calc, pa);
+	return(value);
+}
+
+byte x7_z80_rdmem_m1(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+	pa = 0x4000 * page + (A & 0x3FFF);
+
+	if (TILEM_UNLIKELY(calc->hwregs[NOEXEC] & (1 << (page % 4)))) {
+		tilem_warning(calc, "Executing in restricted Flash area");
+		tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+	}
+
+	if (TILEM_UNLIKELY(page == 0x1E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		tilem_z80_exception(calc, TILEM_EXC_RAM_EXEC);
+	}
+
+	value = readbyte(calc, pa);
+
+	if (TILEM_UNLIKELY(value == 0xff && A == 0x0038)) {
+		tilem_warning(calc, "No OS installed");
+		tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+	}
+
+	return (value);
+}
+
+dword x7_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword x7_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		if (calc->mempagemap[i] == page) {
+			return ((i << 14) | (A & 0x3fff));
+		}
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/x7/x7_subcore.c b/tool/tilem-src/emu/x7/x7_subcore.c
new file mode 100644
index 0000000..69e480a
--- /dev/null
+++ b/tool/tilem-src/emu/x7/x7_subcore.c
@@ -0,0 +1,72 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "x7.h"
+
+static const TilemFlashSector flashsectors[] = {
+	{0x000000, 0x10000, 0},
+	{0x010000, 0x10000, 0},
+	{0x020000, 0x10000, 0},
+	{0x030000, 0x10000, 0},
+	{0x040000, 0x10000, 0},
+	{0x050000, 0x10000, 0},
+	{0x060000, 0x10000, 0},
+	{0x070000, 0x08000, 0},
+	{0x078000, 0x02000, 0},
+	{0x07A000, 0x02000, 0},
+	{0x07C000, 0x04000, 1}};
+
+#define NUM_FLASH_SECTORS (sizeof(flashsectors) / sizeof(TilemFlashSector))
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+static const char* keynames[64] = {
+	"Down", "Left", "Right", "Up", 0, 0, 0, 0,
+	"Enter", "Add", "Sub", "Mul", "Div", "Const", "Clear", 0,
+	"Chs", "3", "6", "9", "RParen", "MixSimp", "AppsMenu", 0,
+	"DecPnt", "2", "5", "8", "LParen", "FracDec", "Prgm", "StatEd",
+	"0", "1", "4", "7", "Percent", "FracSlash", "Expon", "Draw",
+	"On", "Store", "Comma", "VarX", "Simp", "Unit", "Square", "Math",
+	"Graph", "Trace", "Zoom", "Window", "YEqu", "2nd", "Mode", "Del",
+	0, 0, 0, 0, 0, 0, 0, 0};
+
+TilemHardware hardware_ti73 = {
+	'7', "ti73", "TI-73",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_T6A04 | TILEM_CALC_HAS_FLASH),
+	96, 64, 32 * 0x4000, 2 * 0x4000, 15 * 64, 0x40,
+	NUM_FLASH_SECTORS, flashsectors, 0,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	keynames,
+	x7_reset, x7_stateloaded,
+	x7_z80_in, x7_z80_out,
+	x7_z80_wrmem, x7_z80_rdmem, x7_z80_rdmem_m1, NULL,
+	x7_z80_ptimer, tilem_lcd_t6a04_get_data,
+	x7_mem_ltop, x7_mem_ptol };
diff --git a/tool/tilem-src/emu/xn/xn.h b/tool/tilem-src/emu/xn/xn.h
new file mode 100644
index 0000000..b5e4841
--- /dev/null
+++ b/tool/tilem-src/emu/xn/xn.h
@@ -0,0 +1,105 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_XN_H
+#define _TILEM_XN_H
+
+enum {
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* interrupt timer speed */
+	PORT5,			/* memory mapping bank C */
+	PORT6,			/* memory mapping bank A */
+	PORT7,			/* memory mapping bank B */
+	PORT8,			/* link assist mode flags */
+	PORT9,			/* unknown (link assist settings?) */
+	PORTA,			/* unknown (timeout value?) */
+	PORTB,			/* unknown (timeout value?) */
+	PORTC,			/* unknown (timeout value?) */
+	PORTD,			/* unknown */
+	PORTE,			/* unknown */
+	PORTF,			/* unknown */
+
+	PORT20,			/* CPU speed control */
+	PORT21,			/* hardware type / RAM no-exec control */
+	PORT22,			/* Flash no-exec lower limit */
+	PORT23,			/* Flash no-exec upper limit */
+	PORT25,			/* unknown */
+	PORT26,			/* unknown */
+	PORT27,			/* bank C forced-page-0 limit */
+	PORT28,			/* bank B forced-page-1 limit */
+	PORT29,			/* LCD port delay (6 MHz) */
+	PORT2A,			/* LCD port delay (mode 1) */
+	PORT2B,			/* LCD port delay (mode 2) */
+	PORT2C,			/* LCD port delay (mode 3) */
+	PORT2D,			/* unknown */
+	PORT2E,			/* memory delay */
+	PORT2F,			/* Duration of LCD wait timer */
+
+	CLOCK_MODE,		/* clock mode */
+	CLOCK_INPUT,		/* clock input register */
+	CLOCK_DIFF,		/* clock value minus actual time */
+
+	RAM_READ_DELAY,
+	RAM_WRITE_DELAY,
+	RAM_EXEC_DELAY,
+	FLASH_READ_DELAY,
+	FLASH_WRITE_DELAY,
+	FLASH_EXEC_DELAY,
+	LCD_PORT_DELAY,
+	NO_EXEC_RAM,
+
+	LCD_WAIT,		/* LCD wait timer active */
+	PROTECTSTATE,		/* port protection state */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES \
+	{ "port3", "port4", "port5", "port6", "port7", "port8", "port9", \
+	  "portA", "portB", "portC", "portD", "portE", "portF", "port20", \
+	  "port21", "port22", "port23", "port25", "port26", "port27", \
+	  "port28", "port29", "port2A", "port2B", "port2C", "port2D", \
+	  "port2E", "port2F", "clock_mode", "clock_input", "clock_diff", \
+	  "ram_read_delay", "ram_write_delay", "ram_exec_delay", \
+	  "flash_read_delay", "flash_write_delay", "flash_exec_delay", \
+	  "lcd_port_delay", "no_exec_ram", "lcd_wait", "protectstate" }
+
+#define TIMER_INT1 (TILEM_NUM_SYS_TIMERS + 1)
+#define TIMER_INT2A (TILEM_NUM_SYS_TIMERS + 2)
+#define TIMER_INT2B (TILEM_NUM_SYS_TIMERS + 3)
+#define TIMER_LCD_WAIT (TILEM_NUM_SYS_TIMERS + 4)
+#define TIMER_FREEZE_LINK_PORT (TILEM_NUM_SYS_TIMERS + 5)
+#define NUM_HW_TIMERS 5
+
+#define HW_TIMER_NAMES { "int1", "int2a", "int2b", "lcd_wait", \
+			 "freeze_link_port" }
+
+void xn_reset(TilemCalc* calc);
+void xn_stateloaded(TilemCalc* calc, int savtype);
+byte xn_z80_in(TilemCalc* calc, dword port);
+void xn_z80_out(TilemCalc* calc, dword port, byte value);
+void xn_z80_instr(TilemCalc* calc, dword opcode);
+void xn_z80_ptimer(TilemCalc* calc, int id);
+void xn_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte xn_z80_rdmem(TilemCalc* calc, dword addr);
+byte xn_z80_rdmem_m1(TilemCalc* calc, dword addr);
+dword xn_mem_ltop(TilemCalc* calc, dword addr);
+dword xn_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/xn/xn_init.c b/tool/tilem-src/emu/xn/xn_init.c
new file mode 100644
index 0000000..84e715e
--- /dev/null
+++ b/tool/tilem-src/emu/xn/xn_init.c
@@ -0,0 +1,88 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xn.h"
+
+void xn_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x07;
+	calc->hwregs[PORT6] = 0x7F;
+	calc->hwregs[PORT7] = 0x7F;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x7E;
+	calc->mempagemap[2] = 0x7F;
+	calc->mempagemap[3] = 0x7F;
+
+	calc->z80.r.pc.d = 0x8000;
+
+	calc->hwregs[PORT8] = 0x80;
+
+	calc->hwregs[PORT20] = 0;
+	calc->hwregs[PORT21] = 0;
+	calc->hwregs[PORT22] = 0x08;
+	calc->hwregs[PORT23] = 0x69;
+	calc->hwregs[PORT25] = 0x10;
+	calc->hwregs[PORT26] = 0x20;
+	calc->hwregs[PORT27] = 0;
+	calc->hwregs[PORT28] = 0;
+	calc->hwregs[PORT29] = 0x14;
+	calc->hwregs[PORT2A] = 0x27;
+	calc->hwregs[PORT2B] = 0x2F;
+	calc->hwregs[PORT2C] = 0x3B;
+	calc->hwregs[PORT2D] = 0x01;
+	calc->hwregs[PORT2E] = 0x44;
+	calc->hwregs[PORT2F] = 0x4A;
+
+	calc->hwregs[FLASH_READ_DELAY] = 0;
+	calc->hwregs[FLASH_WRITE_DELAY] = 0;
+	calc->hwregs[FLASH_EXEC_DELAY] = 0;
+	calc->hwregs[RAM_READ_DELAY] = 0;
+	calc->hwregs[RAM_WRITE_DELAY] = 0;
+	calc->hwregs[RAM_EXEC_DELAY] = 0;
+	calc->hwregs[LCD_PORT_DELAY] = 5;
+	calc->hwregs[NO_EXEC_RAM] = 0x5555;
+
+	calc->hwregs[PROTECTSTATE] = 0;
+
+	tilem_z80_set_speed(calc, 6000);
+	calc->z80.emuflags |= TILEM_Z80_RESET_UNDOCUMENTED;
+
+	calc->lcd.emuflags &= ~TILEM_LCD_REQUIRE_DELAY;
+
+	tilem_z80_set_timer(calc, TIMER_INT1, 1600, 9277, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2A, 1300, 9277, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2B, 1000, 9277, 1);
+}
+
+void xn_stateloaded(TilemCalc* calc, int savtype TILEM_ATTR_UNUSED)
+{
+	tilem_calc_fix_certificate(calc, calc->mem + (0x7E * 0x4000L),
+	                           0x69, 0x0c, 0x1e50);
+}
diff --git a/tool/tilem-src/emu/xn/xn_io.c b/tool/tilem-src/emu/xn/xn_io.c
new file mode 100644
index 0000000..7b4c85e
--- /dev/null
+++ b/tool/tilem-src/emu/xn/xn_io.c
@@ -0,0 +1,881 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <time.h>
+#include <tilem.h>
+
+#include "xn.h"
+
+static void set_lcd_wait_timer(TilemCalc* calc)
+{
+	static const int delaytime[8] = { 48, 112, 176, 240,
+					  304, 368, 432, 496 };
+	int i;
+
+	switch (calc->hwregs[PORT20] & 3) {
+	case 0:
+		return;
+	case 1:
+		i = (calc->hwregs[PORT2F] & 3);
+		break;
+	case 2:
+		i = ((calc->hwregs[PORT2F] >> 2) & 7);
+		break;
+	default:
+		i = ((calc->hwregs[PORT2F] >> 5) & 7);
+		break;
+	}
+
+	tilem_z80_set_timer(calc, TIMER_LCD_WAIT, delaytime[i], 0, 0);
+	calc->hwregs[LCD_WAIT] = 1;
+}
+
+byte xn_z80_in(TilemCalc* calc, dword port)
+{
+	/* FIXME: measure actual levels */
+	static const byte battlevel[4] = { 33, 39, 36, 43 };
+	byte v;
+	unsigned int f;
+	time_t curtime;
+
+	switch(port&0xff) {
+	case 0x00:
+		if (tilem_z80_timer_running(calc, TIMER_FREEZE_LINK_PORT))
+			return(3);
+
+		v = tilem_linkport_get_lines(calc);
+		v |= (calc->linkport.lines << 4);
+		return(v);
+
+	case 0x01:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+		v = battlevel[calc->hwregs[PORT4] >> 6];
+		return ((calc->battery >= v ? 0xe1 : 0xe0)
+			| (calc->hwregs[LCD_WAIT] ? 0 : 2)
+			| (calc->flash.unlock << 2));
+
+	case 0x03:
+		return(calc->hwregs[PORT3]);
+
+	case 0x04:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER2)
+			v |= 0x04;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ACTIVE)
+			v |= 0x10;
+
+		if (calc->usertimers[0].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x20;
+		if (calc->usertimers[1].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x40;
+		if (calc->usertimers[2].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x80;
+
+		return(v);
+
+	case 0x05:
+		return(calc->hwregs[PORT5] & 0x0f);
+
+	case 0x06:
+		return(calc->hwregs[PORT6]);
+
+	case 0x07:
+		return(calc->hwregs[PORT7]);
+
+	case 0x08:
+		return(calc->hwregs[PORT8]);
+
+	case 0x09:
+		f = tilem_linkport_get_assist_flags(calc);
+
+		if (f & (TILEM_LINK_ASSIST_READ_BUSY
+			 | TILEM_LINK_ASSIST_WRITE_BUSY))
+			v = 0x00;
+		else
+			v = 0x20;
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_READ)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_IDLE)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ERROR)
+			v |= 0x04;
+		if (f & TILEM_LINK_ASSIST_READ_BUSY)
+			v |= 0x08;
+		if (f & TILEM_LINK_ASSIST_READ_BYTE)
+			v |= 0x10;
+		if (f & (TILEM_LINK_ASSIST_READ_ERROR
+			 | TILEM_LINK_ASSIST_WRITE_ERROR))
+			v |= 0x40;
+		if (f & TILEM_LINK_ASSIST_WRITE_BUSY)
+			v |= 0x80;
+
+		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_ERROR;
+
+		return(v);
+
+	case 0x0A:
+		v = calc->linkport.assistlastbyte;
+		tilem_linkport_read_byte(calc);
+		return(v);
+
+	case 0x0E:
+		return(calc->hwregs[PORTE] & 3);
+
+	case 0x0F:
+		return(calc->hwregs[PORTF] & 3);
+
+	case 0x10:
+	case 0x12:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+	case 0x13:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		return(tilem_lcd_t6a04_read(calc));
+
+	case 0x15:
+		return(0x45);	/* ??? */
+
+	case 0x1C:
+		return(tilem_md5_assist_get_value(calc));
+
+	case 0x1D:
+		return(tilem_md5_assist_get_value(calc) >> 8);
+
+	case 0x1E:
+		return(tilem_md5_assist_get_value(calc) >> 16);
+
+	case 0x1F:
+		return(tilem_md5_assist_get_value(calc) >> 24);
+
+	case 0x20:
+		return(calc->hwregs[PORT20] & 3);
+
+	case 0x21:
+		return(calc->hwregs[PORT21] & 0x33);
+
+	case 0x22:
+		return(calc->hwregs[PORT22]);
+
+	case 0x23:
+		return(calc->hwregs[PORT23]);
+
+	case 0x25:
+		return(calc->hwregs[PORT25]);
+
+	case 0x26:
+		return(calc->hwregs[PORT26]);
+
+	case 0x27:
+		return(calc->hwregs[PORT27]);
+
+	case 0x28:
+		return(calc->hwregs[PORT28]);
+
+	case 0x29:
+		return(calc->hwregs[PORT29]);
+
+	case 0x2A:
+		return(calc->hwregs[PORT2A]);
+
+	case 0x2B:
+		return(calc->hwregs[PORT2B]);
+
+	case 0x2C:
+		return(calc->hwregs[PORT2C]);
+
+	case 0x2D:
+		return(calc->hwregs[PORT2D] & 3);
+
+	case 0x2E:
+		return(calc->hwregs[PORT2E]);
+
+	case 0x2F:
+		return(calc->hwregs[PORT2F]);
+
+	case 0x30:
+		return(calc->usertimers[0].frequency);
+	case 0x31:
+		return(calc->usertimers[0].status);
+	case 0x32:
+		return(tilem_user_timer_get_value(calc, 0));
+
+	case 0x33:
+		return(calc->usertimers[1].frequency);
+	case 0x34:
+		return(calc->usertimers[1].status);
+	case 0x35:
+		return(tilem_user_timer_get_value(calc, 1));
+
+	case 0x36:
+		return(calc->usertimers[2].frequency);
+	case 0x37:
+		return(calc->usertimers[2].status);
+	case 0x38:
+		return(tilem_user_timer_get_value(calc, 2));
+
+	case 0x39:
+		return(0xf0);	/* ??? */
+
+	case 0x40:
+		return calc->hwregs[CLOCK_MODE];
+
+	case 0x41:	
+		return calc->hwregs[CLOCK_INPUT]&0xff;
+
+	case 0x42:
+		return (calc->hwregs[CLOCK_INPUT]>>8)&0xff;
+
+	case 0x43:
+		return (calc->hwregs[CLOCK_INPUT]>>16)&0xff;
+
+	case 0x44:
+		return (calc->hwregs[CLOCK_INPUT]>>24)&0xff;
+
+	case 0x45:
+	case 0x46:
+	case 0x47:
+	case 0x48:
+		if (calc->hwregs[CLOCK_MODE] & 1) {
+			time(&curtime);
+		}
+		else {
+			curtime = 0;
+		}
+		curtime += calc->hwregs[CLOCK_DIFF];
+		return (curtime >> ((port - 0x45) * 8));
+
+	case 0x4C:
+		return(0x22);
+
+	case 0x4D:
+		/* USB port - not emulated, calculator should
+		   recognize that the USB cable is
+		   disconnected.
+
+		   Thanks go to Dan Englender for these
+		   values. */
+
+		return(0xA5);
+
+	case 0x55:
+		return(0x1F);
+
+	case 0x56:
+		return(0x00);
+
+	case 0x57:
+		return(0x50);
+
+	case 0x0B:
+	case 0x0C:
+	case 0x0D:
+	case 0x14:
+	case 0x16:
+	case 0x17:
+	case 0x18:
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+		return(0);
+	}
+
+	tilem_warning(calc, "Input from port %x", port);
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB, pageC;
+
+	if (calc->hwregs[PORT6] & 0x80)
+		pageA = (0x80 | (calc->hwregs[PORT6] & 7));
+	else
+		pageA = (calc->hwregs[PORT6] & 0x7f);
+
+	if (calc->hwregs[PORT7] & 0x80)
+		pageB = (0x80 | (calc->hwregs[PORT7] & 7));
+	else
+		pageB = (calc->hwregs[PORT7] & 0x7f);
+
+	pageC = (0x80 | (calc->hwregs[PORT5] & 7));
+
+	if (calc->hwregs[PORT4] & 1) {
+		calc->mempagemap[1] = (pageA & ~1);
+		calc->mempagemap[2] = (pageA | 1);
+		calc->mempagemap[3] = pageB;
+	}
+	else {
+		calc->mempagemap[1] = pageA;
+		calc->mempagemap[2] = pageB;
+		calc->mempagemap[3] = pageC;
+	}
+}
+
+static void setup_clockdelays(TilemCalc* calc)
+{
+	byte lcdport = calc->hwregs[PORT29 + (calc->hwregs[PORT20] & 3)];
+	byte memport = calc->hwregs[PORT2E];
+
+	if (!(lcdport & 1))
+		memport &= ~0x07;
+	if (!(lcdport & 2))
+		memport &= ~0x70;
+
+	calc->hwregs[FLASH_EXEC_DELAY] = (memport & 1);
+	calc->hwregs[FLASH_READ_DELAY] = ((memport >> 1) & 1);
+	calc->hwregs[FLASH_WRITE_DELAY] = ((memport >> 2) & 1);
+
+	calc->hwregs[RAM_EXEC_DELAY] = ((memport >> 4) & 1);
+	calc->hwregs[RAM_READ_DELAY] = ((memport >> 5) & 1);
+	calc->hwregs[RAM_WRITE_DELAY] = ((memport >> 6) & 1);
+
+	calc->hwregs[LCD_PORT_DELAY] = (lcdport >> 2);
+}
+
+void xn_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	static const int tmrvalues[4] = { 1953, 4395, 6836, 9277 };
+	int t, r;
+	unsigned int mode;
+	time_t curtime;
+
+	switch(port&0xff) {
+	case 0x00:
+		if (value == 0
+		    && calc->linkport.lines != 0
+		    && calc->linkport.extlines == 0) {
+			/* Kludge to work around TI's broken
+			   RecAByteIO implementation on 2.46+, which
+			   will fail if the sending device is too
+			   fast. */
+			tilem_z80_set_timer(calc, TIMER_FREEZE_LINK_PORT,
+			                    100, 0, 0);
+		}
+
+		tilem_linkport_set_lines(calc, value);
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x03:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+
+		if (!(value & 0x04))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER2;
+
+		if (value & 0x06) {
+			calc->usertimers[0].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[1].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[2].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+		}
+		else {
+			calc->usertimers[0].status |= TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[1].status |= TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[2].status |= TILEM_USER_TIMER_NO_HALT_INT;
+		}
+
+		mode = calc->linkport.mode;
+		if (value & 0x10)
+			mode |= TILEM_LINK_MODE_INT_ON_ACTIVE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ACTIVE;
+
+		tilem_linkport_set_mode(calc, mode);
+
+		calc->poweronhalt = ((value & 8) >> 3);
+		calc->hwregs[PORT3] = value;
+		break;
+
+
+	case 0x04:
+		calc->hwregs[PORT4] = value;
+
+		t = tmrvalues[(value & 6) >> 1];
+		tilem_z80_set_timer_period(calc, TIMER_INT1, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2A, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2B, t);
+
+		setup_mapping(calc);
+		break;
+	
+	case 0x05:
+		calc->hwregs[PORT5] = value & 0x0f;
+		setup_mapping(calc);
+		break;
+
+	case 0x06:
+		calc->hwregs[PORT6] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x07:
+		calc->hwregs[PORT7] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x08:
+		calc->hwregs[PORT8] = value;
+
+		mode = calc->linkport.mode;
+
+		if (value & 0x01)
+			mode |= TILEM_LINK_MODE_INT_ON_READ;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_READ;
+
+		if (value & 0x02)
+			mode |= TILEM_LINK_MODE_INT_ON_IDLE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_IDLE;
+
+		if (value & 0x04)
+			mode |= TILEM_LINK_MODE_INT_ON_ERROR;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ERROR;
+
+		if (value & 0x80)
+			mode &= ~TILEM_LINK_MODE_ASSIST;
+		else
+			mode |= TILEM_LINK_MODE_ASSIST;
+
+		tilem_linkport_set_mode(calc, mode);
+		break;
+
+	case 0x09:
+		calc->hwregs[PORT9] = value;
+		break;
+
+	case 0x0A:
+		calc->hwregs[PORTA] = value;
+		break;
+
+	case 0x0B:
+		calc->hwregs[PORTB] = value;
+		break;
+
+	case 0x0C:
+		calc->hwregs[PORTC] = value;
+		break;
+
+
+	case 0x0D:
+		if (!(calc->hwregs[PORT8] & 0x80))
+			tilem_linkport_write_byte(calc, value);
+		break;
+
+	case 0x0E:
+		calc->hwregs[PORTE] = value;
+		break;
+
+	case 0x0F:
+		calc->hwregs[PORTF] = value;
+		break;
+
+	case 0x10:
+	case 0x12:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+	case 0x13:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+
+	case 0x14:
+		if (calc->hwregs[PROTECTSTATE] == 7) {
+			/*
+			if (value & 1)
+				tilem_message(calc, "Flash unlocked");
+			else
+				tilem_message(calc, "Flash locked");
+			*/
+			calc->flash.unlock = value&1;
+		}
+		break;
+
+	case 0x18:
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+	case 0x1C:
+	case 0x1D:
+		r = (port & 0xff) - 0x18;
+		calc->md5assist.regs[r] >>= 8;
+		calc->md5assist.regs[r] |= (value << 24);
+		break;
+
+	case 0x1E:
+		calc->md5assist.shift = value & 0x1f;
+		break;
+
+	case 0x1F:
+		calc->md5assist.mode = value & 3;
+		break;
+
+	case 0x20:
+		calc->hwregs[PORT20] = value;
+
+		if (value & 3) {
+			tilem_z80_set_speed(calc, 15000);
+		}
+		else {
+			tilem_z80_set_speed(calc, 6000);
+		}
+
+		setup_clockdelays(calc);
+		break;
+
+	case 0x21:
+		if (calc->flash.unlock && calc->hwregs[PROTECTSTATE] == 7) {
+			calc->hwregs[PORT21] = value;
+
+			/* FIXME: these restrictions were tested on
+			   83+ SE; someone should confirm them for
+			   84+ */
+			switch (value & 0x30) {
+			case 0x00:
+				/* restrict pp. 0, 2, 4, 6, 8, A, C, E */
+				calc->hwregs[NO_EXEC_RAM] = 0x5555;
+				break;
+
+			case 0x10:
+				/* restrict pp. 0, 3, 4, 7, 8, B, C, F */
+				calc->hwregs[NO_EXEC_RAM] = 0x9999;
+				break;
+
+			case 0x20:
+				/* restrict pp. 0, 3-7, 8, B-F */
+				calc->hwregs[NO_EXEC_RAM] = 0xF9F9;
+				break;
+
+			case 0x30:
+				/* restrict pp. 0, 3-F */
+				calc->hwregs[NO_EXEC_RAM] = 0xFFF9;
+				break;
+			}
+		}
+		break;
+
+	case 0x22:
+		if (calc->flash.unlock && calc->hwregs[PROTECTSTATE] == 7) {
+			calc->hwregs[PORT22] = value;
+		}
+		break;
+
+	case 0x23:
+		if (calc->flash.unlock && calc->hwregs[PROTECTSTATE] == 7) {
+			calc->hwregs[PORT23] = value;
+		}
+		break;
+
+	case 0x25:
+		if (calc->flash.unlock && calc->hwregs[PROTECTSTATE] == 7) {
+			calc->hwregs[PORT25] = value;
+		}
+		break;
+
+	case 0x26:
+		if (calc->flash.unlock && calc->hwregs[PROTECTSTATE] == 7) {
+			calc->hwregs[PORT26] = value;
+		}
+		break;
+
+	case 0x27:
+		calc->hwregs[PORT27] = value;
+		break;
+
+	case 0x28:
+		calc->hwregs[PORT28] = value;
+		break;
+
+	case 0x29:
+		calc->hwregs[PORT29] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2A:
+		calc->hwregs[PORT2A] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2B:
+		calc->hwregs[PORT2B] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2C:
+		calc->hwregs[PORT2C] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2D:
+		calc->hwregs[PORT2D] = value;
+		break;
+
+	case 0x2E:
+		calc->hwregs[PORT2E] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2F:
+		calc->hwregs[PORT2F] = value;
+		break;
+
+	case 0x30:
+		tilem_user_timer_set_frequency(calc, 0, value);
+		break;
+	case 0x31:
+		tilem_user_timer_set_mode(calc, 0, value);
+		break;
+	case 0x32:
+		tilem_user_timer_start(calc, 0, value);
+		break;
+
+	case 0x33:
+		tilem_user_timer_set_frequency(calc, 1, value);
+		break;
+	case 0x34:
+		tilem_user_timer_set_mode(calc, 1, value);
+		break;
+	case 0x35:
+		tilem_user_timer_start(calc, 1, value);
+		break;
+
+	case 0x36:
+		tilem_user_timer_set_frequency(calc, 2, value);
+		break;
+	case 0x37:
+		tilem_user_timer_set_mode(calc, 2, value);
+		break;
+	case 0x38:
+		tilem_user_timer_start(calc, 2, value);
+		break;
+
+	case 0x40:
+		time(&curtime);
+
+		if ((calc->hwregs[CLOCK_MODE] & 1) != (value & 1)) {
+			if (value & 1)
+				calc->hwregs[CLOCK_DIFF] -= curtime;
+			else
+				calc->hwregs[CLOCK_DIFF] += curtime;
+		}
+
+		if (!(calc->hwregs[CLOCK_MODE] & 2) && (value & 2)) {
+			calc->hwregs[CLOCK_DIFF] = calc->hwregs[CLOCK_INPUT];
+			if (value & 1)
+				calc->hwregs[CLOCK_DIFF] -= curtime;
+		}
+		calc->hwregs[CLOCK_MODE] = value & 3;
+		break;
+
+	case 0x41:
+		calc->hwregs[CLOCK_INPUT] &= 0xffffff00;
+		calc->hwregs[CLOCK_INPUT] |= value;
+		break;
+
+	case 0x42:
+		calc->hwregs[CLOCK_INPUT] &= 0xffff00ff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 8);
+		break;
+
+	case 0x43:
+		calc->hwregs[CLOCK_INPUT] &= 0xff00ffff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 16);
+		break;
+
+	case 0x44:
+		calc->hwregs[CLOCK_INPUT] &= 0x00ffffff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 24);
+		break;
+	}
+
+	return;
+}
+
+void xn_z80_instr(TilemCalc* calc, dword opcode)
+{
+	dword pa;
+	byte l, h;
+
+	switch (opcode) {
+	case 0xeded:
+		/* emulator control instruction */
+		l = xn_z80_rdmem(calc, calc->z80.r.pc.d);
+		h = xn_z80_rdmem(calc, calc->z80.r.pc.d + 1);
+
+		calc->z80.r.pc.d += 2;
+
+		opcode = (l | (h << 8));
+		switch (opcode) {
+		case 0x1000: /* Power off */
+		case 0x1001: /* Power on */
+		case 0x1002: /* Prepare for power off */
+			break;
+
+		case 0x1003:
+			/* Disable Nspire keypad */
+			tilem_message(calc, "Keypad locked");
+			break;
+
+		case 0x1004:
+			/* Enable Nspire keypad */
+			tilem_message(calc, "Keypad unlocked");
+			break;
+
+		case 0x1005:
+			/* ??? */
+			break;
+
+		case 0x1008:
+			/* check if USB data waiting (?) */
+			calc->z80.r.af.d |= 0x40;
+			break;
+
+		case 0x100C:
+			/* ??? */
+			calc->z80.r.af.d &= ~0x40;
+			break;
+
+		case 0x100D:
+			/* check if USB link should be used (???) */
+			calc->z80.r.af.d &= ~0x40;
+			break;
+
+		case 0x100E:
+		case 0x100F:
+			/* ??? */
+			break;
+
+		case 0x101C:
+			/* disable USB device (???) */
+			break;
+
+		case 0x1020:
+			/* check for something USB-related */
+			calc->z80.r.af.d |= 0x40;
+			break;
+
+		case 0x1024:
+			/* check if USB data waiting */
+			calc->z80.r.af.d |= 0x40;
+			break;
+
+		case 0x1029:
+			/* check for something USB-related */
+			calc->z80.r.af.d |= 0x40;
+			break;
+
+		case 0x1027:
+			/* check for something USB-related */
+			calc->z80.r.af.d |= 0x40;
+			break;
+
+		case 0x102E:
+			/* ??? */
+			break;
+
+		case 0x102F:
+			/* ??? */
+			break;
+
+		default:
+			tilem_warning(calc, "Unknown control instruction %x",
+				      opcode);
+		}
+		break;
+
+	case 0xedee:
+		/* erase Flash at address HL */
+		if (calc->flash.unlock) {
+			pa = xn_mem_ltop(calc, calc->z80.r.hl.w.l);
+			if (pa < 0x200000) {
+				tilem_flash_erase_address(calc, pa);
+			}
+		}
+		break;
+
+	case 0xedef:
+		/* enable Flash writing for following instruction */
+		if (calc->flash.unlock) {
+			calc->flash.state = 3;
+		}
+		break;
+
+	default:
+		tilem_warning(calc, "Invalid opcode %x", opcode);
+		tilem_z80_exception(calc, TILEM_EXC_INSTRUCTION);
+		break;
+	}
+}
+
+void xn_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT1:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+
+	case TIMER_INT2A:
+	case TIMER_INT2B:
+		if (calc->hwregs[PORT3] & 0x04)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER2;
+		break;
+
+	case TIMER_LCD_WAIT:
+		calc->hwregs[LCD_WAIT] = 0;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/xn/xn_memory.c b/tool/tilem-src/emu/xn/xn_memory.c
new file mode 100644
index 0000000..0956550
--- /dev/null
+++ b/tool/tilem-src/emu/xn/xn_memory.c
@@ -0,0 +1,204 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xn.h"
+
+void xn_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	unsigned long pa;
+	byte page;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x200000) {
+		calc->z80.clock += calc->hwregs[FLASH_WRITE_DELAY];
+		if (calc->flash.state == 3) {
+			*(calc->mem+pa) = v;
+			calc->flash.state = 0;
+		}
+	}
+	else if (pa < 0x220000) {
+		calc->z80.clock += calc->hwregs[RAM_WRITE_DELAY];
+		*(calc->mem+pa) = v;
+	}
+}
+
+static inline byte readbyte(TilemCalc* calc, dword pa)
+{
+	static const byte protectbytes[6] = {0x00,0x00,0xed,0x56,0xf3,0xd3};
+	int state = calc->hwregs[PROTECTSTATE];
+	byte value;
+
+	value = *(calc->mem + pa);
+
+	if (pa < 0x1B0000 || pa >= 0x200000
+	    || (pa >= 0x1C0000 && pa < 0x1F0000))
+		calc->hwregs[PROTECTSTATE] = 0;
+	else if (state == 6)
+		calc->hwregs[PROTECTSTATE] = 7;
+	else if (state < 6 && value == protectbytes[state])
+		calc->hwregs[PROTECTSTATE] = state + 1;
+	else
+		calc->hwregs[PROTECTSTATE] = 0;
+
+	return (value);
+}
+
+byte xn_z80_rdmem(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	if (TILEM_UNLIKELY(page == 0x7E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x200000)
+		calc->z80.clock += calc->hwregs[FLASH_READ_DELAY];
+	else
+		calc->z80.clock += calc->hwregs[RAM_READ_DELAY];
+
+	value = readbyte(calc, pa);
+	return (value);
+}
+
+byte xn_z80_rdmem_m1(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	if (TILEM_UNLIKELY((page & 0x80)
+			   && calc->hwregs[NO_EXEC_RAM] & (1 << (page&7)))) {
+		tilem_warning(calc, "Executing in restricted RAM area");
+		tilem_z80_exception(calc, TILEM_EXC_RAM_EXEC);
+	}
+
+	if (TILEM_UNLIKELY(page >= calc->hwregs[PORT22]
+			   && page <= calc->hwregs[PORT23])) {
+		tilem_warning(calc, "Executing in restricted Flash area");
+		tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+	}
+
+	if (TILEM_UNLIKELY(page == 0x7E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x200000)
+		calc->z80.clock += calc->hwregs[FLASH_EXEC_DELAY];
+	else
+		calc->z80.clock += calc->hwregs[RAM_EXEC_DELAY];
+
+	value = readbyte(calc, pa);
+
+	if (TILEM_UNLIKELY(value == 0xff && A == 0x0038)) {
+		tilem_warning(calc, "No OS installed");
+		tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+	}
+
+	return (value);
+}
+
+dword xn_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword xn_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+
+	if (!page)
+		return (A & 0x3fff);
+
+	if (page == calc->mempagemap[1])
+		return (0x4000 | (A & 0x3fff));
+
+	if ((A & 0x3fff) < 64 * calc->hwregs[PORT28]) {
+		if (page == 0x81)
+			return (0x8000 | (A & 0x3fff));
+	}
+	else {
+		if (page == calc->mempagemap[2])
+			return (0x8000 | (A & 0x3fff));
+	}
+
+	if ((A & 0x3fff) >= (0x4000 - 64 * calc->hwregs[PORT27])) {
+		if (page == 0x80)
+			return (0xC000 | (A & 0x3fff));
+	}
+	else {
+		if (page == calc->mempagemap[3])
+			return (0xC000 | (A & 0x3fff));
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/xn/xn_subcore.c b/tool/tilem-src/emu/xn/xn_subcore.c
new file mode 100644
index 0000000..fb26037
--- /dev/null
+++ b/tool/tilem-src/emu/xn/xn_subcore.c
@@ -0,0 +1,73 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xn.h"
+
+static const TilemFlashSector flashsectors[] = {
+	{0x000000, 0x10000, 0}, {0x010000, 0x10000, 0},
+	{0x020000, 0x10000, 0},	{0x030000, 0x10000, 0},
+	{0x040000, 0x10000, 0},	{0x050000, 0x10000, 0},
+	{0x060000, 0x10000, 0},	{0x070000, 0x10000, 0},
+	{0x080000, 0x10000, 0},	{0x090000, 0x10000, 0},
+	{0x0A0000, 0x10000, 0},	{0x0B0000, 0x10000, 0},
+	{0x0C0000, 0x10000, 0},	{0x0D0000, 0x10000, 0},
+	{0x0E0000, 0x10000, 0},	{0x0F0000, 0x10000, 0},
+	{0x100000, 0x10000, 0},	{0x110000, 0x10000, 0},
+	{0x120000, 0x10000, 0},	{0x130000, 0x10000, 0},
+	{0x140000, 0x10000, 0},	{0x150000, 0x10000, 0},
+	{0x160000, 0x10000, 0},	{0x170000, 0x10000, 0},
+	{0x180000, 0x10000, 0}, {0x190000, 0x10000, 0},
+	{0x1A0000, 0x10000, 0}, {0x1B0000, 0x10000, 0},
+	{0x1C0000, 0x10000, 0}, {0x1D0000, 0x10000, 0},
+	{0x1E0000, 0x10000, 0}, {0x1F0000, 0x08000, 0},
+	{0x1F8000, 0x02000, 0}, {0x1FA000, 0x02000, 0},
+	{0x1FC000, 0x04000, 0}};
+
+#define NUM_FLASH_SECTORS (sizeof(flashsectors) / sizeof(TilemFlashSector))
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+extern const char* xp_keynames[];
+
+TilemHardware hardware_ti84pns = {
+	'n', "ti84pns", "TI-Nspire (TI-84 Plus mode)",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_LINK_ASSIST
+	 | TILEM_CALC_HAS_T6A04 | TILEM_CALC_HAS_FLASH
+	 | TILEM_CALC_HAS_MD5_ASSIST),
+	96, 64, 128 * 0x4000, 8 * 0x4000, 15 * 64, 0x80,
+	NUM_FLASH_SECTORS, flashsectors, 3,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	xp_keynames,
+	xn_reset, xn_stateloaded,
+	xn_z80_in, xn_z80_out,
+	xn_z80_wrmem, xn_z80_rdmem, xn_z80_rdmem_m1, xn_z80_instr,
+	xn_z80_ptimer, tilem_lcd_t6a04_get_data,
+	xn_mem_ltop, xn_mem_ptol };
diff --git a/tool/tilem-src/emu/xp/xp.h b/tool/tilem-src/emu/xp/xp.h
new file mode 100644
index 0000000..3d78152
--- /dev/null
+++ b/tool/tilem-src/emu/xp/xp.h
@@ -0,0 +1,62 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_XP_H
+#define _TILEM_XP_H
+
+enum {
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* interrupt timer speed */
+	PORT5,			/* no-exec map index */
+	PORT6,			/* memory mapping bank A */
+	PORT7,			/* memory mapping bank B */
+	NOEXEC0,		/* no-exec map */
+	NOEXEC1,
+	NOEXEC2,
+	NOEXEC3,
+	NOEXEC4,
+	PROTECTSTATE,		/* port protection state */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES					\
+	{ "port3", "port4", "port5", "port6", "port7",  \
+	  "noexec0", "noexec1", "noexec2", "noexec3", \
+	  "noexec4", "protectstate" }
+
+#define TIMER_INT1 (TILEM_NUM_SYS_TIMERS + 1)
+#define TIMER_INT2A (TILEM_NUM_SYS_TIMERS + 2)
+#define TIMER_INT2B (TILEM_NUM_SYS_TIMERS + 3)
+#define NUM_HW_TIMERS 3
+
+#define HW_TIMER_NAMES { "int1", "int2a", "int2b" }
+
+void xp_reset(TilemCalc* calc);
+void xp_stateloaded(TilemCalc* calc, int savtype);
+byte xp_z80_in(TilemCalc* calc, dword port);
+void xp_z80_out(TilemCalc* calc, dword port, byte value);
+void xp_z80_ptimer(TilemCalc* calc, int id);
+void xp_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte xp_z80_rdmem(TilemCalc* calc, dword addr);
+byte xp_z80_rdmem_m1(TilemCalc* calc, dword addr);
+dword xp_mem_ltop(TilemCalc* calc, dword addr);
+dword xp_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/xp/xp_init.c b/tool/tilem-src/emu/xp/xp_init.c
new file mode 100644
index 0000000..0189236
--- /dev/null
+++ b/tool/tilem-src/emu/xp/xp_init.c
@@ -0,0 +1,67 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xp.h"
+
+void xp_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x77;
+	calc->hwregs[PORT6] = 0x1F;
+	calc->hwregs[PORT7] = 0x1F;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x1E;
+	calc->mempagemap[2] = 0x1F;
+	calc->mempagemap[3] = 0x1F;
+
+	calc->z80.r.pc.d = 0x8000;
+
+	calc->hwregs[PORT5] = 0;
+	calc->hwregs[NOEXEC0] = 0;
+	calc->hwregs[NOEXEC1] = 0;
+	calc->hwregs[NOEXEC2] = 0;
+	calc->hwregs[NOEXEC3] = 0;
+	calc->hwregs[NOEXEC4] = 0;
+
+	calc->hwregs[PROTECTSTATE] = 0;
+
+	tilem_linkport_set_mode(calc, TILEM_LINK_MODE_NO_TIMEOUT);
+
+	tilem_z80_set_speed(calc, 6000);
+
+	tilem_z80_set_timer(calc, TIMER_INT1, 1600, 8474, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2A, 1300, 8474, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2B, 1000, 8474, 1);
+}
+
+void xp_stateloaded(TilemCalc* calc, int savtype TILEM_ATTR_UNUSED)
+{
+	tilem_calc_fix_certificate(calc, calc->mem + (0x1E * 0x4000L),
+	                           0x15, 0x0c, 0x1f18);
+}
diff --git a/tool/tilem-src/emu/xp/xp_io.c b/tool/tilem-src/emu/xp/xp_io.c
new file mode 100644
index 0000000..d3fe73f
--- /dev/null
+++ b/tool/tilem-src/emu/xp/xp_io.c
@@ -0,0 +1,284 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xp.h"
+
+byte xp_z80_in(TilemCalc* calc, dword port)
+{
+	/* FIXME: measure actual levels */
+	static const byte battlevel[4] = { 33, 39, 36, 43 };
+	byte v;
+	
+	switch(port&0x1f) {
+	case 0x00:
+	case 0x08:
+		v = tilem_linkport_get_lines(calc);
+		v |= (calc->linkport.lines << 4);
+		if (calc->linkport.mode & TILEM_LINK_MODE_ASSIST)
+			v |= 0x04;
+		if (calc->linkport.assistflags & TILEM_LINK_ASSIST_READ_BYTE)
+			v |= 0x08;
+		else if (calc->linkport.assistflags
+			 & TILEM_LINK_ASSIST_READ_BUSY)
+			v |= 0x40;
+
+		return(v);
+
+	case 0x01:
+	case 0x09:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+	case 0x0A:
+		v = battlevel[calc->hwregs[PORT4] >> 6];
+		return((calc->battery >= v ? 3 : 2)
+		       | (calc->flash.unlock << 2)
+		       | (calc->hwregs[PORT5] << 3));
+
+	case 0x03:
+	case 0x0B:
+		return(calc->hwregs[PORT3]);
+
+	case 0x04:
+	case 0x0C:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER2)
+			v |= 0x04;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ACTIVE)
+			v |= 0x10;
+
+		return(v);
+
+	case 0x05:
+	case 0x0D:
+		return(tilem_linkport_read_byte(calc));
+
+	case 0x06:
+	case 0x0E:
+		return(calc->hwregs[PORT6]);
+
+	case 0x07:
+	case 0x0F:
+		return(calc->hwregs[PORT7]);
+
+	case 0x10:
+	case 0x12:
+	case 0x18:
+	case 0x1A:
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+	case 0x13:
+	case 0x19:
+	case 0x1B:
+		return(tilem_lcd_t6a04_read(calc));
+	}
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB;
+
+	if (calc->hwregs[PORT6] & 0x40)
+		pageA = (0x20 | (calc->hwregs[PORT6] & 1));
+	else
+		pageA = (calc->hwregs[PORT6] & 0x1f);
+
+	if (calc->hwregs[PORT7] & 0x40)
+		pageB = (0x20 | (calc->hwregs[PORT7] & 1));
+	else
+		pageB = (calc->hwregs[PORT7] & 0x1f);
+
+	if (calc->hwregs[PORT4] & 1) {
+		calc->mempagemap[1] = (pageA & ~1);
+		calc->mempagemap[2] = pageA;
+		calc->mempagemap[3] = pageB;
+	}
+	else {
+		calc->mempagemap[1] = pageA;
+		calc->mempagemap[2] = pageB;
+		calc->mempagemap[3] = 0x20;
+	}
+}
+
+void xp_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	static const int tmrvalues[4] = { 1786, 4032, 5882, 8474 };
+	int t;
+	unsigned int mode;
+
+	switch(port&0x1f) {
+	case 0x00:
+	case 0x08:
+		mode = calc->linkport.mode;
+		if (value & 4)
+			mode |= TILEM_LINK_MODE_ASSIST;
+		else
+			mode &= ~TILEM_LINK_MODE_ASSIST;
+
+		tilem_linkport_set_mode(calc, mode);
+		tilem_linkport_set_lines(calc, value);
+		break;
+
+	case 0x01:
+	case 0x09:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x03:
+	case 0x0B:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+
+		if (!(value & 0x04))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER2;
+
+		mode = calc->linkport.mode;
+		if (value & 0x10)
+			mode |= TILEM_LINK_MODE_INT_ON_ACTIVE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ACTIVE;
+
+		tilem_linkport_set_mode(calc, mode);
+
+		calc->poweronhalt = ((value & 8) >> 3);
+		calc->hwregs[PORT3] = value;
+		break;
+
+	case 0x04:
+	case 0x0C:
+		calc->hwregs[PORT4] = value;
+
+		t = tmrvalues[(value & 6) >> 1];
+		tilem_z80_set_timer_period(calc, TIMER_INT1, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2A, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2B, t);
+
+		setup_mapping(calc);
+		break;
+
+	case 0x05:
+	case 0x0D:
+		calc->hwregs[PORT5] = value & 0x07;
+		break;
+	
+	case 0x06:
+	case 0x0E:
+		calc->hwregs[PORT6] = value & 0x7f;
+		setup_mapping(calc);
+		break;
+
+	case 0x07:
+	case 0x0F:
+		calc->hwregs[PORT7] = value & 0x7f;
+		setup_mapping(calc);
+		break;
+
+	case 0x10:
+	case 0x12:
+	case 0x18:
+	case 0x1A:
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+	case 0x13:
+	case 0x19:
+	case 0x1B:
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+
+	case 0x14:
+	case 0x15:
+		if (calc->hwregs[PROTECTSTATE] == 7) {
+			if (value & 1)
+				tilem_message(calc, "Flash unlocked");
+			else
+				tilem_message(calc, "Flash locked");
+			calc->flash.unlock = value&1;
+		}
+		break;
+
+	case 0x16:
+	case 0x17:
+		if (calc->flash.unlock && calc->hwregs[PROTECTSTATE] == 7) {
+			switch(calc->hwregs[PORT5]) {
+			case 0:
+				tilem_message(calc, "No-exec mask for 08-0F set to %x", value);
+				calc->hwregs[NOEXEC1] = value;
+				break;
+			case 1:
+				tilem_message(calc, "No-exec mask for 10-17 set to %x", value);
+				calc->hwregs[NOEXEC2] = value;
+				break;
+			case 2:
+				tilem_message(calc, "No-exec mask for 18-1B set to %x", value);
+				calc->hwregs[NOEXEC3] = value & 0x0f;
+				break;
+			case 7:
+				tilem_message(calc, "No-exec mask for RAM set to %x", value);
+				calc->hwregs[NOEXEC4] = (value & 1) | ((value>>4) & 2);
+				break;
+			}
+		}
+		break;
+	}
+
+	return;
+}
+
+void xp_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT1:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+
+	case TIMER_INT2A:
+	case TIMER_INT2B:
+		if (calc->hwregs[PORT3] & 0x04)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER2;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/xp/xp_memory.c b/tool/tilem-src/emu/xp/xp_memory.c
new file mode 100644
index 0000000..ad717ad
--- /dev/null
+++ b/tool/tilem-src/emu/xp/xp_memory.c
@@ -0,0 +1,135 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xp.h"
+
+void xp_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	unsigned long pa;
+
+	pa = (A & 0x3FFF) + 0x4000*calc->mempagemap[(A)>>14];
+
+	if (pa < 0x80000)
+		tilem_flash_write_byte(calc, pa, v);
+
+	else if (pa < 0x88000)
+		*(calc->mem+pa) = v;
+}
+
+static inline byte readbyte(TilemCalc* calc, dword pa)
+{
+	static const byte protectbytes[6] = {0x00,0x00,0xed,0x56,0xf3,0xd3};
+	int state = calc->hwregs[PROTECTSTATE];
+	byte value;
+
+	if (pa < 0x80000 && (calc->flash.state || calc->flash.busy))
+		value = tilem_flash_read_byte(calc, pa);
+	else
+		value = *(calc->mem + pa);
+
+	if (pa < 0x70000 || pa >= 0x80000)
+		calc->hwregs[PROTECTSTATE] = 0;
+	else if (state == 6)
+		calc->hwregs[PROTECTSTATE] = 7;
+	else if (state < 6 && value == protectbytes[state])
+		calc->hwregs[PROTECTSTATE] = state + 1;
+	else
+		calc->hwregs[PROTECTSTATE] = 0;
+
+	return (value);
+}
+
+byte xp_z80_rdmem(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+	pa = 0x4000 * page + (A & 0x3FFF);
+
+	if (TILEM_UNLIKELY(page == 0x1E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	value = readbyte(calc, pa);
+	return (value);
+}
+
+byte xp_z80_rdmem_m1(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+	pa = 0x4000 * page + (A & 0x3FFF);
+
+	if (TILEM_UNLIKELY(calc->hwregs[NOEXEC0+page/8] & (1<<(page%8)))) {
+		tilem_warning(calc, "Executing in restricted %s area",
+			      page>0x1f?"RAM":"Flash");
+		tilem_z80_exception(calc, (page > 0x1f
+		                           ? TILEM_EXC_RAM_EXEC
+		                           : TILEM_EXC_FLASH_EXEC));
+	}
+
+	if (TILEM_UNLIKELY(page == 0x1E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	value = readbyte(calc, pa);
+
+	if (TILEM_UNLIKELY(value == 0xff && A == 0x0038)) {
+		tilem_warning(calc, "No OS installed");
+		tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+	}
+
+	return (value);
+}
+
+dword xp_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword xp_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		if (calc->mempagemap[i] == page) {
+			return ((i << 14) | (A & 0x3fff));
+		}
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/xp/xp_subcore.c b/tool/tilem-src/emu/xp/xp_subcore.c
new file mode 100644
index 0000000..3c6ed28
--- /dev/null
+++ b/tool/tilem-src/emu/xp/xp_subcore.c
@@ -0,0 +1,73 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xp.h"
+
+static const TilemFlashSector flashsectors[] = {
+	{0x000000, 0x10000, 0},
+	{0x010000, 0x10000, 0},
+	{0x020000, 0x10000, 0},
+	{0x030000, 0x10000, 0},
+	{0x040000, 0x10000, 0},
+	{0x050000, 0x10000, 0},
+	{0x060000, 0x10000, 0},
+	{0x070000, 0x08000, 0},
+	{0x078000, 0x02000, 0},
+	{0x07A000, 0x02000, 0},
+	{0x07C000, 0x04000, 1}};
+
+#define NUM_FLASH_SECTORS (sizeof(flashsectors) / sizeof(TilemFlashSector))
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+const char* xp_keynames[64] = {
+	"Down", "Left", "Right", "Up", 0, 0, 0, 0,
+	"Enter", "Add", "Sub", "Mul", "Div", "Power", "Clear", 0,
+	"Chs", "3", "6", "9", "RParen", "Tan", "Vars", 0,
+	"DecPnt", "2", "5", "8", "LParen", "Cos", "Prgm", "Stat",
+	"0", "1", "4", "7", "Comma", "Sin", "Apps", "Graphvar",
+	"On", "Store", "Ln", "Log", "Square", "Recip", "Math", "Alpha",
+	"Graph", "Trace", "Zoom", "Window", "YEqu", "2nd", "Mode", "Del",
+	0, 0, 0, 0, 0, 0, 0, 0};
+
+TilemHardware hardware_ti83p = {
+	'p', "ti83p", "TI-83 Plus",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_LINK_ASSIST
+	 | TILEM_CALC_HAS_T6A04 | TILEM_CALC_HAS_FLASH),
+	96, 64, 32 * 0x4000, 2 * 0x4000, 15 * 64, 0x40,
+	NUM_FLASH_SECTORS, flashsectors, 0,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	xp_keynames,
+	xp_reset, xp_stateloaded,
+	xp_z80_in, xp_z80_out,
+	xp_z80_wrmem, xp_z80_rdmem, xp_z80_rdmem_m1, NULL,
+	xp_z80_ptimer, tilem_lcd_t6a04_get_data,
+	xp_mem_ltop, xp_mem_ptol };
diff --git a/tool/tilem-src/emu/xs/xs.h b/tool/tilem-src/emu/xs/xs.h
new file mode 100644
index 0000000..6e3e602
--- /dev/null
+++ b/tool/tilem-src/emu/xs/xs.h
@@ -0,0 +1,101 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_XS_H
+#define _TILEM_XS_H
+
+enum {
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* interrupt timer speed */
+	PORT5,			/* memory mapping bank C */
+	PORT6,			/* memory mapping bank A */
+	PORT7,			/* memory mapping bank B */
+	PORT8,			/* link assist mode flags */
+	PORT9,			/* unknown (link assist settings?) */
+	PORTA,			/* unknown (timeout value?) */
+	PORTB,			/* unknown (timeout value?) */
+	PORTC,			/* unknown (timeout value?) */
+	PORTD,			/* unknown */
+	PORTE,			/* unknown */
+	PORTF,			/* unknown */
+
+	PORT20,			/* CPU speed control */
+	PORT21,			/* hardware type / RAM no-exec control */
+	PORT22,			/* Flash no-exec lower limit */
+	PORT23,			/* Flash no-exec upper limit */
+	PORT25,			/* RAM no-exec lower limit */
+	PORT26,			/* RAM no-exec upper limit */
+	PORT27,			/* bank C forced-page-0 limit */
+	PORT28,			/* bank B forced-page-1 limit */
+	PORT29,			/* LCD port delay (6 MHz) */
+	PORT2A,			/* LCD port delay (mode 1) */
+	PORT2B,			/* LCD port delay (mode 2) */
+	PORT2C,			/* LCD port delay (mode 3) */
+	PORT2D,			/* unknown */
+	PORT2E,			/* memory delay */
+	PORT2F,			/* Duration of LCD wait timer */
+
+	RAM_READ_DELAY,
+	RAM_WRITE_DELAY,
+	RAM_EXEC_DELAY,
+	FLASH_READ_DELAY,
+	FLASH_WRITE_DELAY,
+	FLASH_EXEC_DELAY,
+	LCD_PORT_DELAY,
+	NO_EXEC_RAM_MASK,
+	NO_EXEC_RAM_LOWER,
+	NO_EXEC_RAM_UPPER,
+
+	LCD_WAIT,		/* LCD wait timer active */
+	PROTECTSTATE,		/* port protection state */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES \
+	{ "port3", "port4", "port5", "port6", "port7", "port8", "port9", \
+	  "portA", "portB", "portC", "portD", "portE", "portF", "port20", \
+	  "port21", "port22", "port23", "port25", "port26", "port27", \
+	  "port28", "port29", "port2A", "port2B", "port2C", "port2D", \
+	  "port2E", "port2F", "ram_read_delay", "ram_write_delay", \
+	  "ram_exec_delay", "flash_read_delay", "flash_write_delay", \
+	  "flash_exec_delay", "lcd_port_delay", "no_exec_ram_mask", \
+	  "no_exec_ram_lower", "no_exec_ram_upper", \
+	  "lcd_wait", "protectstate" }
+
+#define TIMER_INT1 (TILEM_NUM_SYS_TIMERS + 1)
+#define TIMER_INT2A (TILEM_NUM_SYS_TIMERS + 2)
+#define TIMER_INT2B (TILEM_NUM_SYS_TIMERS + 3)
+#define TIMER_LCD_WAIT (TILEM_NUM_SYS_TIMERS + 4)
+#define NUM_HW_TIMERS 4
+
+#define HW_TIMER_NAMES { "int1", "int2a", "int2b", "lcd_wait" }
+
+void xs_reset(TilemCalc* calc);
+void xs_stateloaded(TilemCalc* calc, int savtype);
+byte xs_z80_in(TilemCalc* calc, dword port);
+void xs_z80_out(TilemCalc* calc, dword port, byte value);
+void xs_z80_ptimer(TilemCalc* calc, int id);
+void xs_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte xs_z80_rdmem(TilemCalc* calc, dword addr);
+byte xs_z80_rdmem_m1(TilemCalc* calc, dword addr);
+dword xs_mem_ltop(TilemCalc* calc, dword addr);
+dword xs_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/xs/xs_init.c b/tool/tilem-src/emu/xs/xs_init.c
new file mode 100644
index 0000000..1e05164
--- /dev/null
+++ b/tool/tilem-src/emu/xs/xs_init.c
@@ -0,0 +1,91 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xs.h"
+
+void xp_fix_cert(TilemCalc* calc, byte *cert);
+
+void xs_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x77;
+	calc->hwregs[PORT6] = 0x7F;
+	calc->hwregs[PORT7] = 0x7F;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x7E;
+	calc->mempagemap[2] = 0x7F;
+	calc->mempagemap[3] = 0x7F;
+
+	calc->z80.r.pc.d = 0x8000;
+
+	calc->hwregs[PORT8] = 0x80;
+
+	calc->hwregs[PORT20] = 0;
+	calc->hwregs[PORT21] = 1;
+	calc->hwregs[PORT22] = 0x08;
+	calc->hwregs[PORT23] = 0x69;
+	calc->hwregs[PORT25] = 0x10;
+	calc->hwregs[PORT26] = 0x20;
+	calc->hwregs[PORT27] = 0;
+	calc->hwregs[PORT28] = 0;
+	calc->hwregs[PORT29] = 0x14;
+	calc->hwregs[PORT2A] = 0x27;
+	calc->hwregs[PORT2B] = 0x2F;
+	calc->hwregs[PORT2C] = 0x3B;
+	calc->hwregs[PORT2D] = 0x01;
+	calc->hwregs[PORT2E] = 0x44;
+	calc->hwregs[PORT2F] = 0x4A;
+
+	calc->hwregs[FLASH_READ_DELAY] = 0;
+	calc->hwregs[FLASH_WRITE_DELAY] = 0;
+	calc->hwregs[FLASH_EXEC_DELAY] = 0;
+	calc->hwregs[RAM_READ_DELAY] = 0;
+	calc->hwregs[RAM_WRITE_DELAY] = 0;
+	calc->hwregs[RAM_EXEC_DELAY] = 0;
+	calc->hwregs[LCD_PORT_DELAY] = 5;
+	calc->hwregs[NO_EXEC_RAM_MASK] = 0x7C00;
+	calc->hwregs[NO_EXEC_RAM_LOWER] = 0x4000;
+	calc->hwregs[NO_EXEC_RAM_UPPER] = 0x8000;
+
+	calc->hwregs[PROTECTSTATE] = 0;
+
+	calc->flash.overridegroup = 1;
+
+	tilem_z80_set_speed(calc, 6000);
+
+	tilem_z80_set_timer(calc, TIMER_INT1, 1600, 9277, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2A, 1300, 9277, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2B, 1000, 9277, 1);
+}
+
+void xs_stateloaded(TilemCalc* calc, int savtype TILEM_ATTR_UNUSED)
+{
+	tilem_calc_fix_certificate(calc, calc->mem + (0x7E * 0x4000L),
+	                           0x69, 0x0c, 0x1e50);
+}
diff --git a/tool/tilem-src/emu/xs/xs_io.c b/tool/tilem-src/emu/xs/xs_io.c
new file mode 100644
index 0000000..ef6f27f
--- /dev/null
+++ b/tool/tilem-src/emu/xs/xs_io.c
@@ -0,0 +1,658 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xs.h"
+
+static void set_lcd_wait_timer(TilemCalc* calc)
+{
+	static const int delaytime[8] = { 48, 112, 176, 240,
+					  304, 368, 432, 496 };
+	int i;
+
+	switch (calc->hwregs[PORT20] & 3) {
+	case 0:
+		return;
+	case 1:
+		i = (calc->hwregs[PORT2F] & 3);
+		break;
+	case 2:
+		i = ((calc->hwregs[PORT2F] >> 2) & 7);
+		break;
+	default:
+		i = ((calc->hwregs[PORT2F] >> 5) & 7);
+		break;
+	}
+
+	tilem_z80_set_timer(calc, TIMER_LCD_WAIT, delaytime[i], 0, 0);
+	calc->hwregs[LCD_WAIT] = 1;
+}
+
+byte xs_z80_in(TilemCalc* calc, dword port)
+{
+	/* FIXME: measure actual levels */
+	static const byte battlevel[4] = { 33, 39, 36, 43 };
+	byte v;
+	unsigned int f;
+
+	switch(port&0xff) {
+	case 0x00:
+		v = tilem_linkport_get_lines(calc);
+		v |= (calc->linkport.lines << 4);
+		return(v);
+
+	case 0x01:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+		v = battlevel[calc->hwregs[PORT4] >> 6];
+		return((calc->battery >= v ? 0xc1 : 0xc0)
+		       | (calc->hwregs[LCD_WAIT] ? 0 : 2)
+		       | (calc->flash.unlock << 2));
+
+	case 0x03:
+		return(calc->hwregs[PORT3]);
+
+	case 0x04:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER2)
+			v |= 0x04;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ACTIVE)
+			v |= 0x10;
+
+		if (calc->usertimers[0].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x20;
+		if (calc->usertimers[1].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x40;
+		if (calc->usertimers[2].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x80;
+
+		return(v);
+
+	case 0x05:
+		return(calc->hwregs[PORT5] & 0x0f);
+
+	case 0x06:
+		return(calc->hwregs[PORT6]);
+
+	case 0x07:
+		return(calc->hwregs[PORT7]);
+
+	case 0x08:
+		return(calc->hwregs[PORT8]);
+
+	case 0x09:
+		f = tilem_linkport_get_assist_flags(calc);
+
+		if (f & (TILEM_LINK_ASSIST_READ_BUSY
+			 | TILEM_LINK_ASSIST_WRITE_BUSY))
+			v = 0x00;
+		else
+			v = 0x20;
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_READ)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_IDLE)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ERROR)
+			v |= 0x04;
+		if (f & TILEM_LINK_ASSIST_READ_BUSY)
+			v |= 0x08;
+		if (f & TILEM_LINK_ASSIST_READ_BYTE)
+			v |= 0x10;
+		if (f & (TILEM_LINK_ASSIST_READ_ERROR
+			 | TILEM_LINK_ASSIST_WRITE_ERROR))
+			v |= 0x40;
+		if (f & TILEM_LINK_ASSIST_WRITE_BUSY)
+			v |= 0x80;
+
+		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_ERROR;
+
+		return(v);
+
+	case 0x0A:
+		v = calc->linkport.assistlastbyte;
+		tilem_linkport_read_byte(calc);
+		return(v);
+
+	case 0x0E:
+		return(calc->hwregs[PORTE] & 3);
+
+	case 0x0F:
+		return(calc->hwregs[PORTF] & 3);
+
+	case 0x10:
+	case 0x12:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+	case 0x13:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		return(tilem_lcd_t6a04_read(calc));
+
+	case 0x15:
+		return(0x33);	/* ??? */
+
+	case 0x1C:
+		return(tilem_md5_assist_get_value(calc));
+
+	case 0x1D:
+		return(tilem_md5_assist_get_value(calc) >> 8);
+
+	case 0x1E:
+		return(tilem_md5_assist_get_value(calc) >> 16);
+
+	case 0x1F:
+		return(tilem_md5_assist_get_value(calc) >> 24);
+
+	case 0x20:
+		return(calc->hwregs[PORT20] & 3);
+
+	case 0x21:
+		return(calc->hwregs[PORT21] & 0x33);
+
+	case 0x22:
+		return(calc->hwregs[PORT22]);
+
+	case 0x23:
+		return(calc->hwregs[PORT23]);
+
+	case 0x25:
+		return(calc->hwregs[PORT25]);
+
+	case 0x26:
+		return(calc->hwregs[PORT26]);
+
+	case 0x27:
+		return(calc->hwregs[PORT27]);
+
+	case 0x28:
+		return(calc->hwregs[PORT28]);
+
+	case 0x29:
+		return(calc->hwregs[PORT29]);
+
+	case 0x2A:
+		return(calc->hwregs[PORT2A]);
+
+	case 0x2B:
+		return(calc->hwregs[PORT2B]);
+
+	case 0x2C:
+		return(calc->hwregs[PORT2C]);
+
+	case 0x2D:
+		return(calc->hwregs[PORT2D] & 3);
+
+	case 0x2E:
+		return(calc->hwregs[PORT2E]);
+
+	case 0x2F:
+		return(calc->hwregs[PORT2F]);
+
+	case 0x30:
+		return(calc->usertimers[0].frequency);
+	case 0x31:
+		return(calc->usertimers[0].status);
+	case 0x32:
+		return(tilem_user_timer_get_value(calc, 0));
+
+	case 0x33:
+		return(calc->usertimers[1].frequency);
+	case 0x34:
+		return(calc->usertimers[1].status);
+	case 0x35:
+		return(tilem_user_timer_get_value(calc, 1));
+
+	case 0x36:
+		return(calc->usertimers[2].frequency);
+	case 0x37:
+		return(calc->usertimers[2].status);
+	case 0x38:
+		return(tilem_user_timer_get_value(calc, 2));
+
+	case 0x0B:
+	case 0x0C:
+	case 0x0D:
+	case 0x14:
+	case 0x16:
+	case 0x17:
+	case 0x18:
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+	case 0x39:
+	case 0x3A:
+	case 0x3B:
+	case 0x3C:
+	case 0x3D:
+	case 0x3E:
+	case 0x3F:
+		return(0);
+	}
+
+	return(0xff);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB, pageC;
+
+	if (calc->hwregs[PORT6] & 0x80)
+		pageA = (0x80 | (calc->hwregs[PORT6] & 7));
+	else
+		pageA = (calc->hwregs[PORT6] & 0x7f);
+
+	if (calc->hwregs[PORT7] & 0x80)
+		pageB = (0x80 | (calc->hwregs[PORT7] & 7));
+	else
+		pageB = (calc->hwregs[PORT7] & 0x7f);
+
+	pageC = (0x80 | (calc->hwregs[PORT5] & 7));
+
+	if (calc->hwregs[PORT4] & 1) {
+		calc->mempagemap[1] = (pageA & ~1);
+		calc->mempagemap[2] = (pageA | 1);
+		calc->mempagemap[3] = pageB;
+	}
+	else {
+		calc->mempagemap[1] = pageA;
+		calc->mempagemap[2] = pageB;
+		calc->mempagemap[3] = pageC;
+	}
+}
+
+static void setup_clockdelays(TilemCalc* calc)
+{
+	byte lcdport = calc->hwregs[PORT29 + (calc->hwregs[PORT20] & 3)];
+	byte memport = calc->hwregs[PORT2E];
+
+	if (!(lcdport & 1))
+		memport &= ~0x07;
+	if (!(lcdport & 2))
+		memport &= ~0x70;
+
+	calc->hwregs[FLASH_EXEC_DELAY] = (memport & 1);
+	calc->hwregs[FLASH_READ_DELAY] = ((memport >> 1) & 1);
+	calc->hwregs[FLASH_WRITE_DELAY] = ((memport >> 2) & 1);
+
+	calc->hwregs[RAM_EXEC_DELAY] = ((memport >> 4) & 1);
+	calc->hwregs[RAM_READ_DELAY] = ((memport >> 5) & 1);
+	calc->hwregs[RAM_WRITE_DELAY] = ((memport >> 6) & 1);
+
+	calc->hwregs[LCD_PORT_DELAY] = (lcdport >> 2);
+}
+
+void xs_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	static const int tmrvalues[4] = { 1953, 4395, 6836, 9277 };
+	int t, r;
+	unsigned int mode;
+
+	switch(port&0xff) {
+	case 0x00:
+		tilem_linkport_set_lines(calc, value);
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x03:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+
+		if (!(value & 0x04))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER2;
+
+		if (value & 0x06) {
+			calc->usertimers[0].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[1].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[2].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+		}
+		else {
+			calc->usertimers[0].status |= TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[1].status |= TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[2].status |= TILEM_USER_TIMER_NO_HALT_INT;
+		}
+
+		mode = calc->linkport.mode;
+		if (value & 0x10)
+			mode |= TILEM_LINK_MODE_INT_ON_ACTIVE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ACTIVE;
+
+		tilem_linkport_set_mode(calc, mode);
+
+		calc->poweronhalt = ((value & 8) >> 3);
+		calc->hwregs[PORT3] = value;
+		break;
+
+	case 0x04:
+		calc->hwregs[PORT4] = value;
+
+		t = tmrvalues[(value & 6) >> 1];
+		tilem_z80_set_timer_period(calc, TIMER_INT1, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2A, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2B, t);
+
+		setup_mapping(calc);
+		break;
+	
+	case 0x05:
+		calc->hwregs[PORT5] = value & 0x0f;
+		setup_mapping(calc);
+		break;
+
+	case 0x06:
+		calc->hwregs[PORT6] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x07:
+		calc->hwregs[PORT7] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x08:
+		calc->hwregs[PORT8] = value;
+
+		mode = calc->linkport.mode;
+
+		if (value & 0x01)
+			mode |= TILEM_LINK_MODE_INT_ON_READ;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_READ;
+
+		if (value & 0x02)
+			mode |= TILEM_LINK_MODE_INT_ON_IDLE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_IDLE;
+
+		if (value & 0x04)
+			mode |= TILEM_LINK_MODE_INT_ON_ERROR;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ERROR;
+
+		if (value & 0x80)
+			mode &= ~TILEM_LINK_MODE_ASSIST;
+		else
+			mode |= TILEM_LINK_MODE_ASSIST;
+
+		tilem_linkport_set_mode(calc, mode);
+		break;
+
+	case 0x09:
+		calc->hwregs[PORT9] = value;
+		break;
+
+	case 0x0A:
+		calc->hwregs[PORTA] = value;
+		break;
+
+	case 0x0B:
+		calc->hwregs[PORTB] = value;
+		break;
+
+	case 0x0C:
+		calc->hwregs[PORTC] = value;
+		break;
+
+	case 0x0D:
+		if (!(calc->hwregs[PORT8] & 0x80))
+			tilem_linkport_write_byte(calc, value);
+		break;
+
+	case 0x0E:
+		calc->hwregs[PORTE] = value;
+		break;
+
+	case 0x0F:
+		calc->hwregs[PORTF] = value;
+		break;
+
+	case 0x10:
+	case 0x12:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+	case 0x13:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+
+	case 0x14:
+		if (calc->hwregs[PROTECTSTATE] == 7) {
+			if (value & 1)
+				tilem_message(calc, "Flash unlocked");
+			else
+				tilem_message(calc, "Flash locked");
+			calc->flash.unlock = value&1;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 14");
+		}
+		break;
+
+	case 0x18:
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+	case 0x1C:
+	case 0x1D:
+		r = (port & 0xff) - 0x18;
+		calc->md5assist.regs[r] >>= 8;
+		calc->md5assist.regs[r] |= (value << 24);
+		break;
+
+	case 0x1E:
+		calc->md5assist.shift = value & 0x1f;
+		break;
+
+	case 0x1F:
+		calc->md5assist.mode = value & 3;
+		break;
+
+	case 0x20:
+		calc->hwregs[PORT20] = value;
+
+		if (value & 3) {
+			tilem_z80_set_speed(calc, 15000);
+		}
+		else {
+			tilem_z80_set_speed(calc, 6000);
+		}
+
+		setup_clockdelays(calc);
+		break;
+
+	case 0x21:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT21] = value;
+			t = (value >> 4) & 3;
+			calc->hwregs[NO_EXEC_RAM_MASK] = (0x8000 << t) - 0x400;
+			calc->flash.overridegroup = value & 3;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 21");
+		}
+		break;
+
+	case 0x22:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT22] = value;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 22");
+		}
+		break;
+
+	case 0x23:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT23] = value;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 23");
+		}
+		break;
+
+	case 0x25:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT25] = value;
+			calc->hwregs[NO_EXEC_RAM_LOWER] = value * 0x400;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 25");
+		}
+		break;
+
+	case 0x26:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT26] = value;
+			calc->hwregs[NO_EXEC_RAM_UPPER] = value * 0x400;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 26");
+		}
+		break;
+
+	case 0x27:
+		calc->hwregs[PORT27] = value;
+		break;
+
+	case 0x28:
+		calc->hwregs[PORT28] = value;
+		break;
+
+	case 0x29:
+		calc->hwregs[PORT29] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2A:
+		calc->hwregs[PORT2A] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2B:
+		calc->hwregs[PORT2B] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2C:
+		calc->hwregs[PORT2C] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2D:
+		calc->hwregs[PORT2D] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2E:
+		calc->hwregs[PORT2E] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2F:
+		calc->hwregs[PORT2F] = value;
+		break;
+
+	case 0x30:
+		tilem_user_timer_set_frequency(calc, 0, value);
+		break;
+	case 0x31:
+		tilem_user_timer_set_mode(calc, 0, value);
+		break;
+	case 0x32:
+		tilem_user_timer_start(calc, 0, value);
+		break;
+
+	case 0x33:
+		tilem_user_timer_set_frequency(calc, 1, value);
+		break;
+	case 0x34:
+		tilem_user_timer_set_mode(calc, 1, value);
+		break;
+	case 0x35:
+		tilem_user_timer_start(calc, 1, value);
+		break;
+
+	case 0x36:
+		tilem_user_timer_set_frequency(calc, 2, value);
+		break;
+	case 0x37:
+		tilem_user_timer_set_mode(calc, 2, value);
+		break;
+	case 0x38:
+		tilem_user_timer_start(calc, 2, value);
+		break;
+	}
+
+	return;
+}
+
+void xs_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT1:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+
+	case TIMER_INT2A:
+	case TIMER_INT2B:
+		if (calc->hwregs[PORT3] & 0x04)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER2;
+		break;
+
+	case TIMER_LCD_WAIT:
+		calc->hwregs[LCD_WAIT] = 0;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/xs/xs_memory.c b/tool/tilem-src/emu/xs/xs_memory.c
new file mode 100644
index 0000000..c007e1a
--- /dev/null
+++ b/tool/tilem-src/emu/xs/xs_memory.c
@@ -0,0 +1,213 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xs.h"
+
+/* FIXME: what effect, if any, do ports 27 and 28 have in memory
+   mapping mode 1? */
+
+void xs_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	unsigned long pa;
+	byte page;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa<0x200000) {
+		calc->z80.clock += calc->hwregs[FLASH_WRITE_DELAY];
+		tilem_flash_write_byte(calc, pa, v);
+	}
+	else if (pa < 0x220000) {
+		calc->z80.clock += calc->hwregs[RAM_WRITE_DELAY];
+		*(calc->mem+pa) = v;
+	}
+}
+
+static inline byte readbyte(TilemCalc* calc, dword pa)
+{
+	static const byte protectbytes[6] = {0x00,0x00,0xed,0x56,0xf3,0xd3};
+	int state = calc->hwregs[PROTECTSTATE];
+	byte value;
+
+	if (pa < 0x200000 && (calc->flash.state || calc->flash.busy))
+		value = tilem_flash_read_byte(calc, pa);
+	else
+		value = *(calc->mem + pa);
+
+	if (pa < 0x1F0000 || pa >= 0x200000)
+		calc->hwregs[PROTECTSTATE] = 0;
+	else if (state == 6)
+		calc->hwregs[PROTECTSTATE] = 7;
+	else if (state < 6 && value == protectbytes[state])
+		calc->hwregs[PROTECTSTATE] = state + 1;
+	else
+		calc->hwregs[PROTECTSTATE] = 0;
+
+	return (value);
+}
+
+byte xs_z80_rdmem(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	if (TILEM_UNLIKELY(page == 0x7E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x200000)
+		calc->z80.clock += calc->hwregs[FLASH_READ_DELAY];
+	else
+		calc->z80.clock += calc->hwregs[RAM_READ_DELAY];
+
+	value = readbyte(calc, pa);
+	return (value);
+}
+
+byte xs_z80_rdmem_m1(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa, m;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	if (TILEM_UNLIKELY(page == 0x7E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x200000) {
+		calc->z80.clock += calc->hwregs[FLASH_EXEC_DELAY];
+
+		if (TILEM_UNLIKELY(page >= calc->hwregs[PORT22]
+		                   && page <= calc->hwregs[PORT23])) {
+			tilem_warning(calc, "Executing in restricted Flash area");
+			tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+		}
+	}
+	else {
+		calc->z80.clock += calc->hwregs[RAM_EXEC_DELAY];
+
+		/* Note: this isn't quite strict enough; when port 21
+		   is set to 30h, the "ghost" RAM pages 88-8F are
+		   treated as distinct pages for restriction purposes.
+		   This detail probably isn't worth emulating. */
+		m = pa & calc->hwregs[NO_EXEC_RAM_MASK];
+		if (TILEM_UNLIKELY(m < calc->hwregs[NO_EXEC_RAM_LOWER]
+		                   || m > calc->hwregs[NO_EXEC_RAM_UPPER])) {
+			tilem_warning(calc, "Executing in restricted RAM area");
+			tilem_z80_exception(calc, TILEM_EXC_RAM_EXEC);
+		}
+	}
+
+	value = readbyte(calc, pa);
+
+	if (TILEM_UNLIKELY(value == 0xff && A == 0x0038)) {
+		tilem_warning(calc, "No OS installed");
+		tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+	}
+
+	return (value);
+}
+
+dword xs_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword xs_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+
+	if (!page)
+		return (A & 0x3fff);
+
+	if (page == calc->mempagemap[1])
+		return (0x4000 | (A & 0x3fff));
+
+	if ((A & 0x3fff) < 64 * calc->hwregs[PORT28]) {
+		if (page == 0x81)
+			return (0x8000 | (A & 0x3fff));
+	}
+	else {
+		if (page == calc->mempagemap[2])
+			return (0x8000 | (A & 0x3fff));
+	}
+
+	if ((A & 0x3fff) >= (0x4000 - 64 * calc->hwregs[PORT27])) {
+		if (page == 0x80)
+			return (0xC000 | (A & 0x3fff));
+	}
+	else {
+		if (page == calc->mempagemap[3])
+			return (0xC000 | (A & 0x3fff));
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/xs/xs_subcore.c b/tool/tilem-src/emu/xs/xs_subcore.c
new file mode 100644
index 0000000..121be93
--- /dev/null
+++ b/tool/tilem-src/emu/xs/xs_subcore.c
@@ -0,0 +1,73 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xs.h"
+
+static const TilemFlashSector flashsectors[] = {
+	{0x000000, 0x10000, 0}, {0x010000, 0x10000, 0},
+	{0x020000, 0x10000, 0},	{0x030000, 0x10000, 0},
+	{0x040000, 0x10000, 0},	{0x050000, 0x10000, 0},
+	{0x060000, 0x10000, 0},	{0x070000, 0x10000, 0},
+	{0x080000, 0x10000, 0},	{0x090000, 0x10000, 0},
+	{0x0A0000, 0x10000, 0},	{0x0B0000, 0x10000, 0},
+	{0x0C0000, 0x10000, 0},	{0x0D0000, 0x10000, 0},
+	{0x0E0000, 0x10000, 0},	{0x0F0000, 0x10000, 0},
+	{0x100000, 0x10000, 0},	{0x110000, 0x10000, 0},
+	{0x120000, 0x10000, 0},	{0x130000, 0x10000, 0},
+	{0x140000, 0x10000, 0},	{0x150000, 0x10000, 0},
+	{0x160000, 0x10000, 0},	{0x170000, 0x10000, 0},
+	{0x180000, 0x10000, 0}, {0x190000, 0x10000, 0},
+	{0x1A0000, 0x10000, 0}, {0x1B0000, 0x10000, 0},
+	{0x1C0000, 0x10000, 0}, {0x1D0000, 0x10000, 0},
+	{0x1E0000, 0x10000, 0}, {0x1F0000, 0x08000, 0},
+	{0x1F8000, 0x02000, 0}, {0x1FA000, 0x02000, 0},
+	{0x1FC000, 0x04000, 2}};
+
+#define NUM_FLASH_SECTORS (sizeof(flashsectors) / sizeof(TilemFlashSector))
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+extern const char* xp_keynames[];
+
+TilemHardware hardware_ti83pse = {
+	's', "ti83pse", "TI-83 Plus Silver Edition",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_LINK_ASSIST
+	 | TILEM_CALC_HAS_T6A04 | TILEM_CALC_HAS_FLASH
+	 | TILEM_CALC_HAS_MD5_ASSIST),
+	96, 64, 128 * 0x4000, 8 * 0x4000, 16 * 64, 0x80,
+	NUM_FLASH_SECTORS, flashsectors, 3,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	xp_keynames,
+	xs_reset, xs_stateloaded,
+	xs_z80_in, xs_z80_out,
+	xs_z80_wrmem, xs_z80_rdmem, xs_z80_rdmem_m1, NULL,
+	xs_z80_ptimer, tilem_lcd_t6a04_get_data,
+	xs_mem_ltop, xs_mem_ptol };
diff --git a/tool/tilem-src/emu/xz/xz.h b/tool/tilem-src/emu/xz/xz.h
new file mode 100644
index 0000000..6f6d440
--- /dev/null
+++ b/tool/tilem-src/emu/xz/xz.h
@@ -0,0 +1,105 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_XZ_H
+#define _TILEM_XZ_H
+
+enum {
+	PORT3,			/* mask of enabled interrupts */
+	PORT4,			/* interrupt timer speed */
+	PORT5,			/* memory mapping bank C */
+	PORT6,			/* memory mapping bank A */
+	PORT7,			/* memory mapping bank B */
+	PORT8,			/* link assist mode flags */
+	PORT9,			/* unknown (link assist settings?) */
+	PORTA,			/* unknown (timeout value?) */
+	PORTB,			/* unknown (timeout value?) */
+	PORTC,			/* unknown (timeout value?) */
+	PORTD,			/* unknown */
+	PORTE,			/* unknown */
+	PORTF,			/* unknown */
+
+	PORT20,			/* CPU speed control */
+	PORT21,			/* hardware type / RAM no-exec control */
+	PORT22,			/* Flash no-exec lower limit */
+	PORT23,			/* Flash no-exec upper limit */
+	PORT25,			/* RAM no-exec lower limit */
+	PORT26,			/* RAM no-exec upper limit */
+	PORT27,			/* bank C forced-page-0 limit */
+	PORT28,			/* bank B forced-page-1 limit */
+	PORT29,			/* LCD port delay (6 MHz) */
+	PORT2A,			/* LCD port delay (mode 1) */
+	PORT2B,			/* LCD port delay (mode 2) */
+	PORT2C,			/* LCD port delay (mode 3) */
+	PORT2D,			/* unknown */
+	PORT2E,			/* memory delay */
+	PORT2F,			/* Duration of LCD wait timer */
+
+	CLOCK_MODE,		/* clock mode */
+	CLOCK_INPUT,		/* clock input register */
+	CLOCK_DIFF,		/* clock value minus actual time */
+
+	RAM_READ_DELAY,
+	RAM_WRITE_DELAY,
+	RAM_EXEC_DELAY,
+	FLASH_READ_DELAY,
+	FLASH_WRITE_DELAY,
+	FLASH_EXEC_DELAY,
+	LCD_PORT_DELAY,
+	NO_EXEC_RAM_MASK,
+	NO_EXEC_RAM_LOWER,
+	NO_EXEC_RAM_UPPER,
+
+	LCD_WAIT,		/* LCD wait timer active */
+	PROTECTSTATE,		/* port protection state */
+	NUM_HW_REGS
+};
+
+#define HW_REG_NAMES \
+	{ "port3", "port4", "port5", "port6", "port7", "port8", "port9", \
+	  "portA", "portB", "portC", "portD", "portE", "portF", "port20", \
+	  "port21", "port22", "port23", "port25", "port26", "port27", \
+	  "port28", "port29", "port2A", "port2B", "port2C", "port2D", \
+	  "port2E", "port2F", "clock_mode", "clock_input", "clock_diff", \
+	  "ram_read_delay", "ram_write_delay", "ram_exec_delay", \
+	  "flash_read_delay", "flash_write_delay", "flash_exec_delay", \
+	  "lcd_port_delay", "no_exec_ram_mask", "no_exec_ram_lower", \
+	  "no_exec_ram_upper", "lcd_wait", "protectstate" }
+
+#define TIMER_INT1 (TILEM_NUM_SYS_TIMERS + 1)
+#define TIMER_INT2A (TILEM_NUM_SYS_TIMERS + 2)
+#define TIMER_INT2B (TILEM_NUM_SYS_TIMERS + 3)
+#define TIMER_LCD_WAIT (TILEM_NUM_SYS_TIMERS + 4)
+#define NUM_HW_TIMERS 4
+
+#define HW_TIMER_NAMES { "int1", "int2a", "int2b", "lcd_wait" }
+
+void xz_reset(TilemCalc* calc);
+void xz_stateloaded(TilemCalc* calc, int savtype);
+byte xz_z80_in(TilemCalc* calc, dword port);
+void xz_z80_out(TilemCalc* calc, dword port, byte value);
+void xz_z80_ptimer(TilemCalc* calc, int id);
+void xz_z80_wrmem(TilemCalc* calc, dword addr, byte value);
+byte xz_z80_rdmem(TilemCalc* calc, dword addr);
+byte xz_z80_rdmem_m1(TilemCalc* calc, dword addr);
+dword xz_mem_ltop(TilemCalc* calc, dword addr);
+dword xz_mem_ptol(TilemCalc* calc, dword addr);
+
+#endif
diff --git a/tool/tilem-src/emu/xz/xz_init.c b/tool/tilem-src/emu/xz/xz_init.c
new file mode 100644
index 0000000..eace5a7
--- /dev/null
+++ b/tool/tilem-src/emu/xz/xz_init.c
@@ -0,0 +1,89 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xz.h"
+
+void xz_reset(TilemCalc* calc)
+{
+	calc->hwregs[PORT3] = 0x0B;
+	calc->hwregs[PORT4] = 0x07;
+	calc->hwregs[PORT6] = 0x7F;
+	calc->hwregs[PORT7] = 0x7F;
+
+	calc->mempagemap[0] = 0x00;
+	calc->mempagemap[1] = 0x7E;
+	calc->mempagemap[2] = 0x7F;
+	calc->mempagemap[3] = 0x7F;
+
+	calc->z80.r.pc.d = 0x8000;
+
+	calc->hwregs[PORT8] = 0x80;
+
+	calc->hwregs[PORT20] = 0;
+	calc->hwregs[PORT21] = 1;
+	calc->hwregs[PORT22] = 0x08;
+	calc->hwregs[PORT23] = 0x69;
+	calc->hwregs[PORT25] = 0x10;
+	calc->hwregs[PORT26] = 0x20;
+	calc->hwregs[PORT27] = 0;
+	calc->hwregs[PORT28] = 0;
+	calc->hwregs[PORT29] = 0x14;
+	calc->hwregs[PORT2A] = 0x27;
+	calc->hwregs[PORT2B] = 0x2F;
+	calc->hwregs[PORT2C] = 0x3B;
+	calc->hwregs[PORT2D] = 0x01;
+	calc->hwregs[PORT2E] = 0x44;
+	calc->hwregs[PORT2F] = 0x4A;
+
+	calc->hwregs[FLASH_READ_DELAY] = 0;
+	calc->hwregs[FLASH_WRITE_DELAY] = 0;
+	calc->hwregs[FLASH_EXEC_DELAY] = 0;
+	calc->hwregs[RAM_READ_DELAY] = 0;
+	calc->hwregs[RAM_WRITE_DELAY] = 0;
+	calc->hwregs[RAM_EXEC_DELAY] = 0;
+	calc->hwregs[LCD_PORT_DELAY] = 5;
+	calc->hwregs[NO_EXEC_RAM_MASK] = 0x7C00;
+	calc->hwregs[NO_EXEC_RAM_LOWER] = 0x4000;
+	calc->hwregs[NO_EXEC_RAM_UPPER] = 0x8000;
+
+	calc->hwregs[PROTECTSTATE] = 0;
+
+	calc->flash.overridegroup = 1;
+
+	tilem_z80_set_speed(calc, 6000);
+
+	tilem_z80_set_timer(calc, TIMER_INT1, 1600, 9277, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2A, 1300, 9277, 1);
+	tilem_z80_set_timer(calc, TIMER_INT2B, 1000, 9277, 1);
+}
+
+void xz_stateloaded(TilemCalc* calc, int savtype TILEM_ATTR_UNUSED)
+{
+	tilem_calc_fix_certificate(calc, calc->mem + (0x7E * 0x4000L),
+	                           0x69, 0x0c, 0x1e50);
+}
diff --git a/tool/tilem-src/emu/xz/xz_io.c b/tool/tilem-src/emu/xz/xz_io.c
new file mode 100644
index 0000000..557e878
--- /dev/null
+++ b/tool/tilem-src/emu/xz/xz_io.c
@@ -0,0 +1,747 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <time.h>
+#include <tilem.h>
+
+#include "xz.h"
+
+static void set_lcd_wait_timer(TilemCalc* calc)
+{
+	static const int delaytime[8] = { 48, 112, 176, 240,
+					  304, 368, 432, 496 };
+	int i;
+
+	switch (calc->hwregs[PORT20] & 3) {
+	case 0:
+		return;
+	case 1:
+		i = (calc->hwregs[PORT2F] & 3);
+		break;
+	case 2:
+		i = ((calc->hwregs[PORT2F] >> 2) & 7);
+		break;
+	default:
+		i = ((calc->hwregs[PORT2F] >> 5) & 7);
+		break;
+	}
+
+	tilem_z80_set_timer(calc, TIMER_LCD_WAIT, delaytime[i], 0, 0);
+	calc->hwregs[LCD_WAIT] = 1;
+}
+
+byte xz_z80_in(TilemCalc* calc, dword port)
+{
+	/* FIXME: measure actual levels */
+	static const byte battlevel[4] = { 33, 39, 36, 43 };
+	byte v;
+	unsigned int f;
+	time_t curtime;
+
+	switch(port&0xff) {
+	case 0x00:
+		v = tilem_linkport_get_lines(calc);
+		v |= (calc->linkport.lines << 4);
+		return(v);
+
+	case 0x01:
+		return(tilem_keypad_read_keys(calc));
+
+	case 0x02:
+		v = battlevel[calc->hwregs[PORT4] >> 6];
+		return ((calc->battery >= v ? 0xe1 : 0xe0)
+			| (calc->hwregs[LCD_WAIT] ? 0 : 2)
+			| (calc->flash.unlock << 2));
+
+	case 0x03:
+		return(calc->hwregs[PORT3]);
+
+	case 0x04:
+		v = (calc->keypad.onkeydown ? 0x00 : 0x08);
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_ON_KEY)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER1)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_TIMER2)
+			v |= 0x04;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ACTIVE)
+			v |= 0x10;
+
+		if (calc->usertimers[0].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x20;
+		if (calc->usertimers[1].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x40;
+		if (calc->usertimers[2].status & TILEM_USER_TIMER_FINISHED)
+			v |= 0x80;
+
+		return(v);
+
+	case 0x05:
+		return(calc->hwregs[PORT5] & 0x0f);
+
+	case 0x06:
+		return(calc->hwregs[PORT6]);
+
+	case 0x07:
+		return(calc->hwregs[PORT7]);
+
+	case 0x08:
+		return(calc->hwregs[PORT8]);
+
+	case 0x09:
+		f = tilem_linkport_get_assist_flags(calc);
+
+		if (f & (TILEM_LINK_ASSIST_READ_BUSY
+			 | TILEM_LINK_ASSIST_WRITE_BUSY))
+			v = 0x00;
+		else
+			v = 0x20;
+
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_READ)
+			v |= 0x01;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_IDLE)
+			v |= 0x02;
+		if (calc->z80.interrupts & TILEM_INTERRUPT_LINK_ERROR)
+			v |= 0x04;
+		if (f & TILEM_LINK_ASSIST_READ_BUSY)
+			v |= 0x08;
+		if (f & TILEM_LINK_ASSIST_READ_BYTE)
+			v |= 0x10;
+		if (f & (TILEM_LINK_ASSIST_READ_ERROR
+			 | TILEM_LINK_ASSIST_WRITE_ERROR))
+			v |= 0x40;
+		if (f & TILEM_LINK_ASSIST_WRITE_BUSY)
+			v |= 0x80;
+
+		calc->z80.interrupts &= ~TILEM_INTERRUPT_LINK_ERROR;
+
+		return(v);
+
+	case 0x0A:
+		v = calc->linkport.assistlastbyte;
+		tilem_linkport_read_byte(calc);
+		return(v);
+
+	case 0x0E:
+		return(calc->hwregs[PORTE] & 3);
+
+	case 0x0F:
+		return(calc->hwregs[PORTF] & 3);
+
+	case 0x10:
+	case 0x12:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		return(tilem_lcd_t6a04_status(calc));
+
+	case 0x11:
+	case 0x13:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		return(tilem_lcd_t6a04_read(calc));
+
+	case 0x15:
+		return(0x45);	/* ??? */
+
+	case 0x1C:
+		return(tilem_md5_assist_get_value(calc));
+
+	case 0x1D:
+		return(tilem_md5_assist_get_value(calc) >> 8);
+
+	case 0x1E:
+		return(tilem_md5_assist_get_value(calc) >> 16);
+
+	case 0x1F:
+		return(tilem_md5_assist_get_value(calc) >> 24);
+
+	case 0x20:
+		return(calc->hwregs[PORT20] & 3);
+
+	case 0x21:
+		return(calc->hwregs[PORT21] & 0x33);
+
+	case 0x22:
+		return(calc->hwregs[PORT22]);
+
+	case 0x23:
+		return(calc->hwregs[PORT23]);
+
+	case 0x25:
+		return(calc->hwregs[PORT25]);
+
+	case 0x26:
+		return(calc->hwregs[PORT26]);
+
+	case 0x27:
+		return(calc->hwregs[PORT27]);
+
+	case 0x28:
+		return(calc->hwregs[PORT28]);
+
+	case 0x29:
+		return(calc->hwregs[PORT29]);
+
+	case 0x2A:
+		return(calc->hwregs[PORT2A]);
+
+	case 0x2B:
+		return(calc->hwregs[PORT2B]);
+
+	case 0x2C:
+		return(calc->hwregs[PORT2C]);
+
+	case 0x2D:
+		return(calc->hwregs[PORT2D] & 3);
+
+	case 0x2E:
+		return(calc->hwregs[PORT2E]);
+
+	case 0x2F:
+		return(calc->hwregs[PORT2F]);
+
+	case 0x30:
+		return(calc->usertimers[0].frequency);
+	case 0x31:
+		return(calc->usertimers[0].status);
+	case 0x32:
+		return(tilem_user_timer_get_value(calc, 0));
+
+	case 0x33:
+		return(calc->usertimers[1].frequency);
+	case 0x34:
+		return(calc->usertimers[1].status);
+	case 0x35:
+		return(tilem_user_timer_get_value(calc, 1));
+
+	case 0x36:
+		return(calc->usertimers[2].frequency);
+	case 0x37:
+		return(calc->usertimers[2].status);
+	case 0x38:
+		return(tilem_user_timer_get_value(calc, 2));
+
+	case 0x39:
+		return(0xf0);	/* ??? */
+
+	case 0x40:
+		return calc->hwregs[CLOCK_MODE];
+
+	case 0x41:	
+		return calc->hwregs[CLOCK_INPUT]&0xff;
+
+	case 0x42:
+		return (calc->hwregs[CLOCK_INPUT]>>8)&0xff;
+
+	case 0x43:
+		return (calc->hwregs[CLOCK_INPUT]>>16)&0xff;
+
+	case 0x44:
+		return (calc->hwregs[CLOCK_INPUT]>>24)&0xff;
+
+	case 0x45:
+	case 0x46:
+	case 0x47:
+	case 0x48:
+		if (calc->hwregs[CLOCK_MODE] & 1) {
+			time(&curtime);
+		}
+		else {
+			curtime = 0;
+		}
+		curtime += calc->hwregs[CLOCK_DIFF];
+		return (curtime >> ((port - 0x45) * 8));
+
+	case 0x4C:
+		return(0x22);
+
+	case 0x4D:
+		/* USB port - not emulated, calculator should
+		   recognize that the USB cable is
+		   disconnected.
+
+		   Thanks go to Dan Englender for these
+		   values. */
+
+		return(0xA5);
+
+	case 0x55:
+		return(0x1F);
+
+	case 0x56:
+		return(0x00);
+
+	case 0x57:
+		return(0x50);
+
+	case 0x0B:
+	case 0x0C:
+	case 0x0D:
+	case 0x14:
+	case 0x16:
+	case 0x17:
+	case 0x18:
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+		return(0);
+	}
+
+	tilem_warning(calc, "Input from port %x", port);
+	return(0x00);
+}
+
+
+static void setup_mapping(TilemCalc* calc)
+{
+	unsigned int pageA, pageB, pageC;
+
+	if (calc->hwregs[PORT6] & 0x80)
+		pageA = (0x80 | (calc->hwregs[PORT6] & 7));
+	else
+		pageA = (calc->hwregs[PORT6] & 0x7f);
+
+	if (calc->hwregs[PORT7] & 0x80)
+		pageB = (0x80 | (calc->hwregs[PORT7] & 7));
+	else
+		pageB = (calc->hwregs[PORT7] & 0x7f);
+
+	pageC = (0x80 | (calc->hwregs[PORT5] & 7));
+
+	if (calc->hwregs[PORT4] & 1) {
+		calc->mempagemap[1] = (pageA & ~1);
+		calc->mempagemap[2] = (pageA | 1);
+		calc->mempagemap[3] = pageB;
+	}
+	else {
+		calc->mempagemap[1] = pageA;
+		calc->mempagemap[2] = pageB;
+		calc->mempagemap[3] = pageC;
+	}
+}
+
+static void setup_clockdelays(TilemCalc* calc)
+{
+	byte lcdport = calc->hwregs[PORT29 + (calc->hwregs[PORT20] & 3)];
+	byte memport = calc->hwregs[PORT2E];
+
+	if (!(lcdport & 1))
+		memport &= ~0x07;
+	if (!(lcdport & 2))
+		memport &= ~0x70;
+
+	calc->hwregs[FLASH_EXEC_DELAY] = (memport & 1);
+	calc->hwregs[FLASH_READ_DELAY] = ((memport >> 1) & 1);
+	calc->hwregs[FLASH_WRITE_DELAY] = ((memport >> 2) & 1);
+
+	calc->hwregs[RAM_EXEC_DELAY] = ((memport >> 4) & 1);
+	calc->hwregs[RAM_READ_DELAY] = ((memport >> 5) & 1);
+	calc->hwregs[RAM_WRITE_DELAY] = ((memport >> 6) & 1);
+
+	calc->hwregs[LCD_PORT_DELAY] = (lcdport >> 2);
+}
+
+void xz_z80_out(TilemCalc* calc, dword port, byte value)
+{
+	static const int tmrvalues[4] = { 1953, 4395, 6836, 9277 };
+	int t, r;
+	unsigned int mode;
+	time_t curtime;
+
+	switch(port&0xff) {
+	case 0x00:
+		tilem_linkport_set_lines(calc, value);
+		break;
+
+	case 0x01:
+		tilem_keypad_set_group(calc, value);
+		break;
+
+	case 0x03:
+		if (value & 0x01) {
+			calc->keypad.onkeyint = 1;
+		}
+		else {
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_ON_KEY;
+			calc->keypad.onkeyint = 0;
+		}
+
+		if (!(value & 0x02))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER1;
+
+		if (!(value & 0x04))
+			calc->z80.interrupts &= ~TILEM_INTERRUPT_TIMER2;
+
+		if (value & 0x06) {
+			calc->usertimers[0].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[1].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[2].status &= ~TILEM_USER_TIMER_NO_HALT_INT;
+		}
+		else {
+			calc->usertimers[0].status |= TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[1].status |= TILEM_USER_TIMER_NO_HALT_INT;
+			calc->usertimers[2].status |= TILEM_USER_TIMER_NO_HALT_INT;
+		}
+
+		mode = calc->linkport.mode;
+		if (value & 0x10)
+			mode |= TILEM_LINK_MODE_INT_ON_ACTIVE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ACTIVE;
+
+		tilem_linkport_set_mode(calc, mode);
+
+		calc->poweronhalt = ((value & 8) >> 3);
+		calc->hwregs[PORT3] = value;
+		break;
+
+
+	case 0x04:
+		calc->hwregs[PORT4] = value;
+
+		t = tmrvalues[(value & 6) >> 1];
+		tilem_z80_set_timer_period(calc, TIMER_INT1, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2A, t);
+		tilem_z80_set_timer_period(calc, TIMER_INT2B, t);
+
+		setup_mapping(calc);
+		break;
+	
+	case 0x05:
+		calc->hwregs[PORT5] = value & 0x0f;
+		setup_mapping(calc);
+		break;
+
+	case 0x06:
+		calc->hwregs[PORT6] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x07:
+		calc->hwregs[PORT7] = value;
+		setup_mapping(calc);
+		break;
+
+	case 0x08:
+		calc->hwregs[PORT8] = value;
+
+		mode = calc->linkport.mode;
+
+		if (value & 0x01)
+			mode |= TILEM_LINK_MODE_INT_ON_READ;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_READ;
+
+		if (value & 0x02)
+			mode |= TILEM_LINK_MODE_INT_ON_IDLE;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_IDLE;
+
+		if (value & 0x04)
+			mode |= TILEM_LINK_MODE_INT_ON_ERROR;
+		else
+			mode &= ~TILEM_LINK_MODE_INT_ON_ERROR;
+
+		if (value & 0x80)
+			mode &= ~TILEM_LINK_MODE_ASSIST;
+		else
+			mode |= TILEM_LINK_MODE_ASSIST;
+
+		tilem_linkport_set_mode(calc, mode);
+		break;
+
+	case 0x09:
+		calc->hwregs[PORT9] = value;
+		break;
+
+	case 0x0A:
+		calc->hwregs[PORTA] = value;
+		break;
+
+	case 0x0B:
+		calc->hwregs[PORTB] = value;
+		break;
+
+	case 0x0C:
+		calc->hwregs[PORTC] = value;
+		break;
+
+
+	case 0x0D:
+		if (!(calc->hwregs[PORT8] & 0x80))
+			tilem_linkport_write_byte(calc, value);
+		break;
+
+	case 0x0E:
+		calc->hwregs[PORTE] = value;
+		break;
+
+	case 0x0F:
+		calc->hwregs[PORTF] = value;
+		break;
+
+	case 0x10:
+	case 0x12:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		tilem_lcd_t6a04_control(calc, value);
+		break;
+
+	case 0x11:
+	case 0x13:
+		calc->z80.clock += calc->hwregs[LCD_PORT_DELAY];
+		set_lcd_wait_timer(calc);
+		tilem_lcd_t6a04_write(calc, value);
+		break;
+
+	case 0x14:
+		if (calc->hwregs[PROTECTSTATE] == 7) {
+			if (value & 1)
+				tilem_message(calc, "Flash unlocked");
+			else
+				tilem_message(calc, "Flash locked");
+			calc->flash.unlock = value&1;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 14");
+		}
+		break;
+
+	case 0x18:
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+	case 0x1C:
+	case 0x1D:
+		r = (port & 0xff) - 0x18;
+		calc->md5assist.regs[r] >>= 8;
+		calc->md5assist.regs[r] |= (value << 24);
+		break;
+
+	case 0x1E:
+		calc->md5assist.shift = value & 0x1f;
+		break;
+
+	case 0x1F:
+		calc->md5assist.mode = value & 3;
+		break;
+
+	case 0x20:
+		calc->hwregs[PORT20] = value;
+
+		if (value & 3) {
+			tilem_z80_set_speed(calc, 15000);
+		}
+		else {
+			tilem_z80_set_speed(calc, 6000);
+		}
+
+		setup_clockdelays(calc);
+		break;
+
+	case 0x21:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT21] = value;
+			t = (value >> 4) & 3;
+			calc->hwregs[NO_EXEC_RAM_MASK] = (0x8000 << t) - 0x400;
+			calc->flash.overridegroup = value & 3;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 21");
+		}
+		break;
+
+	case 0x22:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT22] = value;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 22");
+		}
+		break;
+
+	case 0x23:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT23] = value;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 23");
+		}
+		break;
+
+	case 0x25:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT25] = value;
+			calc->hwregs[NO_EXEC_RAM_LOWER] = value * 0x400;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 25");
+		}
+		break;
+
+	case 0x26:
+		if (calc->flash.unlock) {
+			calc->hwregs[PORT26] = value;
+			calc->hwregs[NO_EXEC_RAM_UPPER] = value * 0x400;
+		}
+		else {
+			tilem_warning(calc, "Writing to protected port 26");
+		}
+		break;
+
+	case 0x27:
+		calc->hwregs[PORT27] = value;
+		break;
+
+	case 0x28:
+		calc->hwregs[PORT28] = value;
+		break;
+
+	case 0x29:
+		calc->hwregs[PORT29] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2A:
+		calc->hwregs[PORT2A] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2B:
+		calc->hwregs[PORT2B] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2C:
+		calc->hwregs[PORT2C] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2D:
+		calc->hwregs[PORT2D] = value;
+		break;
+
+	case 0x2E:
+		calc->hwregs[PORT2E] = value;
+		setup_clockdelays(calc);
+		break;
+
+	case 0x2F:
+		calc->hwregs[PORT2F] = value;
+		break;
+
+	case 0x30:
+		tilem_user_timer_set_frequency(calc, 0, value);
+		break;
+	case 0x31:
+		tilem_user_timer_set_mode(calc, 0, value);
+		break;
+	case 0x32:
+		tilem_user_timer_start(calc, 0, value);
+		break;
+
+	case 0x33:
+		tilem_user_timer_set_frequency(calc, 1, value);
+		break;
+	case 0x34:
+		tilem_user_timer_set_mode(calc, 1, value);
+		break;
+	case 0x35:
+		tilem_user_timer_start(calc, 1, value);
+		break;
+
+	case 0x36:
+		tilem_user_timer_set_frequency(calc, 2, value);
+		break;
+	case 0x37:
+		tilem_user_timer_set_mode(calc, 2, value);
+		break;
+	case 0x38:
+		tilem_user_timer_start(calc, 2, value);
+		break;
+
+	case 0x40:
+		time(&curtime);
+
+		if ((calc->hwregs[CLOCK_MODE] & 1) != (value & 1)) {
+			if (value & 1)
+				calc->hwregs[CLOCK_DIFF] -= curtime;
+			else
+				calc->hwregs[CLOCK_DIFF] += curtime;
+		}
+
+		if (!(calc->hwregs[CLOCK_MODE] & 2) && (value & 2)) {
+			calc->hwregs[CLOCK_DIFF] = calc->hwregs[CLOCK_INPUT];
+			if (value & 1)
+				calc->hwregs[CLOCK_DIFF] -= curtime;
+		}
+		calc->hwregs[CLOCK_MODE] = value & 3;
+		break;
+
+	case 0x41:
+		calc->hwregs[CLOCK_INPUT] &= 0xffffff00;
+		calc->hwregs[CLOCK_INPUT] |= value;
+		break;
+
+	case 0x42:
+		calc->hwregs[CLOCK_INPUT] &= 0xffff00ff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 8);
+		break;
+
+	case 0x43:
+		calc->hwregs[CLOCK_INPUT] &= 0xff00ffff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 16);
+		break;
+
+	case 0x44:
+		calc->hwregs[CLOCK_INPUT] &= 0x00ffffff;
+		calc->hwregs[CLOCK_INPUT] |= (value << 24);
+		break;
+	}
+
+	return;
+}
+
+void xz_z80_ptimer(TilemCalc* calc, int id)
+{
+	switch (id) {
+	case TIMER_INT1:
+		if (calc->hwregs[PORT3] & 0x02)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER1;
+		break;
+
+	case TIMER_INT2A:
+	case TIMER_INT2B:
+		if (calc->hwregs[PORT3] & 0x04)
+			calc->z80.interrupts |= TILEM_INTERRUPT_TIMER2;
+		break;
+
+	case TIMER_LCD_WAIT:
+		calc->hwregs[LCD_WAIT] = 0;
+		break;
+	}
+}
diff --git a/tool/tilem-src/emu/xz/xz_memory.c b/tool/tilem-src/emu/xz/xz_memory.c
new file mode 100644
index 0000000..a8f083c
--- /dev/null
+++ b/tool/tilem-src/emu/xz/xz_memory.c
@@ -0,0 +1,207 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xz.h"
+
+void xz_z80_wrmem(TilemCalc* calc, dword A, byte v)
+{
+	unsigned long pa;
+	byte page;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x200000) {
+		calc->z80.clock += calc->hwregs[FLASH_WRITE_DELAY];
+		tilem_flash_write_byte(calc, pa, v);
+	}
+	else if (pa < 0x220000) {
+		calc->z80.clock += calc->hwregs[RAM_WRITE_DELAY];
+		*(calc->mem+pa) = v;
+	}
+}
+
+static inline byte readbyte(TilemCalc* calc, dword pa)
+{
+	static const byte protectbytes[6] = {0x00,0x00,0xed,0x56,0xf3,0xd3};
+	int state = calc->hwregs[PROTECTSTATE];
+	byte value;
+
+	if (pa < 0x200000 && (calc->flash.state || calc->flash.busy))
+		value = tilem_flash_read_byte(calc, pa);
+	else
+		value = *(calc->mem + pa);
+
+	if (pa < 0x1B0000 || pa >= 0x200000
+	    || (pa >= 0x1C0000 && pa < 0x1F0000))
+		calc->hwregs[PROTECTSTATE] = 0;
+	else if (state == 6)
+		calc->hwregs[PROTECTSTATE] = 7;
+	else if (state < 6 && value == protectbytes[state])
+		calc->hwregs[PROTECTSTATE] = state + 1;
+	else
+		calc->hwregs[PROTECTSTATE] = 0;
+
+	return (value);
+}
+
+byte xz_z80_rdmem(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	if (TILEM_UNLIKELY(page == 0x7E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x200000)
+		calc->z80.clock += calc->hwregs[FLASH_READ_DELAY];
+	else
+		calc->z80.clock += calc->hwregs[RAM_READ_DELAY];
+
+	value = readbyte(calc, pa);
+	return (value);
+}
+
+byte xz_z80_rdmem_m1(TilemCalc* calc, dword A)
+{
+	byte page;
+	unsigned long pa, m;
+	byte value;
+
+	page = calc->mempagemap[A>>14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	if (TILEM_UNLIKELY(page == 0x7E && !calc->flash.unlock)) {
+		tilem_warning(calc, "Reading from read-protected sector");
+		return (0xff);
+	}
+
+	pa = (A & 0x3FFF) + 0x4000L*page;
+
+	if (pa < 0x200000) {
+		calc->z80.clock += calc->hwregs[FLASH_EXEC_DELAY];
+
+		if (TILEM_UNLIKELY(page >= calc->hwregs[PORT22]
+		                   && page <= calc->hwregs[PORT23])) {
+			tilem_warning(calc, "Executing in restricted Flash area");
+			tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+		}
+	}
+	else {
+		calc->z80.clock += calc->hwregs[RAM_EXEC_DELAY];
+
+		m = pa & calc->hwregs[NO_EXEC_RAM_MASK];
+		if (TILEM_UNLIKELY(m < calc->hwregs[NO_EXEC_RAM_LOWER]
+		                   || m > calc->hwregs[NO_EXEC_RAM_UPPER])) {
+			tilem_warning(calc, "Executing in restricted RAM area");
+			tilem_z80_exception(calc, TILEM_EXC_RAM_EXEC);
+		}
+	}
+
+	value = readbyte(calc, pa);
+
+	if (TILEM_UNLIKELY(value == 0xff && A == 0x0038)) {
+		tilem_warning(calc, "No OS installed");
+		tilem_z80_exception(calc, TILEM_EXC_FLASH_EXEC);
+	}
+
+	return (value);
+}
+
+dword xz_mem_ltop(TilemCalc* calc, dword A)
+{
+	byte page = calc->mempagemap[A >> 14];
+
+	if (A & 0x8000) {
+		if (A > (0xFFFF - 64 * calc->hwregs[PORT27]))
+			page = 0x80;
+		else if (A < (0x8000 + 64 * calc->hwregs[PORT28]))
+			page = 0x81;
+	}
+
+	return ((page << 14) | (A & 0x3fff));
+}
+
+dword xz_mem_ptol(TilemCalc* calc, dword A)
+{
+	byte page = A >> 14;
+
+	if (!page)
+		return (A & 0x3fff);
+
+	if (page == calc->mempagemap[1])
+		return (0x4000 | (A & 0x3fff));
+
+	if ((A & 0x3fff) < 64 * calc->hwregs[PORT28]) {
+		if (page == 0x81)
+			return (0x8000 | (A & 0x3fff));
+	}
+	else {
+		if (page == calc->mempagemap[2])
+			return (0x8000 | (A & 0x3fff));
+	}
+
+	if ((A & 0x3fff) >= (0x4000 - 64 * calc->hwregs[PORT27])) {
+		if (page == 0x80)
+			return (0xC000 | (A & 0x3fff));
+	}
+	else {
+		if (page == calc->mempagemap[3])
+			return (0xC000 | (A & 0x3fff));
+	}
+
+	return (0xffffffff);
+}
diff --git a/tool/tilem-src/emu/xz/xz_subcore.c b/tool/tilem-src/emu/xz/xz_subcore.c
new file mode 100644
index 0000000..3692447
--- /dev/null
+++ b/tool/tilem-src/emu/xz/xz_subcore.c
@@ -0,0 +1,73 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2001 Solignac Julien
+ * Copyright (C) 2004-2012 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <tilem.h>
+
+#include "xz.h"
+
+static const TilemFlashSector flashsectors[] = {
+	{0x000000, 0x10000, 0}, {0x010000, 0x10000, 0},
+	{0x020000, 0x10000, 0},	{0x030000, 0x10000, 0},
+	{0x040000, 0x10000, 0},	{0x050000, 0x10000, 0},
+	{0x060000, 0x10000, 0},	{0x070000, 0x10000, 0},
+	{0x080000, 0x10000, 0},	{0x090000, 0x10000, 0},
+	{0x0A0000, 0x10000, 0},	{0x0B0000, 0x10000, 0},
+	{0x0C0000, 0x10000, 0},	{0x0D0000, 0x10000, 0},
+	{0x0E0000, 0x10000, 0},	{0x0F0000, 0x10000, 0},
+	{0x100000, 0x10000, 0},	{0x110000, 0x10000, 0},
+	{0x120000, 0x10000, 0},	{0x130000, 0x10000, 0},
+	{0x140000, 0x10000, 0},	{0x150000, 0x10000, 0},
+	{0x160000, 0x10000, 0},	{0x170000, 0x10000, 0},
+	{0x180000, 0x10000, 0}, {0x190000, 0x10000, 0},
+	{0x1A0000, 0x10000, 0}, {0x1B0000, 0x10000, 2},
+	{0x1C0000, 0x10000, 0}, {0x1D0000, 0x10000, 0},
+	{0x1E0000, 0x10000, 0}, {0x1F0000, 0x08000, 0},
+	{0x1F8000, 0x02000, 0}, {0x1FA000, 0x02000, 0},
+	{0x1FC000, 0x04000, 2}};
+
+#define NUM_FLASH_SECTORS (sizeof(flashsectors) / sizeof(TilemFlashSector))
+
+static const char* hwregnames[NUM_HW_REGS] = HW_REG_NAMES;
+
+static const char* hwtimernames[NUM_HW_TIMERS] = HW_TIMER_NAMES;
+
+extern const char* xp_keynames[];
+
+TilemHardware hardware_ti84pse = {
+	'z', "ti84pse", "TI-84 Plus Silver Edition",
+	(TILEM_CALC_HAS_LINK | TILEM_CALC_HAS_LINK_ASSIST
+	 | TILEM_CALC_HAS_T6A04 | TILEM_CALC_HAS_FLASH
+	 | TILEM_CALC_HAS_MD5_ASSIST),
+	96, 64, 128 * 0x4000, 8 * 0x4000, 16 * 64, 0x80,
+	NUM_FLASH_SECTORS, flashsectors, 3,
+	NUM_HW_REGS, hwregnames,
+	NUM_HW_TIMERS, hwtimernames,
+	xp_keynames,
+	xz_reset, xz_stateloaded,
+	xz_z80_in, xz_z80_out,
+	xz_z80_wrmem, xz_z80_rdmem, xz_z80_rdmem_m1, NULL,
+	xz_z80_ptimer, tilem_lcd_t6a04_get_data,
+	xz_mem_ltop, xz_mem_ptol };
diff --git a/tool/tilem-src/emu/z80.c b/tool/tilem-src/emu/z80.c
new file mode 100644
index 0000000..51399ba
--- /dev/null
+++ b/tool/tilem-src/emu/z80.c
@@ -0,0 +1,990 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "tilem.h"
+#include "z80.h"
+
+/* Timer manipulation */
+
+/*
+static void dumptimers(TilemZ80* z80)
+{
+	int tmr;
+	int t;
+
+	printf("*** RT:");
+	for (tmr = z80->timer_rt; tmr; tmr = z80->timers[tmr].next) {
+		t = z80->timers[tmr].count - z80->clock;
+		printf(" %d:%d", tmr, t);
+	}
+	printf("\n*** CPU:");
+	for (tmr = z80->timer_cpu; tmr; tmr = z80->timers[tmr].next) {
+		t = z80->timers[tmr].count - z80->clock;
+		printf(" %d:%d", tmr, t);
+	}
+	printf("\n*** Free:");
+	for (tmr = z80->timer_free; tmr; tmr = z80->timers[tmr].next) {
+		printf(" %d", tmr);
+	}
+	printf("\n");
+}
+*/
+
+static inline void timer_free(TilemZ80* z80, int tmr)
+{
+	z80->timers[tmr].callback = NULL;
+	z80->timers[tmr].callbackdata = NULL;
+	z80->timers[tmr].next = z80->timer_free;
+	z80->timers[tmr].prev = 0;
+	z80->timer_free = tmr;
+}
+
+static inline int timer_alloc(TilemZ80* z80)
+{
+	int tmr, i;
+
+	if (z80->timer_free) {
+		tmr = z80->timer_free;
+		z80->timer_free = z80->timers[tmr].next;
+		z80->timers[tmr].next = 0;
+		return tmr;
+	}
+
+	i = z80->ntimers;
+	z80->ntimers = i * 2 + 1;
+	z80->timers = tilem_renew(TilemZ80Timer, z80->timers, z80->ntimers);
+	while (i < z80->ntimers) {
+		timer_free(z80, i);
+		i++;
+	}
+
+	tmr = z80->timer_free;
+	z80->timer_free = z80->timers[tmr].next;
+	z80->timers[tmr].next = 0;
+	return tmr;
+}
+
+static inline int timer_earlier(TilemZ80* z80, int tmr1, int tmr2)
+{
+	dword count1, count2;
+
+	count1 = z80->timers[tmr1].count + 10000 - z80->clock;
+	count2 = z80->timers[tmr2].count + 10000 - z80->clock;
+
+	return (count1 < count2);
+}
+
+static inline void timer_insert(TilemZ80* z80, int* list, int tmr)
+{
+	int prev, next;
+
+	if (!*list || timer_earlier(z80, tmr, *list)) {
+		z80->timers[tmr].prev = 0;
+		z80->timers[tmr].next = *list;
+		z80->timers[*list].prev = tmr;
+		*list = tmr;
+		return;
+	}
+
+	prev = *list;
+	next = z80->timers[prev].next;
+
+	while (next && timer_earlier(z80, next, tmr)) {
+		prev = next;
+		next = z80->timers[prev].next;
+	}
+
+	z80->timers[prev].next = tmr;
+	z80->timers[next].prev = tmr;
+	z80->timers[tmr].prev = prev;
+	z80->timers[tmr].next = next;
+}
+
+static inline void timer_set(TilemZ80* z80, int tmr, dword count,
+			     dword period, int rt, dword extra)
+{
+	dword clocks;
+	qword kclocks;
+
+	if (!count) {
+		/* leave timer disabled */
+		z80->timers[tmr].prev = 0;
+		z80->timers[tmr].next = 0;
+	}
+	else if (rt) {
+		kclocks = z80->clockspeed;
+		kclocks *= count;
+		clocks = (kclocks + 500) / 1000 - extra;
+		z80->timers[tmr].count = z80->clock + clocks;
+		z80->timers[tmr].period = period;
+		timer_insert(z80, &z80->timer_rt, tmr);
+	}
+	else {
+		clocks = count - extra;
+		z80->timers[tmr].count = z80->clock + clocks;
+		z80->timers[tmr].period = period;
+		timer_insert(z80, &z80->timer_cpu, tmr);
+	}
+}
+
+static inline void timer_unset(TilemZ80* z80, int tmr)
+{
+	int prev, next;
+
+	if (tmr == z80->timer_cpu)
+		z80->timer_cpu = z80->timers[tmr].next;
+	if (tmr == z80->timer_rt)
+		z80->timer_rt = z80->timers[tmr].next;
+
+	prev = z80->timers[tmr].prev;
+	next = z80->timers[tmr].next;
+	z80->timers[prev].next = next;
+	z80->timers[next].prev = prev;
+	z80->timers[tmr].prev = 0;
+	z80->timers[tmr].next = 0;
+}
+
+
+/* Breakpoint manipulation */
+
+static inline void bp_free(TilemZ80* z80, int bp)
+{
+	z80->breakpoints[bp].type = 0;
+	z80->breakpoints[bp].testfunc = NULL;
+	z80->breakpoints[bp].testdata = NULL;
+	z80->breakpoints[bp].next = z80->breakpoint_free;
+	z80->breakpoints[bp].prev = 0;
+	z80->breakpoint_free = bp;
+}
+
+static inline int bp_alloc(TilemZ80* z80)
+{
+	int bp, i;
+
+	if (z80->breakpoint_free) {
+		bp = z80->breakpoint_free;
+		z80->breakpoint_free = z80->breakpoints[bp].next;
+		return bp;
+	}
+
+	i = z80->nbreakpoints;
+	z80->nbreakpoints = i * 2 + 2;
+	z80->breakpoints = tilem_renew(TilemZ80Breakpoint, z80->breakpoints,
+				       z80->nbreakpoints);
+	while (i < z80->nbreakpoints) {
+		bp_free(z80, i);
+		i++;
+	}
+
+	bp = z80->breakpoint_free;
+	z80->breakpoint_free = z80->breakpoints[bp].next;
+	return bp;
+}
+
+static int* bp_head(TilemCalc *calc, int type)
+{
+	if (type & TILEM_BREAK_DISABLED)
+		return &calc->z80.breakpoint_disabled;
+
+	switch (type & TILEM_BREAK_TYPE_MASK) {
+	case TILEM_BREAK_MEM_READ:
+		return (type & TILEM_BREAK_PHYSICAL
+			? &calc->z80.breakpoint_mpr
+			: &calc->z80.breakpoint_mr);
+
+	case TILEM_BREAK_MEM_EXEC:
+		return (type & TILEM_BREAK_PHYSICAL
+			? &calc->z80.breakpoint_mpx
+			: &calc->z80.breakpoint_mx);
+
+	case TILEM_BREAK_MEM_WRITE:
+		return (type & TILEM_BREAK_PHYSICAL
+			? &calc->z80.breakpoint_mpw
+			: &calc->z80.breakpoint_mw);
+
+	case TILEM_BREAK_PORT_READ:
+		return &calc->z80.breakpoint_pr;
+
+	case TILEM_BREAK_PORT_WRITE:
+		return &calc->z80.breakpoint_pw;
+
+	case TILEM_BREAK_EXECUTE:
+		return &calc->z80.breakpoint_op;
+
+	default:
+		tilem_internal(calc, "invalid bp type");
+		return 0;
+	}
+}
+
+static int bp_add(TilemCalc *calc, int bp, int type)
+{
+	int *head = bp_head(calc, type);
+
+	if (!head) {
+		bp_free(&calc->z80, bp);
+		return 0;
+	}
+
+	calc->z80.breakpoints[bp].next = *head;
+	calc->z80.breakpoints[*head].prev = *head ? bp : 0;
+	*head = bp;
+
+	return bp;
+}
+
+static void bp_rem(TilemCalc *calc, int bp, int type)
+{
+	int prev, next;
+	int *head = bp_head(calc, type);
+
+	prev = calc->z80.breakpoints[bp].prev;
+	next = calc->z80.breakpoints[bp].next;
+
+	if (bp == *head)
+		*head = next;
+
+	calc->z80.breakpoints[prev].next = prev ? next : 0;
+	calc->z80.breakpoints[next].prev = next ? prev : 0;
+}
+
+static void invoke_ptimer(TilemCalc* calc, void* data)
+{
+	(*calc->hw.z80_ptimer)(calc, TILEM_PTR_TO_DWORD(data));
+}
+
+/* Z80 API */
+
+void tilem_z80_reset(TilemCalc* calc)
+{
+	int i;
+
+	AF = BC = DE = HL = AF2 = BC2 = DE2 = HL2 = 0xffff;
+	IX = IY = IR = SP = WZ = WZ2 = 0xffff;
+	PC = 0;
+	Rh = 0x80;
+	IFF1 = IFF2 = IM = 0;
+	calc->z80.interrupts = 0;
+	calc->z80.halted = 0;
+
+	/* Set up hardware timers */
+	if (!calc->z80.ntimers) {
+		calc->z80.ntimers = (calc->hw.nhwtimers
+				+ TILEM_NUM_SYS_TIMERS + 1);
+		tilem_free(calc->z80.timers);
+		calc->z80.timers = tilem_new(TilemZ80Timer, calc->z80.ntimers);
+
+		for (i = 1; i < calc->z80.ntimers; i++) {
+			calc->z80.timers[i].next = 0;
+			calc->z80.timers[i].prev = 0;
+			calc->z80.timers[i].count = 0;
+			calc->z80.timers[i].period = 0;
+			calc->z80.timers[i].callback = &invoke_ptimer;
+			calc->z80.timers[i].callbackdata = TILEM_DWORD_TO_PTR(i);
+		}
+
+		calc->z80.timers[TILEM_TIMER_LCD_DELAY].callback
+			= &tilem_lcd_delay_timer;
+		calc->z80.timers[TILEM_TIMER_FLASH_DELAY].callback
+			= tilem_flash_delay_timer;
+		calc->z80.timers[TILEM_TIMER_LINK_ASSIST].callback
+			= tilem_linkport_assist_timer;
+
+		for (i = 0; i < TILEM_MAX_USER_TIMERS; i++) {
+			calc->z80.timers[TILEM_TIMER_USER1 + i].callback
+				= tilem_user_timer_expired;
+			calc->z80.timers[TILEM_TIMER_USER1 + i].callbackdata
+				= TILEM_DWORD_TO_PTR(i);
+		}
+	}
+}
+
+void tilem_z80_stop(TilemCalc* calc, dword reason)
+{
+	if (!(reason & calc->z80.stop_mask)) {
+		calc->z80.stop_reason |= reason;
+		calc->z80.stopping = 1;
+	}
+}
+
+void tilem_z80_exception(TilemCalc* calc, unsigned type)
+{
+	calc->z80.exception |= type;
+}
+
+void tilem_z80_set_speed(TilemCalc* calc, int speed)
+{
+	int tmr;
+	qword t;
+	int oldspeed = calc->z80.clockspeed;
+
+	if (oldspeed == speed)
+		return;
+
+	for (tmr = calc->z80.timer_rt; tmr; tmr = calc->z80.timers[tmr].next) {
+		if ((calc->z80.clock - calc->z80.timers[tmr].count) < 10000)
+			continue;
+
+		t = calc->z80.timers[tmr].count - calc->z80.clock;
+		t = (t * speed + oldspeed / 2) / oldspeed;
+		calc->z80.timers[tmr].count = calc->z80.clock + t;
+	}
+
+	calc->z80.clockspeed = speed;
+}
+
+int tilem_z80_add_timer(TilemCalc* calc, dword count, dword period,
+			int rt, TilemZ80TimerFunc func, void* data)
+{
+	int id;
+
+	id = timer_alloc(&calc->z80);
+	calc->z80.timers[id].callback = func;
+	calc->z80.timers[id].callbackdata = data;
+	timer_set(&calc->z80, id, count, period, rt, 0);
+	return id;
+}
+
+void tilem_z80_set_timer(TilemCalc* calc, int id, dword count,
+			 dword period, int rt)
+{
+	if (id < 1 || id > calc->z80.ntimers
+	    || !calc->z80.timers[id].callback) {
+		tilem_internal(calc, "setting invalid timer %d", id);
+		return;
+	}
+	timer_unset(&calc->z80, id);
+	timer_set(&calc->z80, id, count, period, rt, 0);
+}
+
+void tilem_z80_set_timer_period(TilemCalc* calc, int id, dword period)
+{
+	if (id < 1 || id > calc->z80.ntimers
+	    || !calc->z80.timers[id].callback) {
+		tilem_internal(calc, "setting invalid timer %d", id);
+		return;
+	}
+
+	calc->z80.timers[id].period = period;
+}
+
+void tilem_z80_remove_timer(TilemCalc* calc, int id)
+{
+	if (id <= calc->hw.nhwtimers + TILEM_NUM_SYS_TIMERS
+	    || id > calc->z80.ntimers || !calc->z80.timers[id].callback) {
+		tilem_internal(calc, "removing invalid timer %d", id);
+		return;
+	}
+	timer_unset(&calc->z80, id);
+	timer_free(&calc->z80, id);
+}
+
+int tilem_z80_timer_running(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.ntimers
+	    || !calc->z80.timers[id].callback) {
+		tilem_internal(calc, "querying invalid timer %d", id);
+		return 0;
+	}
+
+	if (id == calc->z80.timer_rt || id == calc->z80.timer_cpu)
+		return 1;
+	if (calc->z80.timers[id].prev)
+		return 1;
+	return 0;
+}
+
+int tilem_z80_get_timer_clocks(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.ntimers
+	    || !calc->z80.timers[id].callback) {
+		tilem_internal(calc, "querying invalid timer %d", id);
+		return 0;
+	}
+	return (calc->z80.timers[id].count - calc->z80.clock);
+}
+
+int tilem_z80_get_timer_microseconds(TilemCalc* calc, int id)
+{
+	int n = tilem_z80_get_timer_clocks(calc, id);
+
+	if (n < 0) {
+		n = ((((qword) -n * 1000) + (calc->z80.clockspeed / 2))
+		     / calc->z80.clockspeed);
+		return -n;
+	}
+	else {
+		n = ((((qword) n * 1000) + (calc->z80.clockspeed / 2))
+		     / calc->z80.clockspeed);
+		return n;
+	}
+}
+
+int tilem_z80_add_breakpoint(TilemCalc* calc, int type,
+			     dword start, dword end, dword mask,
+			     TilemZ80BreakpointFunc func,
+			     void* data)
+{
+	int bp;
+
+	bp = bp_alloc(&calc->z80);
+
+	calc->z80.breakpoints[bp].type = type;
+	calc->z80.breakpoints[bp].start = start;
+	calc->z80.breakpoints[bp].end = end;
+	calc->z80.breakpoints[bp].mask = mask;
+	calc->z80.breakpoints[bp].testfunc = func;
+	calc->z80.breakpoints[bp].testdata = data;
+	calc->z80.breakpoints[bp].prev = 0;
+
+	return bp_add(calc, bp, type);
+}
+
+void tilem_z80_remove_breakpoint(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to remove invalid breakpoint %d", id);
+		return;
+	}
+
+	bp_rem(calc, id, calc->z80.breakpoints[id].type);
+
+	bp_free(&calc->z80, id);
+}
+
+void tilem_z80_enable_breakpoint(TilemCalc* calc, int id)
+{
+	int type = tilem_z80_get_breakpoint_type(calc, id);
+	tilem_z80_set_breakpoint_type(calc, id, type & ~TILEM_BREAK_DISABLED);
+}
+
+void tilem_z80_disable_breakpoint(TilemCalc* calc, int id)
+{
+	int type = tilem_z80_get_breakpoint_type(calc, id);
+	tilem_z80_set_breakpoint_type(calc, id, type | TILEM_BREAK_DISABLED);
+}
+
+int tilem_z80_breakpoint_enabled(TilemCalc* calc, int id)
+{
+	int type = tilem_z80_get_breakpoint_type(calc, id);
+	return !(type & TILEM_BREAK_DISABLED);
+}
+
+int tilem_z80_get_breakpoint_type(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to access invalid breakpoint %d", id);
+		return -1;
+	}
+
+	return calc->z80.breakpoints[id].type;
+}
+
+dword tilem_z80_get_breakpoint_address_start(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to access invalid breakpoint %d", id);
+		return -1;
+	}
+
+	return calc->z80.breakpoints[id].start;
+}
+
+dword tilem_z80_get_breakpoint_address_end(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to access invalid breakpoint %d", id);
+		return -1;
+	}
+
+	return calc->z80.breakpoints[id].end;
+}
+
+dword tilem_z80_get_breakpoint_address_mask(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to access invalid breakpoint %d", id);
+		return -1;
+	}
+
+	return calc->z80.breakpoints[id].mask;
+}
+
+TilemZ80BreakpointFunc tilem_z80_get_breakpoint_callback(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to access invalid breakpoint %d", id);
+		return 0;
+	}
+
+	return calc->z80.breakpoints[id].testfunc;
+}
+
+void* tilem_z80_get_breakpoint_data(TilemCalc* calc, int id)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to access invalid breakpoint %d", id);
+		return 0;
+	}
+
+	return calc->z80.breakpoints[id].testdata;
+}
+
+void tilem_z80_set_breakpoint_type(TilemCalc* calc, int id, int type)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to modify invalid breakpoint %d", id);
+		return;
+	}
+
+	if (type == calc->z80.breakpoints[id].type)
+		return;
+
+	bp_rem(calc, id, calc->z80.breakpoints[id].type);
+
+	calc->z80.breakpoints[id].type = type;
+
+	bp_add(calc, id, type);
+}
+
+void tilem_z80_set_breakpoint_address_start(TilemCalc* calc, int id, dword start)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to modify invalid breakpoint %d", id);
+		return;
+	}
+
+	calc->z80.breakpoints[id].start = start;
+}
+
+void tilem_z80_set_breakpoint_address_end(TilemCalc* calc, int id, dword end)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to modify invalid breakpoint %d", id);
+		return;
+	}
+
+	calc->z80.breakpoints[id].end = end;
+}
+
+void tilem_z80_set_breakpoint_address_mask(TilemCalc* calc, int id, dword mask)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to modify invalid breakpoint %d", id);
+		return;
+	}
+
+	calc->z80.breakpoints[id].mask = mask;
+}
+
+void tilem_z80_set_breakpoint_callback(TilemCalc* calc, int id,
+				       TilemZ80BreakpointFunc func)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to modify invalid breakpoint %d", id);
+		return;
+	}
+
+	calc->z80.breakpoints[id].testfunc = func;
+}
+
+void tilem_z80_set_breakpoint_data(TilemCalc* calc, int id, void* data)
+{
+	if (id < 1 || id > calc->z80.nbreakpoints
+	    || !calc->z80.breakpoints[id].type) {
+		tilem_internal(calc,
+			       "attempt to modify invalid breakpoint %d", id);
+		return;
+	}
+
+	calc->z80.breakpoints[id].testdata = data;
+}
+
+
+static inline void check_timers(TilemCalc* calc)
+{
+	int tmr;
+	dword t;
+	TilemZ80TimerFunc callback;
+	void* callbackdata;
+
+	while (calc->z80.timer_cpu) {
+		tmr = calc->z80.timer_cpu;
+		t = calc->z80.clock - calc->z80.timers[tmr].count;
+		if (t >= 10000)
+			break;
+
+		callback = calc->z80.timers[tmr].callback;
+		callbackdata = calc->z80.timers[tmr].callbackdata;
+
+		timer_unset(&calc->z80, tmr);
+		timer_set(&calc->z80, tmr, calc->z80.timers[tmr].period,
+			  calc->z80.timers[tmr].period, 0, t);
+
+		(*callback)(calc, callbackdata);
+	}
+
+	while (calc->z80.timer_rt) {
+		tmr = calc->z80.timer_rt;
+		t = calc->z80.clock - calc->z80.timers[tmr].count;
+		if (t >= 10000)
+			break;
+
+		callback = calc->z80.timers[tmr].callback;
+		callbackdata = calc->z80.timers[tmr].callbackdata;
+
+		timer_unset(&calc->z80, tmr);
+		timer_set(&calc->z80, tmr, calc->z80.timers[tmr].period,
+			  calc->z80.timers[tmr].period, 1, t);
+
+		(*callback)(calc, callbackdata);
+	}
+}
+
+static inline void check_breakpoints(TilemCalc* calc, int list, dword addr)
+{
+	dword masked;
+	int bp;
+	TilemZ80BreakpointFunc testfunc;
+	void* testdata;
+
+	for (bp = list; bp; bp = calc->z80.breakpoints[bp].next) {
+		masked = addr & calc->z80.breakpoints[bp].mask;
+		if (masked < calc->z80.breakpoints[bp].start
+		    || masked > calc->z80.breakpoints[bp].end)
+			continue;
+
+		testfunc = calc->z80.breakpoints[bp].testfunc;
+		testdata = calc->z80.breakpoints[bp].testdata;
+
+		if (testfunc && !(*testfunc)(calc, addr, testdata))
+			continue;
+
+		calc->z80.stop_breakpoint = bp;
+		tilem_z80_stop(calc, TILEM_STOP_BREAKPOINT);
+	}
+}
+
+static inline void check_mem_breakpoints(TilemCalc* calc, int list_l,
+					 int list_p, dword addr)
+{
+	check_breakpoints(calc, list_l, addr);
+
+	if (list_p) {
+		addr = (*calc->hw.mem_ltop)(calc, addr);
+		check_breakpoints(calc, list_p, addr);
+	}
+}
+
+static inline byte z80_readb_m1(TilemCalc* calc, dword addr)
+{
+	byte b;
+	addr &= 0xffff;
+	b = (*calc->hw.z80_rdmem_m1)(calc, addr);
+	check_mem_breakpoints(calc, calc->z80.breakpoint_mx,
+			      calc->z80.breakpoint_mpx, addr);
+	Rl++;
+	return b;
+}
+
+static inline byte z80_readb(TilemCalc* calc, dword addr)
+{
+	byte b;
+	addr &= 0xffff;
+	b = (*calc->hw.z80_rdmem)(calc, addr);
+	check_mem_breakpoints(calc, calc->z80.breakpoint_mr,
+			      calc->z80.breakpoint_mpr, addr);
+	return b;
+}
+
+static inline dword z80_readw(TilemCalc* calc, dword addr)
+{
+	dword v;
+	addr &= 0xffff;
+	v = (*calc->hw.z80_rdmem)(calc, addr);
+	check_mem_breakpoints(calc, calc->z80.breakpoint_mr,
+			      calc->z80.breakpoint_mpr, addr);
+	addr = (addr + 1) & 0xffff;
+	v |= (*calc->hw.z80_rdmem)(calc, addr) << 8;
+	check_mem_breakpoints(calc, calc->z80.breakpoint_mr,
+			      calc->z80.breakpoint_mpr, addr);
+	return v;
+}
+
+static inline byte z80_input(TilemCalc* calc, dword addr)
+{
+	byte b;
+	addr &= 0xffff;
+	check_timers(calc);
+	b = (*calc->hw.z80_in)(calc, addr);
+	check_breakpoints(calc, calc->z80.breakpoint_pr, addr);
+	return b;
+}
+
+static inline void z80_writeb(TilemCalc* calc, dword addr, byte value)
+{
+	addr &= 0xffff;
+	(*calc->hw.z80_wrmem)(calc, addr, value);
+	check_mem_breakpoints(calc, calc->z80.breakpoint_mw,
+			      calc->z80.breakpoint_mpw, addr);
+	calc->z80.lastwrite = calc->z80.clock;
+}
+
+static inline void z80_writew(TilemCalc* calc, dword addr, word value)
+{
+	addr &= 0xffff;
+	(*calc->hw.z80_wrmem)(calc, addr, value);
+	check_mem_breakpoints(calc, calc->z80.breakpoint_mw,
+			      calc->z80.breakpoint_mpw, addr);
+	addr = (addr + 1) & 0xffff;
+	value >>= 8;
+	(*calc->hw.z80_wrmem)(calc, addr, value);
+	check_mem_breakpoints(calc, calc->z80.breakpoint_mw,
+			      calc->z80.breakpoint_mpw, addr);
+	calc->z80.lastwrite = calc->z80.clock;
+}
+
+static inline void z80_output(TilemCalc* calc, dword addr, byte value)
+{
+	addr &= 0xffff;
+	check_timers(calc);
+	(*calc->hw.z80_out)(calc, addr, value);
+	check_breakpoints(calc, calc->z80.breakpoint_pw, addr);
+}
+
+#define readb_m1(aaa)     z80_readb_m1(calc, aaa)
+#define readb(aaa)        z80_readb(calc, aaa)
+#define readw(aaa)        z80_readw(calc, aaa)
+#define input(aaa)        z80_input(calc, aaa)
+#define writeb(aaa, vvv)  z80_writeb(calc, aaa, vvv)
+#define writew(aaa, vvv)  z80_writew(calc, aaa, vvv)
+#define output(aaa, vvv)  z80_output(calc, aaa, vvv)
+#define delay(nnn)        calc->z80.clock += (nnn)
+
+#include "z80cmds.h"
+
+static dword z80_execute_opcode(TilemCalc* calc, byte op)
+{
+	byte tmp1;
+	word tmp2;
+	int offs;
+#ifdef DISABLE_Z80_WZ_REGISTER
+	TilemZ80Reg temp_wz, temp_wz2;
+#endif
+
+ opcode_main:
+#include "z80main.h"
+	return op;
+
+ opcode_cb:
+#include "z80cb.h"
+	return op | 0xcb00;
+
+ opcode_ed:
+#include "z80ed.h"
+	return op | 0xed00;
+
+#define PREFIX_DD
+ opcode_dd:
+#include "z80ddfd.h"
+	return op | 0xdd00;
+ opcode_ddcb:
+#include "z80cb.h"
+	return op | 0xddcb0000;
+#undef PREFIX_DD
+
+#define PREFIX_FD
+ opcode_fd:
+#include "z80ddfd.h"
+	return op | 0xfd00;
+ opcode_fdcb:
+#include "z80cb.h"
+	return op | 0xfdcb0000;
+#undef PREFIX_FD
+}
+
+static void z80_execute(TilemCalc* calc)
+{
+	TilemZ80* z80 = &calc->z80;
+	byte busbyte;
+	dword op;
+	dword t1, t2;
+
+	z80->stopping = 0;
+	z80->stop_reason = 0;
+	z80->stop_breakpoint = 0;
+
+	if (!z80->timer_cpu && !z80->timer_rt) {
+		tilem_internal(calc, "No timers set");
+		return;
+	}
+
+	while (!z80->stopping) {
+		z80->exception = 0;
+		op = (*calc->hw.z80_rdmem_m1)(calc, PC);
+		PC++;
+		Rl++;
+		op = z80_execute_opcode(calc, op);
+		check_breakpoints(calc, z80->breakpoint_op, op);
+		check_timers(calc);
+
+		if (z80->interrupts && IFF1 && op != 0xfb
+		    && op != 0xddfb && op != 0xfdfb) {
+			IFF1 = IFF2 = 0;
+			Rl++;
+			z80->halted = 0;
+
+			/* Depending on the calculator, this value
+			   varies somewhat randomly from one interrupt
+			   to the next (making IM 2 rather difficult
+			   to use, and IM 0 essentially worthless.)
+			   Most likely, there is nothing connected to
+			   the data bus at interrupt time.  I seem to
+			   remember somebody (sigma, perhaps?)
+			   experimenting with this on the TI-83+ and
+			   finding it usually 3F, 7F, BF, or FF.  Or
+			   maybe I'm completely wrong.  In any case it
+			   is unwise for programs to depend on this
+			   value! */
+
+			busbyte = rand() & 0xff;
+
+			switch (IM) {
+			case 0:
+				delay(2);
+				z80_execute_opcode(calc, busbyte);
+				break;
+
+			case 1:
+				push(PC);
+				PC = 0x0038;
+				delay(13);
+				break;
+
+			case 2:
+				/* FIXME: does accepting an IM 2
+				   interrupt affect WZ?  It seems very
+				   likely. */
+				push(PC);
+				PC = readw((IR & 0xff00) | busbyte);
+				delay(19);
+			}
+			check_mem_breakpoints(calc, z80->breakpoint_mx, z80->breakpoint_mpx, PC);
+			check_timers(calc);
+		}
+		else if (op != 0x76) {
+			check_mem_breakpoints(calc, z80->breakpoint_mx, z80->breakpoint_mpx, PC);
+		}
+		else {
+			z80->halted = 1;
+			PC--;
+			if (z80->stopping)
+				break;
+
+			/* CPU halted: fast-forward to next timer event */
+			if (z80->timer_cpu && z80->timer_rt) {
+				t1 = (z80->timers[z80->timer_cpu].count
+				      - z80->clock);
+				t2 = (z80->timers[z80->timer_rt].count
+				      - z80->clock);
+				if (t1 > t2)
+					t1 = t2;
+			}
+			else if (z80->timer_cpu) {
+				t1 = (z80->timers[z80->timer_cpu].count
+				      - z80->clock);
+			}
+			else if (z80->timer_rt) {
+				t1 = (z80->timers[z80->timer_rt].count
+				      - z80->clock);
+			}
+			else {
+				tilem_internal(calc, "No timers set");
+				return;
+			}
+
+			z80->clock += t1 & ~3;
+			Rl += t1 / 4;
+			check_timers(calc);
+		}
+
+		if (TILEM_UNLIKELY(z80->exception)) {
+			if (z80->emuflags & TILEM_Z80_BREAK_EXCEPTIONS)
+				tilem_z80_stop(calc, TILEM_STOP_EXCEPTION);
+			if (!(z80->emuflags & TILEM_Z80_IGNORE_EXCEPTIONS))
+				tilem_calc_reset(calc);
+		}
+	}
+}
+
+static void tmr_stop(TilemCalc* calc, void* data TILEM_ATTR_UNUSED)
+{
+	tilem_z80_stop(calc, TILEM_STOP_TIMEOUT);
+}
+
+dword tilem_z80_run(TilemCalc* calc, int clocks, int* remaining)
+{
+	int tmr = tilem_z80_add_timer(calc, clocks, 0, 0, &tmr_stop, 0);
+	z80_execute(calc);
+	if (remaining)
+		*remaining = tilem_z80_get_timer_clocks(calc, tmr);
+	tilem_z80_remove_timer(calc, tmr);
+	return calc->z80.stop_reason;
+}
+
+dword tilem_z80_run_time(TilemCalc* calc, int microseconds, int* remaining)
+{
+	int tmr = tilem_z80_add_timer(calc, microseconds, 0, 1, &tmr_stop, 0);
+	z80_execute(calc);
+	if (remaining)
+		*remaining = tilem_z80_get_timer_microseconds(calc, tmr);
+	tilem_z80_remove_timer(calc, tmr);
+	return calc->z80.stop_reason;
+}
diff --git a/tool/tilem-src/emu/z80.h b/tool/tilem-src/emu/z80.h
new file mode 100644
index 0000000..f48c483
--- /dev/null
+++ b/tool/tilem-src/emu/z80.h
@@ -0,0 +1,110 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TILEM_Z80_H
+#define _TILEM_Z80_H
+
+/* Internal Z80 data structures */
+
+struct _TilemZ80Timer {
+	int next, prev;
+	dword count;
+	dword period;
+	TilemZ80TimerFunc callback;
+	void* callbackdata;
+};
+
+struct _TilemZ80Breakpoint {
+	int next, prev;
+	int type;
+	dword start;
+	dword end;
+	dword mask;
+	TilemZ80BreakpointFunc testfunc;
+	void* testdata;
+};
+
+/* Useful definitions */
+
+#define AF (calc->z80.r.af.d)
+#define BC (calc->z80.r.bc.d)
+#define DE (calc->z80.r.de.d)
+#define HL (calc->z80.r.hl.d)
+#define AF2 (calc->z80.r.af2.d)
+#define BC2 (calc->z80.r.bc2.d)
+#define DE2 (calc->z80.r.de2.d)
+#define HL2 (calc->z80.r.hl2.d)
+#define IX (calc->z80.r.ix.d)
+#define IY (calc->z80.r.iy.d)
+#define SP (calc->z80.r.sp.d)
+#define PC (calc->z80.r.pc.d)
+#define IR (calc->z80.r.ir.d)
+
+#define A (calc->z80.r.af.b.h)
+#define F (calc->z80.r.af.b.l)
+#define B (calc->z80.r.bc.b.h)
+#define C (calc->z80.r.bc.b.l)
+#define D (calc->z80.r.de.b.h)
+#define E (calc->z80.r.de.b.l)
+#define H (calc->z80.r.hl.b.h)
+#define L (calc->z80.r.hl.b.l)
+#define IXh (calc->z80.r.ix.b.h)
+#define IXl (calc->z80.r.ix.b.l)
+#define IYh (calc->z80.r.iy.b.h)
+#define IYl (calc->z80.r.iy.b.l)
+#define I (calc->z80.r.ir.b.h)
+#define Rh (calc->z80.r.r7)
+#define Rl (calc->z80.r.ir.b.l)
+#define R ((Rl & 0x7f) | Rh)
+
+#ifdef DISABLE_Z80_WZ_REGISTER
+# define WZ temp_wz.d
+# define WZ2 temp_wz2.d
+# define W temp_wz.b.h
+# define Z temp_wz.b.l
+#else
+# define WZ (calc->z80.r.wz.d)
+# define WZ2 (calc->z80.r.wz2.d)
+# define W (calc->z80.r.wz.b.h)
+# define Z (calc->z80.r.wz.b.l)
+#endif
+
+#define IFF1 (calc->z80.r.iff1)
+#define IFF2 (calc->z80.r.iff2)
+#define IM (calc->z80.r.im)
+
+#define FLAG_S 0x80
+#define FLAG_Z 0x40
+#define FLAG_Y 0x20
+#define FLAG_H 0x10
+#define FLAG_X 0x08
+#define FLAG_P 0x04
+#define FLAG_V FLAG_P
+#define FLAG_N 0x02
+#define FLAG_C 0x01
+
+#define BCw (calc->z80.r.bc.w.l)
+#define DEw (calc->z80.r.de.w.l)
+#define HLw (calc->z80.r.hl.w.l)
+#define SPw (calc->z80.r.sp.w.l)
+#define IXw (calc->z80.r.ix.w.l)
+#define IYw (calc->z80.r.iy.w.l)
+
+#endif
diff --git a/tool/tilem-src/emu/z80cb.h b/tool/tilem-src/emu/z80cb.h
new file mode 100644
index 0000000..ef276ee
--- /dev/null
+++ b/tool/tilem-src/emu/z80cb.h
@@ -0,0 +1,426 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#if defined(PREFIX_DD) || defined(PREFIX_FD)
+
+# define CBINST(fnc, reg) do {			\
+		UNDOCUMENTED(16);		\
+		tmp1 = readb(WZ);		\
+		fnc(tmp1);			\
+		writeb(WZ, tmp1);		\
+		(reg) = tmp1;			\
+		delay(19);			\
+	} while (0)
+
+# define CBINST_HL(fnc) do {			\
+		tmp1 = readb(WZ);		\
+		fnc(tmp1);			\
+		writeb(WZ, tmp1);		\
+		delay(19);			\
+	} while (0)
+
+# define CBINST_UNDOC(fnc, reg) CBINST(fnc, reg)
+
+# define CBINST_UNDOC_HL(fnc) do {		\
+		UNDOCUMENTED(12);		\
+		CBINST_HL(fnc);			\
+	} while (0)
+
+# define CB_BIT(b, reg) do {			\
+		UNDOCUMENTED(12);		\
+		CB_BIT_HL(b);			\
+	} while (0)
+
+# define CB_BIT_HL(b) do {					\
+		tmp1 = readb(WZ) & (1 << b);			\
+		F = ((tmp1 & FLAG_S)	     /* S */		\
+		     | (W & FLAG_XY)	     /* X/Y */		\
+		     | (tmp1 ? 0 : FLAG_ZP)  /* Z/P */		\
+		     | (FLAG_H)		     /* H */		\
+		     | (F & FLAG_C));				\
+		delay(16);					\
+	} while (0)
+
+# define CB_RES(b, reg) do {			\
+		UNDOCUMENTED(16);		\
+		tmp1 = readb(WZ) & ~(1 << b);	\
+		writeb(WZ, tmp1);		\
+		(reg) = tmp1;			\
+		delay(19);			\
+	} while (0)
+
+# define CB_RES_HL(b) do {			\
+		tmp1 = readb(WZ) & ~(1 << b);	\
+		writeb(WZ, tmp1);		\
+		delay(19);			\
+	} while (0)
+
+# define CB_SET(b, reg) do {			\
+		UNDOCUMENTED(16);		\
+		tmp1 = readb(WZ) | (1 << b);	\
+		writeb(WZ, tmp1);		\
+		(reg) = tmp1;			\
+		delay(19);			\
+	} while (0)
+
+# define CB_SET_HL(b) do {			\
+		tmp1 = readb(WZ) | (1 << b);	\
+		writeb(WZ, tmp1);		\
+		delay(19);			\
+	} while (0)
+
+#else  /* ! PREFIX_DD, ! PREFIX_FD */
+
+# define CBINST(fnc, reg) do {  \
+		fnc(reg);	\
+		delay(8);	\
+	} while (0)
+
+# define CBINST_HL(fnc) do {			\
+		tmp1 = readb(HL);		\
+		fnc(tmp1);			\
+		writeb(HL, tmp1);		\
+		delay(15);			\
+	} while (0)
+
+# define CBINST_UNDOC(fnc, reg) do {		\
+		UNDOCUMENTED(8);		\
+		CBINST(fnc, reg);		\
+	} while (0)
+
+# define CBINST_UNDOC_HL(fnc) do {		\
+		UNDOCUMENTED(8);		\
+		CBINST_HL(fnc);			\
+	} while (0)
+
+# define CB_BIT(b, reg) do {					\
+		tmp1 = (reg) & (1 << b);			\
+		F = ((tmp1 & FLAG_SXY)         /* S/X/Y */	\
+		     | (tmp1 ? 0 : FLAG_ZP)    /* Z/P */	\
+		     | (FLAG_H)		       /* H */		\
+		     | (F & FLAG_C));				\
+		delay(8);					\
+	} while (0)
+
+# define CB_BIT_HL(b) do {					\
+		tmp1 = readb(HL) & (1 << b);			\
+		F = ((tmp1 & FLAG_S)	       /* S */		\
+		     | (W & FLAG_XY)	       /* X/Y */	\
+		     | (tmp1 ? 0 : FLAG_ZP)    /* Z/P */	\
+		     | (FLAG_H)		       /* H */		\
+		     | (F & FLAG_C));				\
+		delay(12);					\
+	} while (0)
+
+# define CB_RES(b, reg) do {			\
+		(reg) &= ~(1 << b);		\
+		delay(8);			\
+	} while (0)
+
+# define CB_RES_HL(b) do {				\
+		writeb(HL, readb(HL) & ~(1 << b));	\
+		delay(15);				\
+	} while (0)
+
+# define CB_SET(b, reg) do {			\
+		(reg) |= (1 << b);		\
+		delay(8);			\
+	} while (0)
+
+# define CB_SET_HL(b) do {				\
+		writeb(HL, readb(HL) | (1 << b));	\
+		delay(15);				\
+	} while (0)
+
+#endif
+
+switch (op) {
+ case 0x00: CBINST(rlc, B); break;
+ case 0x01: CBINST(rlc, C); break;
+ case 0x02: CBINST(rlc, D); break;
+ case 0x03: CBINST(rlc, E); break;
+ case 0x04: CBINST(rlc, H); break;
+ case 0x05: CBINST(rlc, L); break;
+ case 0x06: CBINST_HL(rlc); break;
+ case 0x07: CBINST(rlc, A); break;
+ case 0x08: CBINST(rrc, B); break;
+ case 0x09: CBINST(rrc, C); break;
+ case 0x0A: CBINST(rrc, D); break;
+ case 0x0B: CBINST(rrc, E); break;
+ case 0x0C: CBINST(rrc, H); break;
+ case 0x0D: CBINST(rrc, L); break;
+ case 0x0E: CBINST_HL(rrc); break;
+ case 0x0F: CBINST(rrc, A); break;
+ case 0x10: CBINST(rl, B); break;
+ case 0x11: CBINST(rl, C); break;
+ case 0x12: CBINST(rl, D); break;
+ case 0x13: CBINST(rl, E); break;
+ case 0x14: CBINST(rl, H); break;
+ case 0x15: CBINST(rl, L); break;
+ case 0x16: CBINST_HL(rl); break;
+ case 0x17: CBINST(rl, A); break;
+ case 0x18: CBINST(rr, B); break;
+ case 0x19: CBINST(rr, C); break;
+ case 0x1A: CBINST(rr, D); break;
+ case 0x1B: CBINST(rr, E); break;
+ case 0x1C: CBINST(rr, H); break;
+ case 0x1D: CBINST(rr, L); break;
+ case 0x1E: CBINST_HL(rr); break;
+ case 0x1F: CBINST(rr, A); break;
+ case 0x20: CBINST(sla, B); break;
+ case 0x21: CBINST(sla, C); break;
+ case 0x22: CBINST(sla, D); break;
+ case 0x23: CBINST(sla, E); break;
+ case 0x24: CBINST(sla, H); break;
+ case 0x25: CBINST(sla, L); break;
+ case 0x26: CBINST_HL(sla); break;
+ case 0x27: CBINST(sla, A); break;
+ case 0x28: CBINST(sra, B); break;
+ case 0x29: CBINST(sra, C); break;
+ case 0x2A: CBINST(sra, D); break;
+ case 0x2B: CBINST(sra, E); break;
+ case 0x2C: CBINST(sra, H); break;
+ case 0x2D: CBINST(sra, L); break;
+ case 0x2E: CBINST_HL(sra); break;
+ case 0x2F: CBINST(sra, A); break;
+ case 0x30: CBINST_UNDOC(slia, B); break;
+ case 0x31: CBINST_UNDOC(slia, C); break;
+ case 0x32: CBINST_UNDOC(slia, D); break;
+ case 0x33: CBINST_UNDOC(slia, E); break;
+ case 0x34: CBINST_UNDOC(slia, H); break;
+ case 0x35: CBINST_UNDOC(slia, L); break;
+ case 0x36: CBINST_UNDOC_HL(slia); break;
+ case 0x37: CBINST_UNDOC(slia, A); break;
+ case 0x38: CBINST(srl, B); break;
+ case 0x39: CBINST(srl, C); break;
+ case 0x3A: CBINST(srl, D); break;
+ case 0x3B: CBINST(srl, E); break;
+ case 0x3C: CBINST(srl, H); break;
+ case 0x3D: CBINST(srl, L); break;
+ case 0x3E: CBINST_HL(srl); break;
+ case 0x3F: CBINST(srl, A); break;
+
+ case 0x40: CB_BIT(0, B); break;
+ case 0x41: CB_BIT(0, C); break;
+ case 0x42: CB_BIT(0, D); break;
+ case 0x43: CB_BIT(0, E); break;
+ case 0x44: CB_BIT(0, H); break;
+ case 0x45: CB_BIT(0, L); break;
+ case 0x46: CB_BIT_HL(0); break;
+ case 0x47: CB_BIT(0, A); break;
+ case 0x48: CB_BIT(1, B); break;
+ case 0x49: CB_BIT(1, C); break;
+ case 0x4A: CB_BIT(1, D); break;
+ case 0x4B: CB_BIT(1, E); break;
+ case 0x4C: CB_BIT(1, H); break;
+ case 0x4D: CB_BIT(1, L); break;
+ case 0x4E: CB_BIT_HL(1); break;
+ case 0x4F: CB_BIT(1, A); break;
+ case 0x50: CB_BIT(2, B); break;
+ case 0x51: CB_BIT(2, C); break;
+ case 0x52: CB_BIT(2, D); break;
+ case 0x53: CB_BIT(2, E); break;
+ case 0x54: CB_BIT(2, H); break;
+ case 0x55: CB_BIT(2, L); break;
+ case 0x56: CB_BIT_HL(2); break;
+ case 0x57: CB_BIT(2, A); break;
+ case 0x58: CB_BIT(3, B); break;
+ case 0x59: CB_BIT(3, C); break;
+ case 0x5A: CB_BIT(3, D); break;
+ case 0x5B: CB_BIT(3, E); break;
+ case 0x5C: CB_BIT(3, H); break;
+ case 0x5D: CB_BIT(3, L); break;
+ case 0x5E: CB_BIT_HL(3); break;
+ case 0x5F: CB_BIT(3, A); break;
+ case 0x60: CB_BIT(4, B); break;
+ case 0x61: CB_BIT(4, C); break;
+ case 0x62: CB_BIT(4, D); break;
+ case 0x63: CB_BIT(4, E); break;
+ case 0x64: CB_BIT(4, H); break;
+ case 0x65: CB_BIT(4, L); break;
+ case 0x66: CB_BIT_HL(4); break;
+ case 0x67: CB_BIT(4, A); break;
+ case 0x68: CB_BIT(5, B); break;
+ case 0x69: CB_BIT(5, C); break;
+ case 0x6A: CB_BIT(5, D); break;
+ case 0x6B: CB_BIT(5, E); break;
+ case 0x6C: CB_BIT(5, H); break;
+ case 0x6D: CB_BIT(5, L); break;
+ case 0x6E: CB_BIT_HL(5); break;
+ case 0x6F: CB_BIT(5, A); break;
+ case 0x70: CB_BIT(6, B); break;
+ case 0x71: CB_BIT(6, C); break;
+ case 0x72: CB_BIT(6, D); break;
+ case 0x73: CB_BIT(6, E); break;
+ case 0x74: CB_BIT(6, H); break;
+ case 0x75: CB_BIT(6, L); break;
+ case 0x76: CB_BIT_HL(6); break;
+ case 0x77: CB_BIT(6, A); break;
+ case 0x78: CB_BIT(7, B); break;
+ case 0x79: CB_BIT(7, C); break;
+ case 0x7A: CB_BIT(7, D); break;
+ case 0x7B: CB_BIT(7, E); break;
+ case 0x7C: CB_BIT(7, H); break;
+ case 0x7D: CB_BIT(7, L); break;
+ case 0x7E: CB_BIT_HL(7); break;
+ case 0x7F: CB_BIT(7, A); break;
+
+ case 0x80: CB_RES(0, B); break;
+ case 0x81: CB_RES(0, C); break;
+ case 0x82: CB_RES(0, D); break;
+ case 0x83: CB_RES(0, E); break;
+ case 0x84: CB_RES(0, H); break;
+ case 0x85: CB_RES(0, L); break;
+ case 0x86: CB_RES_HL(0); break;
+ case 0x87: CB_RES(0, A); break;
+ case 0x88: CB_RES(1, B); break;
+ case 0x89: CB_RES(1, C); break;
+ case 0x8A: CB_RES(1, D); break;
+ case 0x8B: CB_RES(1, E); break;
+ case 0x8C: CB_RES(1, H); break;
+ case 0x8D: CB_RES(1, L); break;
+ case 0x8E: CB_RES_HL(1); break;
+ case 0x8F: CB_RES(1, A); break;
+ case 0x90: CB_RES(2, B); break;
+ case 0x91: CB_RES(2, C); break;
+ case 0x92: CB_RES(2, D); break;
+ case 0x93: CB_RES(2, E); break;
+ case 0x94: CB_RES(2, H); break;
+ case 0x95: CB_RES(2, L); break;
+ case 0x96: CB_RES_HL(2); break;
+ case 0x97: CB_RES(2, A); break;
+ case 0x98: CB_RES(3, B); break;
+ case 0x99: CB_RES(3, C); break;
+ case 0x9A: CB_RES(3, D); break;
+ case 0x9B: CB_RES(3, E); break;
+ case 0x9C: CB_RES(3, H); break;
+ case 0x9D: CB_RES(3, L); break;
+ case 0x9E: CB_RES_HL(3); break;
+ case 0x9F: CB_RES(3, A); break;
+ case 0xA0: CB_RES(4, B); break;
+ case 0xA1: CB_RES(4, C); break;
+ case 0xA2: CB_RES(4, D); break;
+ case 0xA3: CB_RES(4, E); break;
+ case 0xA4: CB_RES(4, H); break;
+ case 0xA5: CB_RES(4, L); break;
+ case 0xA6: CB_RES_HL(4); break;
+ case 0xA7: CB_RES(4, A); break;
+ case 0xA8: CB_RES(5, B); break;
+ case 0xA9: CB_RES(5, C); break;
+ case 0xAA: CB_RES(5, D); break;
+ case 0xAB: CB_RES(5, E); break;
+ case 0xAC: CB_RES(5, H); break;
+ case 0xAD: CB_RES(5, L); break;
+ case 0xAE: CB_RES_HL(5); break;
+ case 0xAF: CB_RES(5, A); break;
+ case 0xB0: CB_RES(6, B); break;
+ case 0xB1: CB_RES(6, C); break;
+ case 0xB2: CB_RES(6, D); break;
+ case 0xB3: CB_RES(6, E); break;
+ case 0xB4: CB_RES(6, H); break;
+ case 0xB5: CB_RES(6, L); break;
+ case 0xB6: CB_RES_HL(6); break;
+ case 0xB7: CB_RES(6, A); break;
+ case 0xB8: CB_RES(7, B); break;
+ case 0xB9: CB_RES(7, C); break;
+ case 0xBA: CB_RES(7, D); break;
+ case 0xBB: CB_RES(7, E); break;
+ case 0xBC: CB_RES(7, H); break;
+ case 0xBD: CB_RES(7, L); break;
+ case 0xBE: CB_RES_HL(7); break;
+ case 0xBF: CB_RES(7, A); break;
+
+ case 0xC0: CB_SET(0, B); break;
+ case 0xC1: CB_SET(0, C); break;
+ case 0xC2: CB_SET(0, D); break;
+ case 0xC3: CB_SET(0, E); break;
+ case 0xC4: CB_SET(0, H); break;
+ case 0xC5: CB_SET(0, L); break;
+ case 0xC6: CB_SET_HL(0); break;
+ case 0xC7: CB_SET(0, A); break;
+ case 0xC8: CB_SET(1, B); break;
+ case 0xC9: CB_SET(1, C); break;
+ case 0xCA: CB_SET(1, D); break;
+ case 0xCB: CB_SET(1, E); break;
+ case 0xCC: CB_SET(1, H); break;
+ case 0xCD: CB_SET(1, L); break;
+ case 0xCE: CB_SET_HL(1); break;
+ case 0xCF: CB_SET(1, A); break;
+ case 0xD0: CB_SET(2, B); break;
+ case 0xD1: CB_SET(2, C); break;
+ case 0xD2: CB_SET(2, D); break;
+ case 0xD3: CB_SET(2, E); break;
+ case 0xD4: CB_SET(2, H); break;
+ case 0xD5: CB_SET(2, L); break;
+ case 0xD6: CB_SET_HL(2); break;
+ case 0xD7: CB_SET(2, A); break;
+ case 0xD8: CB_SET(3, B); break;
+ case 0xD9: CB_SET(3, C); break;
+ case 0xDA: CB_SET(3, D); break;
+ case 0xDB: CB_SET(3, E); break;
+ case 0xDC: CB_SET(3, H); break;
+ case 0xDD: CB_SET(3, L); break;
+ case 0xDE: CB_SET_HL(3); break;
+ case 0xDF: CB_SET(3, A); break;
+ case 0xE0: CB_SET(4, B); break;
+ case 0xE1: CB_SET(4, C); break;
+ case 0xE2: CB_SET(4, D); break;
+ case 0xE3: CB_SET(4, E); break;
+ case 0xE4: CB_SET(4, H); break;
+ case 0xE5: CB_SET(4, L); break;
+ case 0xE6: CB_SET_HL(4); break;
+ case 0xE7: CB_SET(4, A); break;
+ case 0xE8: CB_SET(5, B); break;
+ case 0xE9: CB_SET(5, C); break;
+ case 0xEA: CB_SET(5, D); break;
+ case 0xEB: CB_SET(5, E); break;
+ case 0xEC: CB_SET(5, H); break;
+ case 0xED: CB_SET(5, L); break;
+ case 0xEE: CB_SET_HL(5); break;
+ case 0xEF: CB_SET(5, A); break;
+ case 0xF0: CB_SET(6, B); break;
+ case 0xF1: CB_SET(6, C); break;
+ case 0xF2: CB_SET(6, D); break;
+ case 0xF3: CB_SET(6, E); break;
+ case 0xF4: CB_SET(6, H); break;
+ case 0xF5: CB_SET(6, L); break;
+ case 0xF6: CB_SET_HL(6); break;
+ case 0xF7: CB_SET(6, A); break;
+ case 0xF8: CB_SET(7, B); break;
+ case 0xF9: CB_SET(7, C); break;
+ case 0xFA: CB_SET(7, D); break;
+ case 0xFB: CB_SET(7, E); break;
+ case 0xFC: CB_SET(7, H); break;
+ case 0xFD: CB_SET(7, L); break;
+ case 0xFE: CB_SET_HL(7); break;
+ case 0xFF: CB_SET(7, A); break;
+ }
+
+#undef CBINST
+#undef CBINST_HL
+#undef CBINST_UNDOC
+#undef CBINST_UNDOC_HL
+#undef CB_BIT
+#undef CB_BIT_HL
+#undef CB_RES
+#undef CB_RES_HL
+#undef CB_SET
+#undef CB_SET_HL
+
diff --git a/tool/tilem-src/emu/z80cmds.h b/tool/tilem-src/emu/z80cmds.h
new file mode 100644
index 0000000..8ce23b2
--- /dev/null
+++ b/tool/tilem-src/emu/z80cmds.h
@@ -0,0 +1,840 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009-2011 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* Flag magic:
+
+   S (the Sign flag) is set when the result is negative (i.e., copied
+   from the MS bit.)
+
+   Z (the Zero flag) is set when the result is zero.
+
+   Y (undocumented flag) is usually copied from the result.
+
+   H (the Half-carry flag) is set when a carry occurs from LS to MS
+   nibble.  Since bit 4 of the result is equal to (bit 4 of first arg)
+   xor (bit 4 of second arg) xor H, we can infer that H equals (bit 4
+   of first arg) xor (bit 4 of second arg) xor (bit 4 of result).
+
+   X (undocumented flag) is copied from the same place as is Y.
+
+   P (parity flag) is set -- by logic operations -- when the result
+   has even parity; i.e., it's the xor of all the bits of the result
+   and 1.
+
+   V (overflow flag, which is another name for the parity flag) is set
+   -- by arithmetic operations -- when the result overflows.  This can
+   happen when a positive plus a positive is negative, or a negative
+   plus a negative is positive, or a positive minus a negative is
+   negative, or a negative minus a positive is positive.
+
+   (In 8 bits, a positive plus a negative is at least 0 + -128 = -128
+   and at most 127 + -1 + 1 = 127.  A positive minus a positive is at
+   least 0 - 127 - 1 = -128 and at most 127 - 0 = 127.  A negative
+   minus a negative is at least -128 - -1 - 1 = -128 and at most -1 -
+   -128 = 127.  Thus these cases can never overflow.)
+
+   When adding, then, overflow occurs if the args are equal in the
+   MS bit and the result differs in the MS bit.
+
+   When subtracting, overflow occurs if the args differ in the MS
+   bit and the result differs from the first arg in the MS bit.
+
+   N (subtract flag) is set or cleared depending on the operation.
+
+   C (carry flag) is the leftover bit after an addition or
+   subtraction.
+*/
+
+#define FLAG_XY (FLAG_X | FLAG_Y)
+#define FLAG_XYC (FLAG_X | FLAG_Y | FLAG_C)
+#define FLAG_SXY (FLAG_S | FLAG_X | FLAG_Y)
+#define FLAG_SXYC (FLAG_S | FLAG_X | FLAG_Y | FLAG_C)
+#define FLAG_ZP (FLAG_Z | FLAG_P)
+#define FLAG_SZPC (FLAG_S | FLAG_Z | FLAG_P | FLAG_C)
+
+#define lsb(xxx) ((xxx) & 0xff)
+
+#define UNDOCUMENTED(clks)						\
+	if (calc->z80.emuflags & TILEM_Z80_BREAK_UNDOCUMENTED)		\
+		tilem_z80_stop(calc, TILEM_STOP_UNDOCUMENTED_INST);	\
+	if (calc->z80.emuflags & TILEM_Z80_SKIP_UNDOCUMENTED) {		\
+		delay(clks);						\
+		break;							\
+	}								\
+	if (calc->z80.emuflags & TILEM_Z80_RESET_UNDOCUMENTED) {	\
+		tilem_warning(calc, "Invalid opcode %x", op);		\
+		tilem_z80_exception(calc, TILEM_EXC_INSTRUCTION);	\
+		break;							\
+	}
+
+#define add8(dst, src) do {						\
+		word arg1 = (dst);					\
+		word arg2 = (src);					\
+		word res = arg1 + arg2;					\
+		byte resb = res;					\
+		F = ((res & FLAG_SXY)			/* S/X/Y */	\
+		     | (resb ? 0 : FLAG_Z)		/* Z */		\
+		     | ((arg1 ^ arg2 ^ res) & FLAG_H)	/* H */		\
+		     | (((arg1 ^ ~arg2) & (arg1 ^ res) & 0x80) >> 5)	\
+		     | (res >> 8));			/* C */		\
+		(dst) = resb;						\
+	} while (0)
+
+#define adc8(dst, src) do {						\
+		word arg1 = (dst);					\
+		word arg2 = (src);					\
+		word res = arg1 + arg2 + (F & 1);			\
+		byte resb = res;					\
+		F = ((res & FLAG_SXY)			/* S/X/Y */	\
+		     | (resb ? 0 : FLAG_Z)		/* Z */		\
+		     | ((arg1 ^ arg2 ^ res) & FLAG_H)	/* H */		\
+		     | (((arg1 ^ ~arg2) & (arg1 ^ res) & 0x80) >> 5)	\
+		     | (res >> 8));			/* C */		\
+		(dst) = resb;						\
+	} while (0)
+
+#define sub8(dst, src) do {						\
+		word arg1 = (dst);					\
+		word arg2 = (src);					\
+		word res = arg1 - arg2;					\
+		byte resb = res;					\
+		F = ((res & FLAG_SXY)			/* S/X/Y */	\
+		     | (resb ? 0 : FLAG_Z)		/* Z */		\
+		     | ((arg1 ^ arg2 ^ res) & FLAG_H)	/* H */		\
+		     | (((arg1 ^ arg2) & (arg1 ^ res) & 0x80) >> 5)	\
+		     | (FLAG_N)				/* N */		\
+		     | ((res >> 8) & 1));		/* C */		\
+		(dst) = resb;						\
+	} while (0)
+
+#define sbc8(dst, src) do {						\
+		word arg1 = (dst);					\
+		word arg2 = (src);					\
+		word res = arg1 - arg2 - (F & 1);			\
+		byte resb = res;					\
+		F = ((res & FLAG_SXY)			/* S/X/Y */	\
+		     | (resb ? 0 : FLAG_Z)		/* Z */		\
+		     | ((arg1 ^ arg2 ^ res) & FLAG_H)	/* H */		\
+		     | (((arg1 ^ arg2) & (arg1 ^ res) & 0x80) >> 5)	\
+		     | (FLAG_N)				/* N */		\
+		     | ((res >> 8) & 1));		/* C */		\
+		(dst) = resb;						\
+	} while (0)
+
+#define and(dst, src) do {						\
+		byte arg1 = (dst);					\
+		byte arg2 = (src);					\
+		byte res = (arg1 & arg2);				\
+		F = ((res & FLAG_SXY)			/* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)		/* Z */		\
+		     | (FLAG_H)				/* H */		\
+		     | parity_table[res]);		/* P */		\
+		(dst) = res;						\
+	} while (0)
+
+#define xor(dst, src) do {						\
+		byte arg1 = (dst);					\
+		byte arg2 = (src);					\
+		byte res = (arg1 ^ arg2);				\
+		F = ((res & FLAG_SXY)			/* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)		/* Z */		\
+		     | parity_table[res]);		/* P */		\
+		(dst) = res;						\
+	} while (0)
+
+#define or(dst, src) do {						\
+		byte arg1 = (dst);					\
+		byte arg2 = (src);					\
+		byte res = (arg1 | arg2);				\
+		F = ((res & FLAG_SXY)			/* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)		/* Z */		\
+		     | parity_table[res]);		/* P */		\
+		(dst) = res;						\
+	} while (0)
+
+#define cp(dst, src) do {						\
+		word arg1 = (dst);					\
+		word arg2 = (src);					\
+		word res = arg1 - arg2;					\
+		byte resb = res;					\
+		F = ((res & FLAG_SXY)	                /* S/X/Y */	\
+		     | (resb ? 0 : FLAG_Z)		/* Z */	\
+		     | ((arg1 ^ arg2 ^ res) & FLAG_H)	/* H */		\
+		     | (((arg1 ^ arg2) & (arg1 ^ res) & 0x80) >> 5)	\
+		     | (FLAG_N)		                /* N */		\
+		     | ((res >> 8) & 1));		/* C */		\
+	} while (0)
+
+
+#define add16(dst, src) do {						\
+		dword arg1 = (dst);					\
+		dword arg2 = (src);					\
+		dword res = arg1 + arg2;				\
+		F = ((F & (FLAG_S | FLAG_Z | FLAG_P))			\
+		     | ((res >> 8) & FLAG_XY)		     /* X/Y */	\
+		     | (((arg1 ^ arg2 ^ res) >> 8) & FLAG_H) /* H */	\
+		     | (res >> 16));		   	     /* C */	\
+		(dst) = res;						\
+	} while (0)
+
+#define adc16(dst, src) do {						\
+		dword arg1 = (dst);					\
+		dword arg2 = (src);					\
+		dword res = arg1 + arg2 + (F & 1);			\
+		word resw = res;					\
+		F = (((res >> 8) & FLAG_SXY)		     /* S/X/Y */\
+		     | (resw ? 0 : FLAG_Z)		     /* Z */	\
+		     | (((arg1 ^ arg2 ^ res) >> 8) & FLAG_H) /* H */	\
+		     | (((arg1 ^ ~arg2) & (arg1 ^ res) & 0x8000) >> 11) \
+		     | (res >> 16));		     	     /* C */	\
+		(dst) = resw;						\
+	} while (0)
+
+#define sbc16(dst, src) do {						\
+		dword arg1 = (dst);					\
+		dword arg2 = (src);					\
+		dword res = arg1 - arg2 - (F & 1);			\
+		word resw = res;					\
+		F = (((res >> 8) & FLAG_SXY)		     /* S/X/Y */\
+		     | (resw ? 0 : FLAG_Z)		     /* Z */	\
+		     | (((arg1 ^ arg2 ^ res) >> 8) & FLAG_H) /* H */	\
+		     | (((arg1 ^ arg2) & (arg1 ^ res) & 0x8000) >> 11)	\
+		     | (FLAG_N)				     /* N */	\
+		     | ((res >> 16) & 1));		     /* C */	\
+		(dst) = resw;						\
+	} while (0)
+
+
+#define inc(reg) do {						\
+		byte arg = (reg);				\
+		byte res = arg + 1;				\
+		F = ((res & FLAG_SXY)	       	    /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	    /* Z */	\
+		     | ((arg ^ res) & FLAG_H)	    /* H */	\
+		     | (res == 0x80 ? FLAG_V : 0)   /* V */	\
+		     | (F & FLAG_C));				\
+		(reg) = res;					\
+	} while (0)
+
+#define dec(reg) do {						\
+		byte arg = (reg);				\
+		byte res = arg - 1;				\
+		F = ((res & FLAG_SXY)	       	    /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	    /* Z */	\
+		     | ((arg ^ res) & FLAG_H)	    /* H */	\
+		     | (res == 0x7f ? FLAG_V : 0)   /* V */	\
+		     | (FLAG_N)			    /* N */	\
+		     | (F & FLAG_C));				\
+		(reg) = res;					\
+	} while (0)
+
+
+#define cpl(reg) do {						\
+		byte arg = (reg);				\
+		byte res = ~arg;				\
+		F = ((res & FLAG_XY)		/* X/Y */	\
+		     | FLAG_H | FLAG_N		/* H/N */	\
+		     | (F & FLAG_SZPC));			\
+		(reg) = res;					\
+	} while (0)
+
+#define neg(reg) do {						\
+		byte arg = (reg);				\
+		byte res = -arg;				\
+		F = ((res & FLAG_SXY)		    /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	    /* Z */	\
+		     | ((arg ^ res) & FLAG_H)	    /* H */	\
+		     | (arg == 0x80 ? FLAG_V : 0)   /* V */	\
+		     | (FLAG_N)			    /* N */	\
+		     | (!!arg));		    /* C */	\
+		(reg) = res;					\
+	} while (0)
+
+#define daa do {							\
+		word idx = (((F & 0x10) << 6) | ((F & 0x03) << 8) | A);	\
+		AF = daa_table[idx];					\
+	} while (0)
+
+
+#define rlca do {							\
+		byte arg = A;						\
+		byte res = ((arg << 1) | (arg >> 7));			\
+		F = ((F & (FLAG_S | FLAG_Z | FLAG_P))			\
+		     | (res & FLAG_XYC));	          /* X/Y/C */	\
+		A = res;						\
+	} while (0)
+
+#define rrca do {						\
+		byte arg = A;					\
+		byte res = ((arg >> 1) | (arg << 7));		\
+		F = ((F & (FLAG_S | FLAG_Z | FLAG_P))		\
+		     | (res & FLAG_XY)	          /* X/Y */	\
+		     | (arg & FLAG_C));		  /* C */	\
+		A = res;					\
+	} while (0)
+    
+#define rla do {						\
+		byte arg = A;					\
+		byte res = ((arg << 1) | (F & 1));		\
+		F = ((F & (FLAG_S | FLAG_Z | FLAG_P))		\
+		     | (res & FLAG_XY)	          /* X/Y */	\
+		     | (arg >> 7));		  /* C */	\
+		A = res;					\
+	} while (0)
+
+#define rra do {						\
+		byte arg = A;					\
+		byte res = ((arg >> 1) | (F << 7));		\
+		F = ((F & (FLAG_S | FLAG_Z | FLAG_P))		\
+		     | (res & FLAG_XY)		  /* X/Y */	\
+		     | (arg & FLAG_C));		  /* C */	\
+		A = res;					\
+	} while (0)
+
+#define rlc(reg) do {						\
+		byte arg = (reg);				\
+		byte res = ((arg << 1) | (arg >> 7));		\
+		F = ((res & FLAG_SXYC)	          /* S/X/Y/C */ \
+		     | (res ? 0 : FLAG_Z)	  /* Z */       \
+		     | parity_table[res]);	  /* P */	\
+		(reg) = res;					\
+	} while (0)
+
+#define rrc(reg) do {						\
+		byte arg = (reg);				\
+		byte res = ((arg >> 1) | (arg << 7));		\
+		F = ((res & FLAG_SXY)	          /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	  /* Z */	\
+		     | parity_table[res]	  /* P */	\
+		     | (arg & FLAG_C));		  /* C */	\
+		(reg) = res;					\
+	} while (0)
+
+#define rl(reg) do {						\
+		byte arg = (reg);				\
+		byte res = ((arg << 1) | (F & 1));		\
+		F = ((res & FLAG_SXY)	          /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	  /* Z */	\
+		     | parity_table[res]	  /* P */	\
+		     | (arg >> 7));		  /* C */	\
+		(reg) = res;					\
+	} while (0)
+
+#define rr(reg) do {						\
+		byte arg = (reg);				\
+		byte res = ((arg >> 1) | (F << 7));		\
+		F = ((res & FLAG_SXY)	          /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	  /* Z */	\
+		     | parity_table[res]	  /* P */	\
+		     | (arg & FLAG_C));		  /* C */	\
+		(reg) = res;					\
+	} while (0)
+
+#define sla(reg) do {						\
+		byte arg = (reg);				\
+		byte res = (arg << 1);				\
+		F = ((res & FLAG_SXY)	          /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	  /* Z */	\
+		     | parity_table[res]	  /* P */	\
+		     | (arg >> 7));		  /* C */	\
+		(reg) = res;					\
+	} while (0)
+
+#define sra(reg) do {						\
+		byte arg = (reg);				\
+		byte res = ((signed char) arg >> 1);		\
+		F = ((res & FLAG_SXY)	          /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	  /* Z */	\
+		     | parity_table[res]	  /* P */	\
+		     | (arg & FLAG_C));		  /* C */	\
+		(reg) = res;					\
+	} while (0)
+
+#define slia(reg) do {						\
+		byte arg = (reg);				\
+		byte res = ((arg << 1) | 1);			\
+		F = ((res & FLAG_SXY)	          /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	  /* Z */	\
+		     | parity_table[res]	  /* P */	\
+		     | (arg >> 7));		  /* C */	\
+		(reg) = res;					\
+	} while (0)
+
+#define srl(reg) do {						\
+		byte arg = (reg);				\
+		byte res = (arg >> 1);				\
+		F = ((res & FLAG_SXY)	          /* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	  /* Z */	\
+		     | parity_table[res]	  /* P */	\
+		     | (arg & FLAG_C));		  /* C */	\
+		(reg) = res;					\
+	} while (0)
+
+#define rld do {						\
+		byte arg1 = readb(HL);				\
+		byte arg2 = A;					\
+		byte res = ((arg2 & 0xf0) | (arg1 >> 4));	\
+		WZ = HL + 1;					\
+		writeb(HL, (arg1 << 4) | (arg2 & 0xf));		\
+		F = ((res & FLAG_SXY)		/* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	/* Z */		\
+		     | parity_table[res]	/* P */		\
+		     | (F & FLAG_C));				\
+		A = res;					\
+	} while (0)
+
+#define rrd do {						\
+		byte arg1 = readb(HL);				\
+		byte arg2 = A;					\
+		byte res = ((arg2 & 0xf0) | (arg1 & 0x0f));	\
+		WZ = HL + 1;					\
+		writeb(HL, (arg1 >> 4) | (arg2 << 4));		\
+		F = ((res & FLAG_SXY)		/* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	/* Z */		\
+		     | parity_table[res]       	/* P */		\
+		     | (F & FLAG_C));				\
+		A = res;					\
+	} while (0)
+
+#define push(reg) do {				\
+		SP -= 2;			\
+		writew(SP, (reg));		\
+	} while (0)
+
+#define pop(reg) do {				\
+		(reg) = readw(SP);		\
+		SP += 2;			\
+	} while (0)
+
+#define ex(aaa, bbb) do {		\
+		tmp2 = (aaa);		\
+		(aaa) = (bbb);		\
+		(bbb) = tmp2;		\
+	} while (0)
+
+#define in(reg) do {						\
+		byte res = input(WZ);				\
+		F = ((res & FLAG_SXY)       /* S/X/Y */		\
+		     | (res ? 0 : FLAG_Z)   /* Z */		\
+		     | parity_table[res]    /* P */		\
+		     | (F & FLAG_C));				\
+		(reg) = res;					\
+	} while (0)
+
+
+#define ld_a_ir(reg) do {					\
+		byte res = (reg);				\
+		A = res;					\
+		F = ((res & FLAG_SXY)		/* S/X/Y */	\
+		     | (res ? 0 : FLAG_Z)	/* Z */		\
+		     | (IFF2 ? FLAG_P : 0)	/* P */		\
+		     | (F & FLAG_C));				\
+	} while (0)
+
+#define ldi do {						\
+		byte res = readb(HL++);				\
+		writeb(DE++, res);				\
+		BC--;						\
+		F = ((F & (FLAG_S | FLAG_Z | FLAG_C))		\
+		     | ((res << 4) & FLAG_Y)	/* Y */		\
+		     | (res & FLAG_X)		/* X */		\
+		     | (BCw ? FLAG_P : 0));	/* P */	\
+	} while (0)
+
+#define ldd do {						\
+		byte res = readb(HL--);				\
+		writeb(DE--, res);				\
+		BC--;						\
+		F = ((F & (FLAG_S | FLAG_Z | FLAG_C))		\
+		     | ((res << 4) & FLAG_Y) 	/* Y */		\
+		     | (res & FLAG_X)        	/* X */		\
+		     | (BCw ? FLAG_P : 0));	/* P */		\
+	} while (0)
+
+/* FIXME: CPI and CPD obviously use WZ, but I haven't yet figured out
+   precisely how. */
+
+#define cpi do {						\
+		word arg1 = A;					\
+		word arg2 = readb(HL++);			\
+		word res = arg1 - arg2;				\
+		byte hc = ((arg1 ^ arg2 ^ res) & 0x10);		\
+		byte res2 = res - (hc >> 4);			\
+		BC--;						\
+		F = ((res & FLAG_S)		   /* S */	\
+		     | (lsb(res) ? 0 : FLAG_Z)	   /* Z */	\
+		     | ((res2 << 4) & FLAG_Y)	   /* Y */	\
+		     | hc			   /* H */	\
+		     | (res2 & FLAG_X)		   /* X */	\
+		     | (BCw ? FLAG_P : 0)	   /* P */	\
+		     | FLAG_N			   /* N */	\
+		     | (F & FLAG_C));				\
+	} while (0)
+
+#define cpd do {						\
+		word arg1 = A;					\
+		word arg2 = readb(HL--);			\
+		word res = arg1 - arg2;				\
+		byte hc = ((arg1 ^ arg2 ^ res) & 0x10);		\
+		byte res2 = res - (hc >> 4);			\
+		BC--;						\
+		F = ((res & FLAG_S)		   /* S */	\
+		     | (lsb(res) ? 0 : FLAG_Z)	   /* Z */	\
+		     | ((res2 << 4) & FLAG_Y)	   /* Y */	\
+		     | hc			   /* H */	\
+		     | (res2 & FLAG_X)		   /* X */	\
+		     | (BCw ? FLAG_P : 0)	   /* P */	\
+		     | FLAG_N			   /* N */	\
+		     | (F & FLAG_C));				\
+	} while (0)
+
+#define ini do {							\
+		byte value, count;					\
+		word k;							\
+		value = input(BC);					\
+		writeb(HL++, value);					\
+		WZ = BC + 1;						\
+		k = Z + value;						\
+		count = --B;						\
+		F = ((count & FLAG_SXY)			/* S/X/Y */	\
+		     | (count ? 0 : FLAG_Z)		/* Z */		\
+		     | ((k >> 8) ? 0x11 : 0)		/* H/C */	\
+		     | parity_table[(k & 7) ^ count]	/* P */		\
+		     | ((value >> 6) & FLAG_N));	/* N */		\
+	} while (0)
+
+#define ind do {							\
+		byte value, count;					\
+		word k;							\
+		value = input(BC);					\
+		writeb(HL--, value);					\
+		WZ = BC - 1;						\
+		k = Z + value;						\
+		count = --B;						\
+		F = ((count & FLAG_SXY)	  		/* S/X/Y */	\
+		     | (count ? 0 : FLAG_Z)		/* Z */		\
+		     | ((k >> 8) ? 0x11 : 0)    	/* H/C */	\
+		     | parity_table[(k & 7) ^ count]	/* P */		\
+		     | ((value >> 6) & FLAG_N)); 	/* N */		\
+	} while (0)
+
+#define outi do {							\
+		byte value, count;					\
+		word k;							\
+		value = readb(HL++);					\
+		count = --B;						\
+		output(BC, value);					\
+		k = L + value;						\
+		WZ = BC + 1;						\
+		F = ((count & FLAG_SXY)			/* S/X/Y */	\
+		     | (count ? 0 : FLAG_Z)		/* Z */		\
+		     | ((k >> 8) ? 0x11 : 0)	  	/* H/C */	\
+		     | parity_table[(k & 7) ^ count]	/* P */		\
+		     | ((value >> 6) & FLAG_N));	/* N */		\
+	} while (0)
+
+#define outd do {							\
+		byte value, count;					\
+		word k;							\
+		value = readb(HL--);					\
+		count = --B;						\
+		output(BC, value);					\
+		k = L + value;						\
+		WZ = BC - 1;						\
+		F = ((count & FLAG_SXY)			/* S/X/Y */	\
+		     | (count ? 0 : FLAG_Z)		/* Z */		\
+		     | ((k >> 8) ? 0x11 : 0)		/* H/C */	\
+		     | parity_table[(k & 7) ^ count]	/* P */		\
+		     | ((value >> 6) & FLAG_N));	/* N */		\
+	} while (0)
+
+/* Table giving parity flag values.  (Also known as the Thue-Morse
+   sequence.) */
+static const byte parity_table[256] = {
+	4,0,0,4,0,4,4,0, 0,4,4,0,4,0,0,4, 0,4,4,0,4,0,0,4, 4,0,0,4,0,4,4,0,
+	0,4,4,0,4,0,0,4, 4,0,0,4,0,4,4,0, 4,0,0,4,0,4,4,0, 0,4,4,0,4,0,0,4,
+	0,4,4,0,4,0,0,4, 4,0,0,4,0,4,4,0, 4,0,0,4,0,4,4,0, 0,4,4,0,4,0,0,4,
+	4,0,0,4,0,4,4,0, 0,4,4,0,4,0,0,4, 0,4,4,0,4,0,0,4, 4,0,0,4,0,4,4,0,
+
+	0,4,4,0,4,0,0,4, 4,0,0,4,0,4,4,0, 4,0,0,4,0,4,4,0, 0,4,4,0,4,0,0,4,
+	4,0,0,4,0,4,4,0, 0,4,4,0,4,0,0,4, 0,4,4,0,4,0,0,4, 4,0,0,4,0,4,4,0,
+	4,0,0,4,0,4,4,0, 0,4,4,0,4,0,0,4, 0,4,4,0,4,0,0,4, 4,0,0,4,0,4,4,0,
+	0,4,4,0,4,0,0,4, 4,0,0,4,0,4,4,0, 4,0,0,4,0,4,4,0, 0,4,4,0,4,0,0,4,
+};
+
+/* Table giving AF values following DAA */
+static const word daa_table[2048] = {
+	0x0044, 0x0100, 0x0200, 0x0304, 0x0400, 0x0504, 0x0604, 0x0700,
+	0x0808, 0x090c, 0x1010, 0x1114, 0x1214, 0x1310, 0x1414, 0x1510,
+	0x1000, 0x1104, 0x1204, 0x1300, 0x1404, 0x1500, 0x1600, 0x1704,
+	0x180c, 0x1908, 0x2030, 0x2134, 0x2234, 0x2330, 0x2434, 0x2530,
+	0x2020, 0x2124, 0x2224, 0x2320, 0x2424, 0x2520, 0x2620, 0x2724,
+	0x282c, 0x2928, 0x3034, 0x3130, 0x3230, 0x3334, 0x3430, 0x3534,
+	0x3024, 0x3120, 0x3220, 0x3324, 0x3420, 0x3524, 0x3624, 0x3720,
+	0x3828, 0x392c, 0x4010, 0x4114, 0x4214, 0x4310, 0x4414, 0x4510,
+	0x4000, 0x4104, 0x4204, 0x4300, 0x4404, 0x4500, 0x4600, 0x4704,
+	0x480c, 0x4908, 0x5014, 0x5110, 0x5210, 0x5314, 0x5410, 0x5514,
+	0x5004, 0x5100, 0x5200, 0x5304, 0x5400, 0x5504, 0x5604, 0x5700,
+	0x5808, 0x590c, 0x6034, 0x6130, 0x6230, 0x6334, 0x6430, 0x6534,
+	0x6024, 0x6120, 0x6220, 0x6324, 0x6420, 0x6524, 0x6624, 0x6720,
+	0x6828, 0x692c, 0x7030, 0x7134, 0x7234, 0x7330, 0x7434, 0x7530,
+	0x7020, 0x7124, 0x7224, 0x7320, 0x7424, 0x7520, 0x7620, 0x7724,
+	0x782c, 0x7928, 0x8090, 0x8194, 0x8294, 0x8390, 0x8494, 0x8590,
+	0x8080, 0x8184, 0x8284, 0x8380, 0x8484, 0x8580, 0x8680, 0x8784,
+	0x888c, 0x8988, 0x9094, 0x9190, 0x9290, 0x9394, 0x9490, 0x9594,
+	0x9084, 0x9180, 0x9280, 0x9384, 0x9480, 0x9584, 0x9684, 0x9780,
+	0x9888, 0x998c, 0x0055, 0x0111, 0x0211, 0x0315, 0x0411, 0x0515,
+	0x0045, 0x0101, 0x0201, 0x0305, 0x0401, 0x0505, 0x0605, 0x0701,
+	0x0809, 0x090d, 0x1011, 0x1115, 0x1215, 0x1311, 0x1415, 0x1511,
+	0x1001, 0x1105, 0x1205, 0x1301, 0x1405, 0x1501, 0x1601, 0x1705,
+	0x180d, 0x1909, 0x2031, 0x2135, 0x2235, 0x2331, 0x2435, 0x2531,
+	0x2021, 0x2125, 0x2225, 0x2321, 0x2425, 0x2521, 0x2621, 0x2725,
+	0x282d, 0x2929, 0x3035, 0x3131, 0x3231, 0x3335, 0x3431, 0x3535,
+	0x3025, 0x3121, 0x3221, 0x3325, 0x3421, 0x3525, 0x3625, 0x3721,
+	0x3829, 0x392d, 0x4011, 0x4115, 0x4215, 0x4311, 0x4415, 0x4511,
+	0x4001, 0x4105, 0x4205, 0x4301, 0x4405, 0x4501, 0x4601, 0x4705,
+	0x480d, 0x4909, 0x5015, 0x5111, 0x5211, 0x5315, 0x5411, 0x5515,
+	0x5005, 0x5101, 0x5201, 0x5305, 0x5401, 0x5505, 0x5605, 0x5701,
+	0x5809, 0x590d, 0x6035, 0x6131, 0x6231, 0x6335, 0x6431, 0x6535,
+	0x6025, 0x6121, 0x6221, 0x6325, 0x6421, 0x6525, 0x6625, 0x6721,
+	0x6829, 0x692d, 0x7031, 0x7135, 0x7235, 0x7331, 0x7435, 0x7531,
+	0x7021, 0x7125, 0x7225, 0x7321, 0x7425, 0x7521, 0x7621, 0x7725,
+	0x782d, 0x7929, 0x8091, 0x8195, 0x8295, 0x8391, 0x8495, 0x8591,
+	0x8081, 0x8185, 0x8285, 0x8381, 0x8485, 0x8581, 0x8681, 0x8785,
+	0x888d, 0x8989, 0x9095, 0x9191, 0x9291, 0x9395, 0x9491, 0x9595,
+	0x9085, 0x9181, 0x9281, 0x9385, 0x9481, 0x9585, 0x9685, 0x9781,
+	0x9889, 0x998d, 0xa0b5, 0xa1b1, 0xa2b1, 0xa3b5, 0xa4b1, 0xa5b5,
+	0xa0a5, 0xa1a1, 0xa2a1, 0xa3a5, 0xa4a1, 0xa5a5, 0xa6a5, 0xa7a1,
+	0xa8a9, 0xa9ad, 0xb0b1, 0xb1b5, 0xb2b5, 0xb3b1, 0xb4b5, 0xb5b1,
+	0xb0a1, 0xb1a5, 0xb2a5, 0xb3a1, 0xb4a5, 0xb5a1, 0xb6a1, 0xb7a5,
+	0xb8ad, 0xb9a9, 0xc095, 0xc191, 0xc291, 0xc395, 0xc491, 0xc595,
+	0xc085, 0xc181, 0xc281, 0xc385, 0xc481, 0xc585, 0xc685, 0xc781,
+	0xc889, 0xc98d, 0xd091, 0xd195, 0xd295, 0xd391, 0xd495, 0xd591,
+	0xd081, 0xd185, 0xd285, 0xd381, 0xd485, 0xd581, 0xd681, 0xd785,
+	0xd88d, 0xd989, 0xe0b1, 0xe1b5, 0xe2b5, 0xe3b1, 0xe4b5, 0xe5b1,
+	0xe0a1, 0xe1a5, 0xe2a5, 0xe3a1, 0xe4a5, 0xe5a1, 0xe6a1, 0xe7a5,
+	0xe8ad, 0xe9a9, 0xf0b5, 0xf1b1, 0xf2b1, 0xf3b5, 0xf4b1, 0xf5b5,
+	0xf0a5, 0xf1a1, 0xf2a1, 0xf3a5, 0xf4a1, 0xf5a5, 0xf6a5, 0xf7a1,
+	0xf8a9, 0xf9ad, 0x0055, 0x0111, 0x0211, 0x0315, 0x0411, 0x0515,
+	0x0045, 0x0101, 0x0201, 0x0305, 0x0401, 0x0505, 0x0605, 0x0701,
+	0x0809, 0x090d, 0x1011, 0x1115, 0x1215, 0x1311, 0x1415, 0x1511,
+	0x1001, 0x1105, 0x1205, 0x1301, 0x1405, 0x1501, 0x1601, 0x1705,
+	0x180d, 0x1909, 0x2031, 0x2135, 0x2235, 0x2331, 0x2435, 0x2531,
+	0x2021, 0x2125, 0x2225, 0x2321, 0x2425, 0x2521, 0x2621, 0x2725,
+	0x282d, 0x2929, 0x3035, 0x3131, 0x3231, 0x3335, 0x3431, 0x3535,
+	0x3025, 0x3121, 0x3221, 0x3325, 0x3421, 0x3525, 0x3625, 0x3721,
+	0x3829, 0x392d, 0x4011, 0x4115, 0x4215, 0x4311, 0x4415, 0x4511,
+	0x4001, 0x4105, 0x4205, 0x4301, 0x4405, 0x4501, 0x4601, 0x4705,
+	0x480d, 0x4909, 0x5015, 0x5111, 0x5211, 0x5315, 0x5411, 0x5515,
+	0x5005, 0x5101, 0x5201, 0x5305, 0x5401, 0x5505, 0x5605, 0x5701,
+	0x5809, 0x590d, 0x6035, 0x6131, 0x6231, 0x6335, 0x6431, 0x6535,
+	0x0046, 0x0102, 0x0202, 0x0306, 0x0402, 0x0506, 0x0606, 0x0702,
+	0x080a, 0x090e, 0x0402, 0x0506, 0x0606, 0x0702, 0x080a, 0x090e,
+	0x1002, 0x1106, 0x1206, 0x1302, 0x1406, 0x1502, 0x1602, 0x1706,
+	0x180e, 0x190a, 0x1406, 0x1502, 0x1602, 0x1706, 0x180e, 0x190a,
+	0x2022, 0x2126, 0x2226, 0x2322, 0x2426, 0x2522, 0x2622, 0x2726,
+	0x282e, 0x292a, 0x2426, 0x2522, 0x2622, 0x2726, 0x282e, 0x292a,
+	0x3026, 0x3122, 0x3222, 0x3326, 0x3422, 0x3526, 0x3626, 0x3722,
+	0x382a, 0x392e, 0x3422, 0x3526, 0x3626, 0x3722, 0x382a, 0x392e,
+	0x4002, 0x4106, 0x4206, 0x4302, 0x4406, 0x4502, 0x4602, 0x4706,
+	0x480e, 0x490a, 0x4406, 0x4502, 0x4602, 0x4706, 0x480e, 0x490a,
+	0x5006, 0x5102, 0x5202, 0x5306, 0x5402, 0x5506, 0x5606, 0x5702,
+	0x580a, 0x590e, 0x5402, 0x5506, 0x5606, 0x5702, 0x580a, 0x590e,
+	0x6026, 0x6122, 0x6222, 0x6326, 0x6422, 0x6526, 0x6626, 0x6722,
+	0x682a, 0x692e, 0x6422, 0x6526, 0x6626, 0x6722, 0x682a, 0x692e,
+	0x7022, 0x7126, 0x7226, 0x7322, 0x7426, 0x7522, 0x7622, 0x7726,
+	0x782e, 0x792a, 0x7426, 0x7522, 0x7622, 0x7726, 0x782e, 0x792a,
+	0x8082, 0x8186, 0x8286, 0x8382, 0x8486, 0x8582, 0x8682, 0x8786,
+	0x888e, 0x898a, 0x8486, 0x8582, 0x8682, 0x8786, 0x888e, 0x898a,
+	0x9086, 0x9182, 0x9282, 0x9386, 0x9482, 0x9586, 0x9686, 0x9782,
+	0x988a, 0x998e, 0x3423, 0x3527, 0x3627, 0x3723, 0x382b, 0x392f,
+	0x4003, 0x4107, 0x4207, 0x4303, 0x4407, 0x4503, 0x4603, 0x4707,
+	0x480f, 0x490b, 0x4407, 0x4503, 0x4603, 0x4707, 0x480f, 0x490b,
+	0x5007, 0x5103, 0x5203, 0x5307, 0x5403, 0x5507, 0x5607, 0x5703,
+	0x580b, 0x590f, 0x5403, 0x5507, 0x5607, 0x5703, 0x580b, 0x590f,
+	0x6027, 0x6123, 0x6223, 0x6327, 0x6423, 0x6527, 0x6627, 0x6723,
+	0x682b, 0x692f, 0x6423, 0x6527, 0x6627, 0x6723, 0x682b, 0x692f,
+	0x7023, 0x7127, 0x7227, 0x7323, 0x7427, 0x7523, 0x7623, 0x7727,
+	0x782f, 0x792b, 0x7427, 0x7523, 0x7623, 0x7727, 0x782f, 0x792b,
+	0x8083, 0x8187, 0x8287, 0x8383, 0x8487, 0x8583, 0x8683, 0x8787,
+	0x888f, 0x898b, 0x8487, 0x8583, 0x8683, 0x8787, 0x888f, 0x898b,
+	0x9087, 0x9183, 0x9283, 0x9387, 0x9483, 0x9587, 0x9687, 0x9783,
+	0x988b, 0x998f, 0x9483, 0x9587, 0x9687, 0x9783, 0x988b, 0x998f,
+	0xa0a7, 0xa1a3, 0xa2a3, 0xa3a7, 0xa4a3, 0xa5a7, 0xa6a7, 0xa7a3,
+	0xa8ab, 0xa9af, 0xa4a3, 0xa5a7, 0xa6a7, 0xa7a3, 0xa8ab, 0xa9af,
+	0xb0a3, 0xb1a7, 0xb2a7, 0xb3a3, 0xb4a7, 0xb5a3, 0xb6a3, 0xb7a7,
+	0xb8af, 0xb9ab, 0xb4a7, 0xb5a3, 0xb6a3, 0xb7a7, 0xb8af, 0xb9ab,
+	0xc087, 0xc183, 0xc283, 0xc387, 0xc483, 0xc587, 0xc687, 0xc783,
+	0xc88b, 0xc98f, 0xc483, 0xc587, 0xc687, 0xc783, 0xc88b, 0xc98f,
+	0xd083, 0xd187, 0xd287, 0xd383, 0xd487, 0xd583, 0xd683, 0xd787,
+	0xd88f, 0xd98b, 0xd487, 0xd583, 0xd683, 0xd787, 0xd88f, 0xd98b,
+	0xe0a3, 0xe1a7, 0xe2a7, 0xe3a3, 0xe4a7, 0xe5a3, 0xe6a3, 0xe7a7,
+	0xe8af, 0xe9ab, 0xe4a7, 0xe5a3, 0xe6a3, 0xe7a7, 0xe8af, 0xe9ab,
+	0xf0a7, 0xf1a3, 0xf2a3, 0xf3a7, 0xf4a3, 0xf5a7, 0xf6a7, 0xf7a3,
+	0xf8ab, 0xf9af, 0xf4a3, 0xf5a7, 0xf6a7, 0xf7a3, 0xf8ab, 0xf9af,
+	0x0047, 0x0103, 0x0203, 0x0307, 0x0403, 0x0507, 0x0607, 0x0703,
+	0x080b, 0x090f, 0x0403, 0x0507, 0x0607, 0x0703, 0x080b, 0x090f,
+	0x1003, 0x1107, 0x1207, 0x1303, 0x1407, 0x1503, 0x1603, 0x1707,
+	0x180f, 0x190b, 0x1407, 0x1503, 0x1603, 0x1707, 0x180f, 0x190b,
+	0x2023, 0x2127, 0x2227, 0x2323, 0x2427, 0x2523, 0x2623, 0x2727,
+	0x282f, 0x292b, 0x2427, 0x2523, 0x2623, 0x2727, 0x282f, 0x292b,
+	0x3027, 0x3123, 0x3223, 0x3327, 0x3423, 0x3527, 0x3627, 0x3723,
+	0x382b, 0x392f, 0x3423, 0x3527, 0x3627, 0x3723, 0x382b, 0x392f,
+	0x4003, 0x4107, 0x4207, 0x4303, 0x4407, 0x4503, 0x4603, 0x4707,
+	0x480f, 0x490b, 0x4407, 0x4503, 0x4603, 0x4707, 0x480f, 0x490b,
+	0x5007, 0x5103, 0x5203, 0x5307, 0x5403, 0x5507, 0x5607, 0x5703,
+	0x580b, 0x590f, 0x5403, 0x5507, 0x5607, 0x5703, 0x580b, 0x590f,
+	0x6027, 0x6123, 0x6223, 0x6327, 0x6423, 0x6527, 0x6627, 0x6723,
+	0x682b, 0x692f, 0x6423, 0x6527, 0x6627, 0x6723, 0x682b, 0x692f,
+	0x7023, 0x7127, 0x7227, 0x7323, 0x7427, 0x7523, 0x7623, 0x7727,
+	0x782f, 0x792b, 0x7427, 0x7523, 0x7623, 0x7727, 0x782f, 0x792b,
+	0x8083, 0x8187, 0x8287, 0x8383, 0x8487, 0x8583, 0x8683, 0x8787,
+	0x888f, 0x898b, 0x8487, 0x8583, 0x8683, 0x8787, 0x888f, 0x898b,
+	0x9087, 0x9183, 0x9283, 0x9387, 0x9483, 0x9587, 0x9687, 0x9783,
+	0x988b, 0x998f, 0x9483, 0x9587, 0x9687, 0x9783, 0x988b, 0x998f,
+	0x0604, 0x0700, 0x0808, 0x090c, 0x0a0c, 0x0b08, 0x0c0c, 0x0d08,
+	0x0e08, 0x0f0c, 0x1010, 0x1114, 0x1214, 0x1310, 0x1414, 0x1510,
+	0x1600, 0x1704, 0x180c, 0x1908, 0x1a08, 0x1b0c, 0x1c08, 0x1d0c,
+	0x1e0c, 0x1f08, 0x2030, 0x2134, 0x2234, 0x2330, 0x2434, 0x2530,
+	0x2620, 0x2724, 0x282c, 0x2928, 0x2a28, 0x2b2c, 0x2c28, 0x2d2c,
+	0x2e2c, 0x2f28, 0x3034, 0x3130, 0x3230, 0x3334, 0x3430, 0x3534,
+	0x3624, 0x3720, 0x3828, 0x392c, 0x3a2c, 0x3b28, 0x3c2c, 0x3d28,
+	0x3e28, 0x3f2c, 0x4010, 0x4114, 0x4214, 0x4310, 0x4414, 0x4510,
+	0x4600, 0x4704, 0x480c, 0x4908, 0x4a08, 0x4b0c, 0x4c08, 0x4d0c,
+	0x4e0c, 0x4f08, 0x5014, 0x5110, 0x5210, 0x5314, 0x5410, 0x5514,
+	0x5604, 0x5700, 0x5808, 0x590c, 0x5a0c, 0x5b08, 0x5c0c, 0x5d08,
+	0x5e08, 0x5f0c, 0x6034, 0x6130, 0x6230, 0x6334, 0x6430, 0x6534,
+	0x6624, 0x6720, 0x6828, 0x692c, 0x6a2c, 0x6b28, 0x6c2c, 0x6d28,
+	0x6e28, 0x6f2c, 0x7030, 0x7134, 0x7234, 0x7330, 0x7434, 0x7530,
+	0x7620, 0x7724, 0x782c, 0x7928, 0x7a28, 0x7b2c, 0x7c28, 0x7d2c,
+	0x7e2c, 0x7f28, 0x8090, 0x8194, 0x8294, 0x8390, 0x8494, 0x8590,
+	0x8680, 0x8784, 0x888c, 0x8988, 0x8a88, 0x8b8c, 0x8c88, 0x8d8c,
+	0x8e8c, 0x8f88, 0x9094, 0x9190, 0x9290, 0x9394, 0x9490, 0x9594,
+	0x9684, 0x9780, 0x9888, 0x998c, 0x9a8c, 0x9b88, 0x9c8c, 0x9d88,
+	0x9e88, 0x9f8c, 0x0055, 0x0111, 0x0211, 0x0315, 0x0411, 0x0515,
+	0x0605, 0x0701, 0x0809, 0x090d, 0x0a0d, 0x0b09, 0x0c0d, 0x0d09,
+	0x0e09, 0x0f0d, 0x1011, 0x1115, 0x1215, 0x1311, 0x1415, 0x1511,
+	0x1601, 0x1705, 0x180d, 0x1909, 0x1a09, 0x1b0d, 0x1c09, 0x1d0d,
+	0x1e0d, 0x1f09, 0x2031, 0x2135, 0x2235, 0x2331, 0x2435, 0x2531,
+	0x2621, 0x2725, 0x282d, 0x2929, 0x2a29, 0x2b2d, 0x2c29, 0x2d2d,
+	0x2e2d, 0x2f29, 0x3035, 0x3131, 0x3231, 0x3335, 0x3431, 0x3535,
+	0x3625, 0x3721, 0x3829, 0x392d, 0x3a2d, 0x3b29, 0x3c2d, 0x3d29,
+	0x3e29, 0x3f2d, 0x4011, 0x4115, 0x4215, 0x4311, 0x4415, 0x4511,
+	0x4601, 0x4705, 0x480d, 0x4909, 0x4a09, 0x4b0d, 0x4c09, 0x4d0d,
+	0x4e0d, 0x4f09, 0x5015, 0x5111, 0x5211, 0x5315, 0x5411, 0x5515,
+	0x5605, 0x5701, 0x5809, 0x590d, 0x5a0d, 0x5b09, 0x5c0d, 0x5d09,
+	0x5e09, 0x5f0d, 0x6035, 0x6131, 0x6231, 0x6335, 0x6431, 0x6535,
+	0x6625, 0x6721, 0x6829, 0x692d, 0x6a2d, 0x6b29, 0x6c2d, 0x6d29,
+	0x6e29, 0x6f2d, 0x7031, 0x7135, 0x7235, 0x7331, 0x7435, 0x7531,
+	0x7621, 0x7725, 0x782d, 0x7929, 0x7a29, 0x7b2d, 0x7c29, 0x7d2d,
+	0x7e2d, 0x7f29, 0x8091, 0x8195, 0x8295, 0x8391, 0x8495, 0x8591,
+	0x8681, 0x8785, 0x888d, 0x8989, 0x8a89, 0x8b8d, 0x8c89, 0x8d8d,
+	0x8e8d, 0x8f89, 0x9095, 0x9191, 0x9291, 0x9395, 0x9491, 0x9595,
+	0x9685, 0x9781, 0x9889, 0x998d, 0x9a8d, 0x9b89, 0x9c8d, 0x9d89,
+	0x9e89, 0x9f8d, 0xa0b5, 0xa1b1, 0xa2b1, 0xa3b5, 0xa4b1, 0xa5b5,
+	0xa6a5, 0xa7a1, 0xa8a9, 0xa9ad, 0xaaad, 0xaba9, 0xacad, 0xada9,
+	0xaea9, 0xafad, 0xb0b1, 0xb1b5, 0xb2b5, 0xb3b1, 0xb4b5, 0xb5b1,
+	0xb6a1, 0xb7a5, 0xb8ad, 0xb9a9, 0xbaa9, 0xbbad, 0xbca9, 0xbdad,
+	0xbead, 0xbfa9, 0xc095, 0xc191, 0xc291, 0xc395, 0xc491, 0xc595,
+	0xc685, 0xc781, 0xc889, 0xc98d, 0xca8d, 0xcb89, 0xcc8d, 0xcd89,
+	0xce89, 0xcf8d, 0xd091, 0xd195, 0xd295, 0xd391, 0xd495, 0xd591,
+	0xd681, 0xd785, 0xd88d, 0xd989, 0xda89, 0xdb8d, 0xdc89, 0xdd8d,
+	0xde8d, 0xdf89, 0xe0b1, 0xe1b5, 0xe2b5, 0xe3b1, 0xe4b5, 0xe5b1,
+	0xe6a1, 0xe7a5, 0xe8ad, 0xe9a9, 0xeaa9, 0xebad, 0xeca9, 0xedad,
+	0xeead, 0xefa9, 0xf0b5, 0xf1b1, 0xf2b1, 0xf3b5, 0xf4b1, 0xf5b5,
+	0xf6a5, 0xf7a1, 0xf8a9, 0xf9ad, 0xfaad, 0xfba9, 0xfcad, 0xfda9,
+	0xfea9, 0xffad, 0x0055, 0x0111, 0x0211, 0x0315, 0x0411, 0x0515,
+	0x0605, 0x0701, 0x0809, 0x090d, 0x0a0d, 0x0b09, 0x0c0d, 0x0d09,
+	0x0e09, 0x0f0d, 0x1011, 0x1115, 0x1215, 0x1311, 0x1415, 0x1511,
+	0x1601, 0x1705, 0x180d, 0x1909, 0x1a09, 0x1b0d, 0x1c09, 0x1d0d,
+	0x1e0d, 0x1f09, 0x2031, 0x2135, 0x2235, 0x2331, 0x2435, 0x2531,
+	0x2621, 0x2725, 0x282d, 0x2929, 0x2a29, 0x2b2d, 0x2c29, 0x2d2d,
+	0x2e2d, 0x2f29, 0x3035, 0x3131, 0x3231, 0x3335, 0x3431, 0x3535,
+	0x3625, 0x3721, 0x3829, 0x392d, 0x3a2d, 0x3b29, 0x3c2d, 0x3d29,
+	0x3e29, 0x3f2d, 0x4011, 0x4115, 0x4215, 0x4311, 0x4415, 0x4511,
+	0x4601, 0x4705, 0x480d, 0x4909, 0x4a09, 0x4b0d, 0x4c09, 0x4d0d,
+	0x4e0d, 0x4f09, 0x5015, 0x5111, 0x5211, 0x5315, 0x5411, 0x5515,
+	0x5605, 0x5701, 0x5809, 0x590d, 0x5a0d, 0x5b09, 0x5c0d, 0x5d09,
+	0x5e09, 0x5f0d, 0x6035, 0x6131, 0x6231, 0x6335, 0x6431, 0x6535,
+	0xfabe, 0xfbba, 0xfcbe, 0xfdba, 0xfeba, 0xffbe, 0x0046, 0x0102,
+	0x0202, 0x0306, 0x0402, 0x0506, 0x0606, 0x0702, 0x080a, 0x090e,
+	0x0a1e, 0x0b1a, 0x0c1e, 0x0d1a, 0x0e1a, 0x0f1e, 0x1002, 0x1106,
+	0x1206, 0x1302, 0x1406, 0x1502, 0x1602, 0x1706, 0x180e, 0x190a,
+	0x1a1a, 0x1b1e, 0x1c1a, 0x1d1e, 0x1e1e, 0x1f1a, 0x2022, 0x2126,
+	0x2226, 0x2322, 0x2426, 0x2522, 0x2622, 0x2726, 0x282e, 0x292a,
+	0x2a3a, 0x2b3e, 0x2c3a, 0x2d3e, 0x2e3e, 0x2f3a, 0x3026, 0x3122,
+	0x3222, 0x3326, 0x3422, 0x3526, 0x3626, 0x3722, 0x382a, 0x392e,
+	0x3a3e, 0x3b3a, 0x3c3e, 0x3d3a, 0x3e3a, 0x3f3e, 0x4002, 0x4106,
+	0x4206, 0x4302, 0x4406, 0x4502, 0x4602, 0x4706, 0x480e, 0x490a,
+	0x4a1a, 0x4b1e, 0x4c1a, 0x4d1e, 0x4e1e, 0x4f1a, 0x5006, 0x5102,
+	0x5202, 0x5306, 0x5402, 0x5506, 0x5606, 0x5702, 0x580a, 0x590e,
+	0x5a1e, 0x5b1a, 0x5c1e, 0x5d1a, 0x5e1a, 0x5f1e, 0x6026, 0x6122,
+	0x6222, 0x6326, 0x6422, 0x6526, 0x6626, 0x6722, 0x682a, 0x692e,
+	0x6a3e, 0x6b3a, 0x6c3e, 0x6d3a, 0x6e3a, 0x6f3e, 0x7022, 0x7126,
+	0x7226, 0x7322, 0x7426, 0x7522, 0x7622, 0x7726, 0x782e, 0x792a,
+	0x7a3a, 0x7b3e, 0x7c3a, 0x7d3e, 0x7e3e, 0x7f3a, 0x8082, 0x8186,
+	0x8286, 0x8382, 0x8486, 0x8582, 0x8682, 0x8786, 0x888e, 0x898a,
+	0x8a9a, 0x8b9e, 0x8c9a, 0x8d9e, 0x8e9e, 0x8f9a, 0x9086, 0x9182,
+	0x9282, 0x9386, 0x3423, 0x3527, 0x3627, 0x3723, 0x382b, 0x392f,
+	0x3a3f, 0x3b3b, 0x3c3f, 0x3d3b, 0x3e3b, 0x3f3f, 0x4003, 0x4107,
+	0x4207, 0x4303, 0x4407, 0x4503, 0x4603, 0x4707, 0x480f, 0x490b,
+	0x4a1b, 0x4b1f, 0x4c1b, 0x4d1f, 0x4e1f, 0x4f1b, 0x5007, 0x5103,
+	0x5203, 0x5307, 0x5403, 0x5507, 0x5607, 0x5703, 0x580b, 0x590f,
+	0x5a1f, 0x5b1b, 0x5c1f, 0x5d1b, 0x5e1b, 0x5f1f, 0x6027, 0x6123,
+	0x6223, 0x6327, 0x6423, 0x6527, 0x6627, 0x6723, 0x682b, 0x692f,
+	0x6a3f, 0x6b3b, 0x6c3f, 0x6d3b, 0x6e3b, 0x6f3f, 0x7023, 0x7127,
+	0x7227, 0x7323, 0x7427, 0x7523, 0x7623, 0x7727, 0x782f, 0x792b,
+	0x7a3b, 0x7b3f, 0x7c3b, 0x7d3f, 0x7e3f, 0x7f3b, 0x8083, 0x8187,
+	0x8287, 0x8383, 0x8487, 0x8583, 0x8683, 0x8787, 0x888f, 0x898b,
+	0x8a9b, 0x8b9f, 0x8c9b, 0x8d9f, 0x8e9f, 0x8f9b, 0x9087, 0x9183,
+	0x9283, 0x9387, 0x9483, 0x9587, 0x9687, 0x9783, 0x988b, 0x998f,
+	0x9a9f, 0x9b9b, 0x9c9f, 0x9d9b, 0x9e9b, 0x9f9f, 0xa0a7, 0xa1a3,
+	0xa2a3, 0xa3a7, 0xa4a3, 0xa5a7, 0xa6a7, 0xa7a3, 0xa8ab, 0xa9af,
+	0xaabf, 0xabbb, 0xacbf, 0xadbb, 0xaebb, 0xafbf, 0xb0a3, 0xb1a7,
+	0xb2a7, 0xb3a3, 0xb4a7, 0xb5a3, 0xb6a3, 0xb7a7, 0xb8af, 0xb9ab,
+	0xbabb, 0xbbbf, 0xbcbb, 0xbdbf, 0xbebf, 0xbfbb, 0xc087, 0xc183,
+	0xc283, 0xc387, 0xc483, 0xc587, 0xc687, 0xc783, 0xc88b, 0xc98f,
+	0xca9f, 0xcb9b, 0xcc9f, 0xcd9b, 0xce9b, 0xcf9f, 0xd083, 0xd187,
+	0xd287, 0xd383, 0xd487, 0xd583, 0xd683, 0xd787, 0xd88f, 0xd98b,
+	0xda9b, 0xdb9f, 0xdc9b, 0xdd9f, 0xde9f, 0xdf9b, 0xe0a3, 0xe1a7,
+	0xe2a7, 0xe3a3, 0xe4a7, 0xe5a3, 0xe6a3, 0xe7a7, 0xe8af, 0xe9ab,
+	0xeabb, 0xebbf, 0xecbb, 0xedbf, 0xeebf, 0xefbb, 0xf0a7, 0xf1a3,
+	0xf2a3, 0xf3a7, 0xf4a3, 0xf5a7, 0xf6a7, 0xf7a3, 0xf8ab, 0xf9af,
+	0xfabf, 0xfbbb, 0xfcbf, 0xfdbb, 0xfebb, 0xffbf, 0x0047, 0x0103,
+	0x0203, 0x0307, 0x0403, 0x0507, 0x0607, 0x0703, 0x080b, 0x090f,
+	0x0a1f, 0x0b1b, 0x0c1f, 0x0d1b, 0x0e1b, 0x0f1f, 0x1003, 0x1107,
+	0x1207, 0x1303, 0x1407, 0x1503, 0x1603, 0x1707, 0x180f, 0x190b,
+	0x1a1b, 0x1b1f, 0x1c1b, 0x1d1f, 0x1e1f, 0x1f1b, 0x2023, 0x2127,
+	0x2227, 0x2323, 0x2427, 0x2523, 0x2623, 0x2727, 0x282f, 0x292b,
+	0x2a3b, 0x2b3f, 0x2c3b, 0x2d3f, 0x2e3f, 0x2f3b, 0x3027, 0x3123,
+	0x3223, 0x3327, 0x3423, 0x3527, 0x3627, 0x3723, 0x382b, 0x392f,
+	0x3a3f, 0x3b3b, 0x3c3f, 0x3d3b, 0x3e3b, 0x3f3f, 0x4003, 0x4107,
+	0x4207, 0x4303, 0x4407, 0x4503, 0x4603, 0x4707, 0x480f, 0x490b,
+	0x4a1b, 0x4b1f, 0x4c1b, 0x4d1f, 0x4e1f, 0x4f1b, 0x5007, 0x5103,
+	0x5203, 0x5307, 0x5403, 0x5507, 0x5607, 0x5703, 0x580b, 0x590f,
+	0x5a1f, 0x5b1b, 0x5c1f, 0x5d1b, 0x5e1b, 0x5f1f, 0x6027, 0x6123,
+	0x6223, 0x6327, 0x6423, 0x6527, 0x6627, 0x6723, 0x682b, 0x692f,
+	0x6a3f, 0x6b3b, 0x6c3f, 0x6d3b, 0x6e3b, 0x6f3f, 0x7023, 0x7127,
+	0x7227, 0x7323, 0x7427, 0x7523, 0x7623, 0x7727, 0x782f, 0x792b,
+	0x7a3b, 0x7b3f, 0x7c3b, 0x7d3f, 0x7e3f, 0x7f3b, 0x8083, 0x8187,
+	0x8287, 0x8383, 0x8487, 0x8583, 0x8683, 0x8787, 0x888f, 0x898b,
+	0x8a9b, 0x8b9f, 0x8c9b, 0x8d9f, 0x8e9f, 0x8f9b, 0x9087, 0x9183,
+	0x9283, 0x9387, 0x9483, 0x9587, 0x9687, 0x9783, 0x988b, 0x998f
+};
diff --git a/tool/tilem-src/emu/z80ddfd.h b/tool/tilem-src/emu/z80ddfd.h
new file mode 100644
index 0000000..63288f6
--- /dev/null
+++ b/tool/tilem-src/emu/z80ddfd.h
@@ -0,0 +1,343 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef PREFIX_DD
+# define RegH IXh
+# define RegL IXl
+# define RegHL IX
+#else
+# define RegH IYh
+# define RegL IYl
+# define RegHL IY
+#endif
+
+switch (op) {
+ case 0x09:			/* ADD IX, BC */
+	 WZ = RegHL + 1;
+	 add16(RegHL, BC);
+	 delay(11);
+	 break;
+ case 0x19:			/* ADD IX, DE */
+	 WZ = RegHL + 1;
+	 add16(RegHL, DE);
+	 delay(11);
+	 break;
+ case 0x21:			/* LD IX, nn */
+	 RegHL = readw(PC);
+	 PC += 2;
+	 delay(10);
+	 break;
+ case 0x22:			/* LD (nn), IX */
+	 WZ = readw(PC);
+	 PC += 2;
+	 writeb(WZ, RegL);
+	 WZ++;
+	 writeb(WZ, RegH);
+	 delay(16);
+	 break;
+ case 0x23:			/* INC IX */
+	 RegHL++;
+	 delay(6);
+	 break;
+ case 0x24:			/* INC IXH */
+	 UNDOCUMENTED(4);
+	 inc(RegH);
+	 delay(4);
+	 break;
+ case 0x25:			/* DEC IXH */
+	 UNDOCUMENTED(4);
+	 dec(RegH);
+	 delay(4);
+	 break;
+ case 0x26:			/* LD IXH, n */
+	 UNDOCUMENTED(4);
+	 RegH = readb(PC++);
+	 delay(7);
+	 break;
+ case 0x29:			/* ADD IX, IX */
+	 WZ = RegHL + 1;
+	 add16(RegHL, RegHL);
+	 delay(11);
+	 break;
+ case 0x2A:			/* LD IX, (nn) */
+	 WZ = readw(PC);
+	 PC += 2;
+	 RegL = readb(WZ);
+	 WZ++;
+	 RegH = readb(WZ);
+	 delay(16);
+	 break;
+ case 0x2B:			/* DEC IX */
+	 RegHL--;
+	 delay(6);
+	 break;
+ case 0x2C:			/* INC IXL */
+	 UNDOCUMENTED(4);
+	 inc(RegL);
+	 delay(4);
+	 break;
+ case 0x2D:			/* DEC IXL */
+	 UNDOCUMENTED(4);
+	 dec(RegL);
+	 delay(4);
+	 break;
+ case 0x2E:			/* LD IXL,n */
+	 UNDOCUMENTED(4);
+	 RegL = readb(PC++);
+	 delay(7);
+	 break;
+
+ case 0x34:			/* INC (IX + n) */
+	 offs = (int) (signed char) readb(PC++);
+	 WZ = RegHL + offs;
+	 tmp1 = readb(WZ);
+	 inc(tmp1);
+	 writeb(WZ, tmp1);
+	 delay(19);
+	 break;
+ case 0x35:			/* DEC (IX + n) */
+	 offs = (int) (signed char) readb(PC++);
+	 WZ = RegHL + offs;
+	 tmp1 = readb(WZ);
+	 dec(tmp1);
+	 writeb(WZ, tmp1);
+	 delay(19);
+	 break;
+ case 0x36:			/* LD (IX + n), n */
+	 offs = (int) (signed char) readb(PC++);
+	 writeb(RegHL + offs, readb(PC++));
+	 delay(15);		/* Yes, really! */
+	 break;
+
+ case 0x39:			/* ADD IX, SP */
+	 WZ = RegHL + 1;
+	 add16(RegHL, SP);
+	 delay(11);
+	 break;
+
+ case 0x44: UNDOCUMENTED(4); B = RegH; delay(4); break;
+ case 0x45: UNDOCUMENTED(4); B = RegL; delay(4); break;
+ case 0x46:
+	 offs = (int) (signed char) readb(PC++);
+	 B = readb(RegHL + offs);
+	 delay(15);
+	 break;
+
+ case 0x4C: UNDOCUMENTED(4); C = RegH; delay(4); break;
+ case 0x4D: UNDOCUMENTED(4); C = RegL; delay(4); break;
+ case 0x4E:
+	 offs = (int) (signed char) readb(PC++);
+	 C = readb(RegHL + offs);
+	 delay(15);
+	 break;
+
+ case 0x54: UNDOCUMENTED(4); D = RegH; delay(4); break;
+ case 0x55: UNDOCUMENTED(4); D = RegL; delay(4); break;
+ case 0x56:
+	 offs = (int) (signed char) readb(PC++);
+	 D = readb(RegHL + offs);
+	 delay(15);
+	 break;
+
+ case 0x5C: UNDOCUMENTED(4); E = RegH; delay(4); break;
+ case 0x5D: UNDOCUMENTED(4); E = RegL; delay(4); break;
+ case 0x5E:
+	 offs = (int) (signed char) readb(PC++);
+	 E = readb(RegHL + offs);
+	 delay(15);
+	 break;
+
+ case 0x60: UNDOCUMENTED(4); RegH = B; delay(4); break;
+ case 0x61: UNDOCUMENTED(4); RegH = C; delay(4); break;
+ case 0x62: UNDOCUMENTED(4); RegH = D; delay(4); break;
+ case 0x63: UNDOCUMENTED(4); RegH = E; delay(4); break;
+ case 0x64: UNDOCUMENTED(4); RegH = RegH; delay(4); break;
+ case 0x65: UNDOCUMENTED(4); RegH = RegL; delay(4); break;
+ case 0x66:
+	 offs = (int) (signed char) readb(PC++);
+	 H = readb(RegHL + offs);
+	 delay(15);
+	 break;
+ case 0x67: UNDOCUMENTED(4); RegH = A; delay(4); break;
+
+ case 0x68: UNDOCUMENTED(4); RegL = B; delay(4); break;
+ case 0x69: UNDOCUMENTED(4); RegL = C; delay(4); break;
+ case 0x6A: UNDOCUMENTED(4); RegL = D; delay(4); break;
+ case 0x6B: UNDOCUMENTED(4); RegL = E; delay(4); break;
+ case 0x6C: UNDOCUMENTED(4); RegL = RegH; delay(4); break;
+ case 0x6D: UNDOCUMENTED(4); RegL = RegL; delay(4); break;
+ case 0x6E:
+	 offs = (int) (signed char) readb(PC++);
+	 L = readb(RegHL + offs);
+	 delay(15);
+	 break;
+ case 0x6F: UNDOCUMENTED(4); RegL = A; delay(4); break;
+
+ case 0x70:
+	 offs = (int) (signed char) readb(PC++);
+	 writeb(RegHL + offs, B);
+	 delay(15);
+	 break;
+ case 0x71:
+	 offs = (int) (signed char) readb(PC++);
+	 writeb(RegHL + offs, C);
+	 delay(15);
+	 break;
+ case 0x72:
+	 offs = (int) (signed char) readb(PC++);
+	 writeb(RegHL + offs, D);
+	 delay(15);
+	 break;
+ case 0x73:
+	 offs = (int) (signed char) readb(PC++);
+	 writeb(RegHL + offs, E);
+	 delay(15);
+	 break;
+ case 0x74:
+	 offs = (int) (signed char) readb(PC++);
+	 writeb(RegHL + offs, H);
+	 delay(15);
+	 break;
+ case 0x75:
+	 offs = (int) (signed char) readb(PC++);
+	 writeb(RegHL + offs, L);
+	 delay(15);
+	 break;
+ case 0x77:
+	 offs = (int) (signed char) readb(PC++);
+	 writeb(RegHL + offs, A);
+	 delay(15);
+	 break;
+
+ case 0x7C: UNDOCUMENTED(4); A = RegH; delay(4); break;
+ case 0x7D: UNDOCUMENTED(4); A = RegL; delay(4); break;
+ case 0x7E:
+	 offs = (int) (signed char) readb(PC++);
+	 A = readb(RegHL + offs);
+	 delay(15);
+	 break;
+
+ case 0x84: UNDOCUMENTED(4); add8(A, RegH); delay(4); break;
+ case 0x85: UNDOCUMENTED(4); add8(A, RegL); delay(4); break;
+ case 0x86:
+	 offs = (int) (signed char) readb(PC++);
+	 add8(A, readb(RegHL + offs));
+	 delay(7);
+	 break;
+
+ case 0x8C: UNDOCUMENTED(4); adc8(A, RegH); delay(4); break;
+ case 0x8D: UNDOCUMENTED(4); adc8(A, RegL); delay(4); break;
+ case 0x8E:
+	 offs = (int) (signed char) readb(PC++);
+	 adc8(A, readb(RegHL + offs));
+	 delay(15);
+	 break;
+
+ case 0x94: UNDOCUMENTED(4); sub8(A, RegH); delay(4); break;
+ case 0x95: UNDOCUMENTED(4); sub8(A, RegL); delay(4); break;
+ case 0x96: 
+	 offs = (int) (signed char) readb(PC++);
+	 sub8(A, readb(RegHL + offs));
+	 delay(15);
+	 break;
+
+ case 0x9C: UNDOCUMENTED(4); sbc8(A, RegH); delay(4); break;
+ case 0x9D: UNDOCUMENTED(4); sbc8(A, RegL); delay(4); break;
+ case 0x9E: 
+	 offs = (int) (signed char) readb(PC++);
+	 sbc8(A, readb(RegHL + offs));
+	 delay(15);
+	 break;
+
+ case 0xA4: UNDOCUMENTED(4); and(A, RegH); delay(4); break;
+ case 0xA5: UNDOCUMENTED(4); and(A, RegL); delay(4); break;
+ case 0xA6: 
+	 offs = (int) (signed char) readb(PC++);
+	 and(A, readb(RegHL + offs));
+	 delay(15);
+	 break;
+
+ case 0xAC: UNDOCUMENTED(4); xor(A, RegH); delay(4); break;
+ case 0xAD: UNDOCUMENTED(4); xor(A, RegL); delay(4); break;
+ case 0xAE: 
+	 offs = (int) (signed char) readb(PC++);
+	 xor(A, readb(RegHL + offs));
+	 delay(15);
+	 break;
+
+ case 0xB4: UNDOCUMENTED(4); or(A, RegH); delay(4); break;
+ case 0xB5: UNDOCUMENTED(4); or(A, RegL); delay(4); break;
+ case 0xB6: 
+	 offs = (int) (signed char) readb(PC++);
+	 or(A, readb(RegHL + offs));
+	 delay(15);
+	 break;
+
+ case 0xBC: UNDOCUMENTED(4); cp(A, RegH); delay(4); break;
+ case 0xBD: UNDOCUMENTED(4); cp(A, RegL); delay(4); break;
+ case 0xBE: 
+	 offs = (int) (signed char) readb(PC++);
+	 cp(A, readb(RegHL + offs));
+	 delay(15);
+	 break;
+
+ case 0xCB:
+	 offs = (int) (signed char) readb(PC++);
+	 WZ = RegHL + offs;
+	 op = readb(PC++);
+#ifdef PREFIX_DD
+	 goto opcode_ddcb;
+#else
+	 goto opcode_fdcb;
+#endif
+
+ case 0xE1:			/* POP IX */
+	 pop(RegHL);
+	 delay(10);
+	 break;
+ case 0xE3:			/* EX (SP), IX */
+	 WZ = readw(SP);
+	 writew(SP, RegHL);
+	 RegHL = WZ;
+	 delay(19);
+	 break;
+ case 0xE5:			/* PUSH IX */
+	 push(RegHL);
+	 delay(11);
+	 break;
+
+ case 0xE9:			/* JP IX */
+	 PC = RegHL;
+	 delay(4);
+	 break;
+
+ case 0xF9:			/* LD SP, IX */
+	 SP = RegHL;
+	 delay(4);
+	 break;
+
+ default:
+	 goto opcode_main;
+ }
+
+#undef RegH
+#undef RegL
+#undef RegHL
+
diff --git a/tool/tilem-src/emu/z80ed.h b/tool/tilem-src/emu/z80ed.h
new file mode 100644
index 0000000..930bf63
--- /dev/null
+++ b/tool/tilem-src/emu/z80ed.h
@@ -0,0 +1,457 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+switch (op) {
+ case 0x40:			/* IN B, (C) */
+	 WZ = BC;
+	 delay(12);
+	 in(B);
+	 break;
+ case 0x41:			/* OUT (C), B */
+	 delay(12);
+	 output(BC, B);
+	 break;
+ case 0x42:			/* SBC HL, BC */
+	 WZ = HL + 1;
+	 sbc16(HLw, BCw);
+	 delay(15);
+	 break;
+ case 0x43:			/* LD (nn), BC */
+	 WZ = readw(PC);
+	 PC += 2;
+	 writew(WZ++, BC);
+	 delay(20);
+	 break;
+ case 0x44:			/* NEG */
+	 neg(A);
+	 delay(8);
+	 break;
+ case 0x45:			/* RETN */
+	 IFF1 = IFF2;
+	 pop(PC);
+	 delay(14);
+	 break;
+ case 0x46:			/* IM 0 */
+	 IM = 0;
+	 delay(8);
+	 break;
+ case 0x47:			/* LD I,A */
+	 I = A;
+	 delay(9);
+	 break;
+
+ case 0x48:			/* IN C, (C) */
+	 WZ = BC;
+	 delay(12);
+	 in(C);
+	 break;
+ case 0x49:			/* OUT (C), C */
+	 delay(12);
+	 output(BC, C);
+	 break;
+ case 0x4A:			/* ADC HL, BC */
+	 WZ = HL + 1;
+	 adc16(HLw, BCw);
+	 delay(15);
+	 break;
+ case 0x4B:			/* LD BC, (nn) */
+	 WZ = readw(PC);
+	 PC += 2;
+	 BC = readw(WZ++);
+	 delay(20);
+	 break;
+ case 0x4C:			/* NEG */
+	 UNDOCUMENTED(8);
+	 neg(A);
+	 delay(8);
+	 break;
+ case 0x4D:			/* RETI */
+	 IFF1 = IFF2;
+	 pop(PC);
+	 delay(14);
+	 break;
+ case 0x4E:			/* IM 0 */
+	 UNDOCUMENTED(8);
+	 IM = 0;
+	 delay(8);
+	 break;
+ case 0x4F:			/* LD R,A */
+	 Rl = A;
+	 Rh = A & 0x80;
+	 delay(9);
+	 break;
+
+ case 0x50:			/* IN D, (C) */
+	 WZ = BC;
+	 delay(12);
+	 in(D);
+	 break;
+ case 0x51:			/* OUT (C), D */
+	 delay(12);
+	 output(BC, D);
+	 break;
+ case 0x52:			/* SBC HL, DE */
+	 WZ = HL + 1;
+	 sbc16(HLw, DEw);
+	 delay(15);
+	 break;
+ case 0x53:			/* LD (nn), DE */
+	 WZ = readw(PC);
+	 PC += 2;
+	 writew(WZ++, DE);
+	 delay(20);
+	 break;
+ case 0x54:			/* NEG */
+	 UNDOCUMENTED(8);
+	 neg(A);
+	 delay(8);
+	 break;
+ case 0x55:			/* RETN */
+	 UNDOCUMENTED(8);
+	 IFF1 = IFF2;
+	 pop(PC);
+	 delay(14);
+	 break;
+ case 0x56:			/* IM 1 */
+	 IM = 1;
+	 delay(8);
+	 break;
+ case 0x57:			/* LD A,I */
+	 ld_a_ir(I);
+	 delay(9);
+	 break;
+
+ case 0x58:			/* IN E, (C) */
+	 WZ = BC;
+	 delay(12);
+	 in(E);
+	 break;
+ case 0x59:			/* OUT (C), E */
+	 delay(12);
+	 output(BC, E);
+	 break;
+ case 0x5A:			/* ADC HL, DE */
+	 WZ = HL + 1;
+	 adc16(HLw, DEw);
+	 delay(15);
+	 break;
+ case 0x5B:			/* LD DE, (nn) */
+	 WZ = readw(PC);
+	 PC += 2;
+	 DE = readw(WZ++);
+	 delay(20);
+	 break;
+ case 0x5C:			/* NEG */
+	 UNDOCUMENTED(8);
+	 neg(A);
+	 delay(8);
+	 break;
+ case 0x5D:			/* RETN */
+	 UNDOCUMENTED(8);
+	 IFF1 = IFF2;
+	 pop(PC);
+	 delay(14);
+	 break;
+ case 0x5E:			/* IM 2 */
+	 IM = 2;
+	 delay(8);
+	 break;
+ case 0x5F:			/* LD A,R */
+	 ld_a_ir(R);
+	 delay(9);
+	 break;
+
+ case 0x60:			/* IN H, (C) */
+	 WZ = BC;
+	 delay(12);
+	 in(H);
+	 break;
+ case 0x61:			/* OUT (C), H */
+	 delay(12);
+	 output(BC, H);
+	 break;
+ case 0x62:			/* SBC HL, HL */
+	 WZ = HL + 1;
+	 sbc16(HLw, HLw);
+	 delay(15);
+	 break;
+ case 0x63:			/* LD (nn), HL */
+	 WZ = readw(PC);
+	 PC += 2;
+	 writew(WZ++, HL);
+	 delay(20);
+	 break;
+ case 0x64:			/* NEG */
+	 UNDOCUMENTED(8);
+	 neg(A);
+	 delay(8);
+	 break;
+ case 0x65:			/* RETN */
+	 UNDOCUMENTED(8);
+	 IFF1 = IFF2;
+	 pop(PC);
+	 delay(14);
+	 break;
+ case 0x66:			/* IM 0 */
+	 UNDOCUMENTED(8);
+	 IM = 0;
+	 delay(8);
+	 break;
+ case 0x67:			/* RRD */
+	 rrd;
+	 delay(18);
+	 break;
+
+ case 0x68:			/* IN L, (C) */
+	 WZ = BC;
+	 delay(12);
+	 in(L);
+	 break;
+ case 0x69:			/* OUT (C), L */
+	 delay(12);
+	 output(BC, L);
+	 break;
+ case 0x6A:			/* ADC HL, HL */
+	 WZ = HL + 1;
+	 adc16(HLw, HLw);
+	 delay(15);
+	 break;
+ case 0x6B:			/* LD HL, (nn) */
+	 WZ = readw(PC);
+	 PC += 2;
+	 HL = readw(WZ++);
+	 delay(20);
+	 break;
+ case 0x6C:			/* NEG */
+	 UNDOCUMENTED(8);
+	 neg(A);
+	 delay(8);
+	 break;
+ case 0x6D:			/* RETN */
+	 UNDOCUMENTED(8);
+	 IFF1 = IFF2;
+	 pop(PC);
+	 delay(14);
+	 break;
+ case 0x6E:			/* IM 0 */
+	 UNDOCUMENTED(8);
+	 IM = 0;
+	 delay(8);
+	 break;
+ case 0x6F:			/* RLD */
+	 rld;
+	 delay(18);
+	 break;
+
+ case 0x70:			/* IN (C) */
+	 UNDOCUMENTED(8);
+	 WZ = BC;
+	 delay(12);
+	 in(tmp1);
+	 break;
+ case 0x71:			/* OUT (C), 0 */
+	 UNDOCUMENTED(8);
+	 delay(12);
+	 output(BC, 0);
+	 break;
+ case 0x72:			/* SBC HL, SP */
+	 WZ = HL + 1;
+	 sbc16(HLw, SPw);
+	 delay(15);
+	 break;
+ case 0x73:			/* LD (nn), SP */
+	 WZ = readw(PC);
+	 PC += 2;
+	 writew(WZ++, SP);
+	 delay(20);
+	 break;
+ case 0x74:			/* NEG */
+	 UNDOCUMENTED(8);	
+	 neg(A);
+	 delay(8);
+	 break;
+ case 0x75:			/* RETN */
+	 UNDOCUMENTED(8);
+	 IFF1 = IFF2;
+	 pop(PC);
+	 delay(14);
+	 break;
+ case 0x76:			/* IM 1 */
+	 UNDOCUMENTED(8);
+	 IM = 1;
+	 delay(8);
+	 break;
+
+ case 0x78:			/* IN A, (C) */
+	 WZ = BC;
+	 delay(12);
+	 in(A);
+	 break;
+ case 0x79:			/* OUT (C), A */
+	 delay(12);
+	 output(BC, A);
+	 break;
+ case 0x7A:			/* ADC HL, SP */
+	 WZ = HL + 1;
+	 adc16(HLw, SPw);
+	 delay(15);
+	 break;
+ case 0x7B:			/* LD SP, (nn) */
+	 WZ = readw(PC);
+	 PC += 2;
+	 SP = readw(WZ);
+	 WZ++;
+	 delay(20);
+	 break;
+ case 0x7C:			/* NEG */
+	 UNDOCUMENTED(8);
+	 neg(A);
+	 delay(8);
+	 break;
+ case 0x7D:			/* RETN */
+	 UNDOCUMENTED(8);
+	 IFF1 = IFF2;
+	 pop(PC);
+	 delay(14);
+	 break;
+ case 0x7E:			/* IM 2 */
+	 UNDOCUMENTED(8);
+	 IM = 2;
+	 delay(8);
+	 break;
+
+ case 0xA0:			/* LDI */
+	 ldi;
+	 delay(16);
+	 break;
+ case 0xA1:			/* CPI */
+	 cpi;
+	 delay(16);
+	 break;
+ case 0xA2:			/* INI */
+	 delay(13);
+	 ini;
+	 delay(3);
+	 break;
+ case 0xA3:			/* OUTI */
+	 delay(16);
+	 outi;
+	 break;
+
+ case 0xA8:			/* LDD */
+	 ldd;
+	 delay(16);
+	 break;
+ case 0xA9:			/* CPD */
+	 cpd;
+	 delay(16);
+	 break;
+ case 0xAA:			/* IND */
+	 delay(13);
+	 ind;
+	 delay(3);
+	 break;
+ case 0xAB:			/* OUTD */
+	 delay(16);
+	 outd;
+	 break;
+
+ case 0xB0:			/* LDIR */
+	 ldi;
+	 if (BCw) {
+		 PC -= 2;
+		 delay(21);
+	 }
+	 else
+		 delay(16);
+	 break;
+ case 0xB1:			/* CPIR */
+	 cpi;
+	 if (BCw && !(F & FLAG_Z)) {
+		 PC -= 2;
+		 delay(21);
+	 }
+	 else
+		 delay(16);
+	 break;
+ case 0xB2:			/* INIR */
+	 delay(13);
+	 ini;
+	 if (B) {
+		 PC -= 2;
+		 delay(8);
+	 }
+	 else
+		 delay(3);
+	 break;
+ case 0xB3:			/* OTIR */
+	 delay(16);
+	 outi;
+	 if (B) {
+		 PC -= 2;
+		 delay(5);
+	 }
+	 break;
+
+ case 0xB8:			/* LDDR */
+	 ldd;
+	 if (BCw) {
+		 PC -= 2;
+		 delay(21);
+	 }
+	 else
+		 delay(16);
+	 break;
+ case 0xB9:			/* CPDR */
+	 cpd;
+	 if (BCw && !(F & FLAG_Z)) {
+		 PC -= 2;
+		 delay(21);
+	 }
+	 else
+		 delay(16);
+	 break;
+ case 0xBA:			/* INDR */
+	 delay(13);
+	 ind;
+	 if (B) {
+		 PC -= 2;
+		 delay(8);
+	 }
+	 else
+		 delay(3);
+	 break;
+ case 0xBB:			/* OTDR */
+	 delay(16);
+	 outd;
+	 if (B) {
+		 PC -= 2;
+		 delay(5);
+	 }
+	 break;
+
+ default:
+	 delay(8);
+	 if (calc->hw.z80_instr)
+		 (*calc->hw.z80_instr)(calc, 0xed00 | op);
+	 else if (calc->z80.emuflags & TILEM_Z80_BREAK_INVALID)
+		 tilem_z80_stop(calc, TILEM_STOP_INVALID_INST);
+	 break;
+ }
diff --git a/tool/tilem-src/emu/z80main.h b/tool/tilem-src/emu/z80main.h
new file mode 100644
index 0000000..26d83e7
--- /dev/null
+++ b/tool/tilem-src/emu/z80main.h
@@ -0,0 +1,893 @@
+/*
+ * libtilemcore - Graphing calculator emulation library
+ *
+ * Copyright (C) 2009 Benjamin Moody
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+switch (op) {
+ case 0x00:			/* NOP */
+	 delay(4);
+	 break;
+ case 0x01:			/* LD BC, nn */
+	 BC = readw(PC);
+	 PC += 2;
+	 delay(10);
+	 break;
+ case 0x02:			/* LD (BC), A */
+	 W = A;
+	 writeb(BC, A);
+	 delay(7);
+	 break;
+ case 0x03:			/* INC BC */
+	 BC++;
+	 delay(6);
+	 break;
+ case 0x04:			/* INC B */
+	 inc(B);
+	 delay(4);
+	 break;
+ case 0x05:			/* DEC B */
+	 dec(B);
+	 delay(4);
+	 break;
+ case 0x06:			/* LD B, n */
+	 B = readb(PC++);
+	 delay(7);
+	 break;
+ case 0x07:			/* RLCA */
+	 rlca;
+	 delay(4);
+	 break;
+
+ case 0x08:			/* EX AF, AF' */
+	 ex(AF, AF2);
+	 delay(4);
+	 break;
+ case 0x09:			/* ADD HL, BC */
+	 WZ = HL + 1;
+	 add16(HLw, BCw);
+	 delay(11);
+	 break;
+ case 0x0A:			/* LD A, (BC) */
+	 WZ = BC;
+	 A = readb(WZ++);
+	 delay(7);
+	 break;
+ case 0x0B:			/* DEC BC */
+	 BC--;
+	 delay(6);
+	 break;
+ case 0x0C:			/* INC C */
+	 inc(C);
+	 delay(4);
+	 break;
+ case 0x0D:			/* DEC C */
+	 dec(C);
+	 delay(4);
+	 break;
+ case 0x0E:			/* LD C, n */
+	 C = readb(PC++);
+	 delay(7);
+	 break;
+ case 0x0F:			/* RRCA */
+	 rrca;
+	 delay(4);
+	 break;
+
+ case 0x10:			/* DJNZ $+n */
+	 offs = (int) (signed char) readb(PC++);
+	 B--;
+	 if (B) {
+		 WZ = PC + offs;
+		 PC = WZ;
+		 delay(13);
+	 }
+	 else
+		 delay(8);
+	 break;
+ case 0x11:			/* LD DE, nn */
+	 DE = readw(PC);
+	 PC += 2;
+	 delay(10);
+	 break;
+ case 0x12:			/* LD (DE), A */
+	 W = A;
+	 writeb(DE, A);
+	 delay(7);
+	 break;
+ case 0x13:			/* INC DE */
+	 DE++;
+	 delay(6);
+	 break;
+ case 0x14:			/* INC D */
+	 inc(D);
+	 delay(4);
+	 break;
+ case 0x15:			/* DEC D */
+	 dec(D);
+	 delay(4);
+	 break;
+ case 0x16:			/* LD D, n */
+	 D = readb(PC++);
+	 delay(7);
+	 break;
+ case 0x17:			/* RLA */
+	 rla;
+	 delay(4);
+	 break;
+
+ case 0x18:			/* JR $+n */
+	 offs = (int) (signed char) readb(PC++);
+	 WZ = PC + offs;
+	 PC = WZ;
+	 delay(12);
+	 break;
+ case 0x19:			/* ADD HL, DE */
+	 WZ = HL + 1;
+	 add16(HLw, DEw);
+	 delay(11);
+	 break;
+ case 0x1A:			/* LD A, (DE) */
+	 WZ = DE;
+	 A = readb(WZ++);
+	 delay(7);
+	 break;
+ case 0x1B:			/* DEC DE */
+	 DE--;
+	 delay(6);
+	 break;
+ case 0x1C:			/* INC E */
+	 inc(E);
+	 delay(4);
+	 break;
+ case 0x1D:			/* DEC E */
+	 dec(E);
+	 delay(4);
+	 break;
+ case 0x1E:			/* LD E, n */
+	 E = readb(PC++);
+	 delay(7);
+	 break;
+ case 0x1F:			/* RRA */
+	 rra;
+	 delay(4);
+	 break;
+
+ case 0x20:			/* JR NZ, $+n */
+	 offs = (int) (signed char) readb(PC++);
+	 if (!(F & FLAG_Z)) {
+		 WZ = PC + offs;
+		 PC = WZ;
+		 delay(12);
+	 }
+	 else
+		 delay(7);
+	 break;
+ case 0x21:			/* LD HL, nn */
+	 HL = readw(PC);
+	 PC += 2;
+	 delay(10);
+	 break;
+ case 0x22:			/* LD (nn), HL */
+	 WZ = readw(PC);
+	 PC += 2;
+	 writew(WZ++, HL);
+	 delay(16);
+	 break;
+ case 0x23:			/* INC HL */
+	 HL++;
+	 delay(6);
+	 break;
+ case 0x24:			/* INC H */
+	 inc(H);
+	 delay(4);
+	 break;
+ case 0x25:			/* DEC H */
+	 dec(H);
+	 delay(4);
+	 break;
+ case 0x26:			/* LD H, n */
+	 H = readb(PC++);
+	 delay(7);
+	 break;
+ case 0x27:			/* DAA */
+	 daa;
+	 delay(4);
+	 break;
+
+ case 0x28:			/* JR Z, $+n */
+	 offs = (int) (signed char) readb(PC++);
+	 if (F & FLAG_Z) {
+		 WZ = PC + offs;
+		 PC = WZ;
+		 delay(12);
+	 }
+	 else
+		 delay(7);
+	 break;
+ case 0x29:			/* ADD HL, HL */
+	 WZ = HL + 1;
+	 add16(HLw, HLw);
+	 delay(11);
+	 break;
+ case 0x2A:			/* LD HL, (nn) */
+	 WZ = readw(PC);
+	 PC += 2;
+	 HL = readw(WZ++);
+	 delay(16);
+	 break;
+ case 0x2B:			/* DEC HL */
+	 HL--;
+	 delay(6);
+	 break;
+ case 0x2C:			/* INC L */
+	 inc(L);
+	 delay(4);
+	 break;
+ case 0x2D:			/* DEC L */
+	 dec(L);
+	 delay(4);
+	 break;
+ case 0x2E:			/* LD L,n */
+	 L = readb(PC++);
+	 delay(7);
+	 break;
+ case 0x2F:			/* CPL */
+	 cpl(A);
+	 delay(4);
+	 break;
+
+ case 0x30:			/* JR NC, $+n */
+	 offs = (int) (signed char) readb(PC++);
+	 if (!(F & FLAG_C)) {
+		 WZ = PC + offs;
+		 PC = WZ;
+		 delay(12);
+	 }
+	 else
+		 delay(7);
+	 break;
+ case 0x31:			/* LD SP, nn */
+	 SP = readw(PC);
+	 PC += 2;
+	 delay(10);
+	 break;
+ case 0x32:			/* LD (nn), A */
+	 tmp2 = readw(PC);
+	 PC += 2;
+	 writeb(tmp2, A);
+	 W = A;			/* is this really correct?! */
+	 delay(13);
+	 break;
+ case 0x33:			/* INC SP */
+	 SP++;
+	 delay(6);
+	 break;
+ case 0x34:			/* INC (HL) */
+	 tmp1 = readb(HL);
+	 inc(tmp1);
+	 writeb(HL, tmp1);
+	 delay(11);
+	 break;
+ case 0x35:			/* DEC (HL) */
+	 tmp1 = readb(HL);
+	 dec(tmp1);
+	 writeb(HL, tmp1);
+	 delay(11);
+	 break;
+ case 0x36:			/* LD (HL), n */
+	 tmp1 = readb(PC++);
+	 writeb(HL, tmp1);
+	 delay(10);
+	 break;
+ case 0x37:			/* SCF */
+	 F |= FLAG_C;
+	 delay(4);
+	 break;
+ case 0x38:			/* JR C, $+n */
+	 offs = (int) (signed char) readb(PC++);
+	 if (F & FLAG_C) {
+		 WZ = PC + offs;
+		 PC = WZ;
+		 delay(12);
+	 }
+	 else
+		 delay(7);
+	 break;
+ case 0x39:			/* ADD HL, SP */
+	 WZ = HL + 1;
+	 add16(HLw, SPw);
+	 delay(11);
+	 break;
+ case 0x3A:			/* LD A, (nn) */
+	 WZ = readw(PC);
+	 PC += 2;
+	 A = readb(WZ++);
+	 delay(13);
+	 break;
+ case 0x3B:			/* DEC SP */
+	 SP--;
+	 delay(6);
+	 break;
+ case 0x3C:			/* INC A */
+	 inc(A);
+	 delay(4);
+	 break;
+ case 0x3D:			/* DEC A */
+	 dec(A);
+	 delay(4);
+	 break;
+ case 0x3E:			/* LD A, n */
+	 A = readb(PC++);
+	 delay(7);
+	 break;
+ case 0x3F:			/* CCF */
+	 F ^= FLAG_C;
+	 delay(4);
+	 break;
+
+ case 0x40: B = B; delay(4); break;
+ case 0x41: B = C; delay(4); break;
+ case 0x42: B = D; delay(4); break;
+ case 0x43: B = E; delay(4); break;
+ case 0x44: B = H; delay(4); break;
+ case 0x45: B = L; delay(4); break;
+ case 0x46: B = readb(HL); delay(7); break;
+ case 0x47: B = A; delay(4); break;
+ case 0x48: C = B; delay(4); break;
+ case 0x49: C = C; delay(4); break;
+ case 0x4A: C = D; delay(4); break;
+ case 0x4B: C = E; delay(4); break;
+ case 0x4C: C = H; delay(4); break;
+ case 0x4D: C = L; delay(4); break;
+ case 0x4E: C = readb(HL); delay(7); break;
+ case 0x4F: C = A; delay(4); break;
+ case 0x50: D = B; delay(4); break;
+ case 0x51: D = C; delay(4); break;
+ case 0x52: D = D; delay(4); break;
+ case 0x53: D = E; delay(4); break;
+ case 0x54: D = H; delay(4); break;
+ case 0x55: D = L; delay(4); break;
+ case 0x56: D = readb(HL); delay(7); break;
+ case 0x57: D = A; delay(4); break;
+ case 0x58: E = B; delay(4); break;
+ case 0x59: E = C; delay(4); break;
+ case 0x5A: E = D; delay(4); break;
+ case 0x5B: E = E; delay(4); break;
+ case 0x5C: E = H; delay(4); break;
+ case 0x5D: E = L; delay(4); break;
+ case 0x5E: E = readb(HL); delay(7); break;
+ case 0x5F: E = A; delay(4); break;
+ case 0x60: H = B; delay(4); break;
+ case 0x61: H = C; delay(4); break;
+ case 0x62: H = D; delay(4); break;
+ case 0x63: H = E; delay(4); break;
+ case 0x64: H = H; delay(4); break;
+ case 0x65: H = L; delay(4); break;
+ case 0x66: H = readb(HL); delay(7); break;
+ case 0x67: H = A; delay(4); break;
+ case 0x68: L = B; delay(4); break;
+ case 0x69: L = C; delay(4); break;
+ case 0x6A: L = D; delay(4); break;
+ case 0x6B: L = E; delay(4); break;
+ case 0x6C: L = H; delay(4); break;
+ case 0x6D: L = L; delay(4); break;
+ case 0x6E: L = readb(HL); delay(7); break;
+ case 0x6F: L = A; delay(4); break;
+ case 0x70: writeb(HL, B); delay(7); break;
+ case 0x71: writeb(HL, C); delay(7); break;
+ case 0x72: writeb(HL, D); delay(7); break;
+ case 0x73: writeb(HL, E); delay(7); break;
+ case 0x74: writeb(HL, H); delay(7); break;
+ case 0x75: writeb(HL, L); delay(7); break;
+ case 0x76: delay(4); break;
+ case 0x77: writeb(HL, A); delay(7); break;
+ case 0x78: A = B; delay(4); break;
+ case 0x79: A = C; delay(4); break;
+ case 0x7A: A = D; delay(4); break;
+ case 0x7B: A = E; delay(4); break;
+ case 0x7C: A = H; delay(4); break;
+ case 0x7D: A = L; delay(4); break;
+ case 0x7E: A = readb(HL); delay(7); break;
+ case 0x7F: A = A; delay(4); break;
+
+ case 0x80: add8(A, B); delay(4); break;
+ case 0x81: add8(A, C); delay(4); break;
+ case 0x82: add8(A, D); delay(4); break;
+ case 0x83: add8(A, E); delay(4); break;
+ case 0x84: add8(A, H); delay(4); break;
+ case 0x85: add8(A, L); delay(4); break;
+ case 0x86: add8(A, readb(HL)); delay(7); break;
+ case 0x87: add8(A, A); delay(4); break;
+ case 0x88: adc8(A, B); delay(4); break;
+ case 0x89: adc8(A, C); delay(4); break;
+ case 0x8A: adc8(A, D); delay(4); break;
+ case 0x8B: adc8(A, E); delay(4); break;
+ case 0x8C: adc8(A, H); delay(4); break;
+ case 0x8D: adc8(A, L); delay(4); break;
+ case 0x8E: adc8(A, readb(HL)); delay(7); break;
+ case 0x8F: adc8(A, A); delay(4); break;
+ case 0x90: sub8(A, B); delay(4); break;
+ case 0x91: sub8(A, C); delay(4); break;
+ case 0x92: sub8(A, D); delay(4); break;
+ case 0x93: sub8(A, E); delay(4); break;
+ case 0x94: sub8(A, H); delay(4); break;
+ case 0x95: sub8(A, L); delay(4); break;
+ case 0x96: sub8(A, readb(HL)); delay(7); break;
+ case 0x97: sub8(A, A); delay(4); break;
+ case 0x98: sbc8(A, B); delay(4); break;
+ case 0x99: sbc8(A, C); delay(4); break;
+ case 0x9A: sbc8(A, D); delay(4); break;
+ case 0x9B: sbc8(A, E); delay(4); break;
+ case 0x9C: sbc8(A, H); delay(4); break;
+ case 0x9D: sbc8(A, L); delay(4); break;
+ case 0x9E: sbc8(A, readb(HL)); delay(7); break;
+ case 0x9F: sbc8(A, A); delay(4); break;
+ case 0xA0: and(A, B); delay(4); break;
+ case 0xA1: and(A, C); delay(4); break;
+ case 0xA2: and(A, D); delay(4); break;
+ case 0xA3: and(A, E); delay(4); break;
+ case 0xA4: and(A, H); delay(4); break;
+ case 0xA5: and(A, L); delay(4); break;
+ case 0xA6: and(A, readb(HL)); delay(7); break;
+ case 0xA7: and(A, A); delay(4); break;
+ case 0xA8: xor(A, B); delay(4); break;
+ case 0xA9: xor(A, C); delay(4); break;
+ case 0xAA: xor(A, D); delay(4); break;
+ case 0xAB: xor(A, E); delay(4); break;
+ case 0xAC: xor(A, H); delay(4); break;
+ case 0xAD: xor(A, L); delay(4); break;
+ case 0xAE: xor(A, readb(HL)); delay(7); break;
+ case 0xAF: xor(A, A); delay(4); break;
+ case 0xB0: or(A, B); delay(4); break;
+ case 0xB1: or(A, C); delay(4); break;
+ case 0xB2: or(A, D); delay(4); break;
+ case 0xB3: or(A, E); delay(4); break;
+ case 0xB4: or(A, H); delay(4); break;
+ case 0xB5: or(A, L); delay(4); break;
+ case 0xB6: or(A, readb(HL)); delay(7); break;
+ case 0xB7: or(A, A); delay(4); break;
+ case 0xB8: cp(A, B); delay(4); break;
+ case 0xB9: cp(A, C); delay(4); break;
+ case 0xBA: cp(A, D); delay(4); break;
+ case 0xBB: cp(A, E); delay(4); break;
+ case 0xBC: cp(A, H); delay(4); break;
+ case 0xBD: cp(A, L); delay(4); break;
+ case 0xBE: cp(A, readb(HL)); delay(7); break;
+ case 0xBF: cp(A, A); delay(4); break;
+
+ case 0xC0:			/* RET NZ */
+	 if (!(F & FLAG_Z)) {
+		 pop(WZ);
+		 PC = WZ;
+		 delay(11);
+	 }
+	 else
+		 delay(5);
+	 break;
+ case 0xC1:			/* POP BC */
+	 pop(BC);
+	 delay(10);
+	 break;
+ case 0xC2:			/* JP NZ, nn */
+	 WZ = readw(PC);
+	 if (!(F & FLAG_Z))
+		 PC = WZ;
+	 else
+		 PC += 2;
+	 delay(10);
+	 break;
+ case 0xC3:			/* JP nn */
+	 WZ = readw(PC);
+	 PC = WZ;
+	 delay(10);
+	 break;
+ case 0xC4:			/* CALL NZ, nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 if (!(F & FLAG_Z)) {
+		 push(PC);
+		 PC = WZ;
+		 delay(17);
+	 }
+	 else
+		 delay(10);
+	 break;
+ case 0xC5:			/* PUSH BC */
+	 push(BC);
+	 delay(11);
+	 break;
+ case 0xC6:			/* ADD A, n */
+	 add8(A, readb(PC++));
+	 delay(7);
+	 break;
+ case 0xC7:			/* RST 00h */
+	 /* FIXME: I have not tested whether RST affects WZ */
+	 push(PC);
+	 PC = 0x0000;
+	 delay(11);
+	 break;
+
+ case 0xC8:			/* RET Z */
+	 if (F & FLAG_Z) {
+		 pop(WZ);
+		 PC = WZ;
+		 delay(11);
+	 }
+	 else
+		 delay(5);
+	 break;
+ case 0xC9:			/* RET */
+	 pop(WZ);
+	 PC = WZ;
+	 delay(10);
+	 break;
+ case 0xCA:			/* JP Z, nn */
+	 WZ = readw(PC);
+	 if (F & FLAG_Z)
+		 PC = WZ;
+	 else
+		 PC += 2;
+	 delay(10);
+	 break;
+
+ case 0xCB:
+	 op = readb_m1(PC++);
+	 goto opcode_cb;
+
+ case 0xCC:			/* CALL Z, nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 if (F & FLAG_Z) {
+		 push(PC);
+		 PC = WZ;
+		 delay(17);
+	 }
+	 else
+		 delay(10);
+	 break;
+ case 0xCD:			/* CALL nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 push(PC);
+	 PC = WZ;
+	 delay(17);
+	 break;
+ case 0xCE:			/* ADC A, n */
+	 adc8(A, readb(PC++));
+	 delay(7);
+	 break;
+ case 0xCF:			/* RST 08h */
+	 push(PC);
+	 PC = 0x0008;
+	 delay(11);
+	 break;
+
+ case 0xD0:			/* RET NC */
+	 if (!(F & FLAG_C)) {
+		 pop(WZ);
+		 PC = WZ;
+		 delay(11);
+	 }
+	 else
+		 delay(5);
+	 break;
+ case 0xD1:			/* POP DE */
+	 pop(DE);
+	 delay(10);
+	 break;
+ case 0xD2:			/* JP NC, nn */
+	 WZ = readw(PC);
+	 if (!(F & FLAG_C))
+		 PC = WZ;
+	 else
+		 PC += 2;
+	 delay(10);
+	 break;
+ case 0xD3:			/* OUT (n), A */
+	 W = A;
+	 Z = readb(PC++);
+	 delay(11);
+	 output(WZ, A);
+	 break;
+ case 0xD4:			/* CALL NC, nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 if (!(F & FLAG_C)) {
+		 push(PC);
+		 PC = WZ;
+		 delay(17);
+	 }
+	 else
+		 delay(10);
+	 break;
+ case 0xD5:			/* PUSH DE */
+	 push(DE);
+	 delay(11);
+	 break;
+ case 0xD6:			/* SUB n */
+	 sub8(A, readb(PC++));
+	 delay(7);
+	 break;
+ case 0xD7:			/* RST 10h */
+	 push(PC);
+	 PC = 0x0010;
+	 delay(11);
+	 break;
+
+ case 0xD8:			/* RET C */
+	 if (F & FLAG_C) {
+		 pop(WZ);
+		 PC = WZ;
+		 delay(11);
+	 }
+	 else
+		 delay(5);
+	 break;
+ case 0xD9:			/* EXX */
+	 ex(BC, BC2);
+	 ex(DE, DE2);
+	 ex(HL, HL2);
+	 ex(WZ, WZ2);
+	 delay(4);
+	 break;
+ case 0xDA:			/* JP C, nn */
+	 WZ = readw(PC);
+	 if (F & FLAG_C)
+		 PC = WZ;
+	 else
+		 PC += 2;
+	 delay(10);
+	 break;
+ case 0xDB:			/* IN A, (n) */
+	 W = A;
+	 Z = readb(PC++);
+	 delay(11);
+	 A = input(WZ);
+	 break;
+ case 0xDC:			/* CALL C, nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 if (F & FLAG_C) {
+		 push(PC);
+		 PC = WZ;
+		 delay(17);
+	 }
+	 else
+		 delay(10);
+	 break;
+
+ case 0xDD:
+	 op = readb_m1(PC++);
+	 delay(4);
+	 goto opcode_dd;
+
+ case 0xDE:			/* SBC A, n */
+	 sbc8(A, readb(PC++));
+	 delay(7);
+	 break;
+ case 0xDF:			/* RST 18h */
+	 push(PC);
+	 PC = 0x0018;
+	 delay(11);
+	 break;
+
+ case 0xE0:			/* RET PO */
+	 if (!(F & FLAG_P)) {
+		 pop(WZ);
+		 PC = WZ;
+		 delay(11);
+	 }
+	 else
+		 delay(5);
+	 break;
+ case 0xE1:			/* POP HL */
+	 pop(HL);
+	 delay(10);
+	 break;
+ case 0xE2:			/* JP PO, nn */
+	 WZ = readw(PC);
+	 if (!(F & FLAG_P))
+		 PC = WZ;
+	 else
+		 PC += 2;
+	 delay(10);
+	 break;
+ case 0xE3:			/* EX (SP), HL */
+	 WZ = readw(SP);
+	 writew(SP, HL);
+	 HL = WZ;
+	 delay(19);
+	 break;
+ case 0xE4:			/* CALL PO, nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 if (!(F & FLAG_P)) {
+		 push(PC);
+		 PC = WZ;
+		 delay(17);
+	 }
+	 else
+		 delay(10);
+	 break;
+ case 0xE5:			/* PUSH HL */
+	 push(HL);
+	 delay(11);
+	 break;
+ case 0xE6:			/* AND n */
+	 and(A, readb(PC++));
+	 delay(7);
+	 break;
+ case 0xE7:			/* RST 20h */
+	 push(PC);
+	 PC = 0x0020;
+	 delay(11);
+	 break;
+
+ case 0xE8:			/* RET PE */
+	 if (F & FLAG_P) {
+		 pop(WZ);
+		 PC = WZ;
+		 delay(11);
+	 }
+	 else
+		 delay(5);
+	 break;
+ case 0xE9:			/* JP HL */
+	 PC = HL;
+	 delay(4);
+	 break;
+ case 0xEA:			/* JP PE, nn */
+	 WZ = readw(PC);
+	 if (F & FLAG_P)
+		 PC = WZ;
+	 else
+		 PC += 2;
+	 delay(10);
+	 break;
+ case 0xEB:			/* EX DE,HL */
+	 ex(DE, HL);
+	 delay(4);
+	 break;
+ case 0xEC:			/* CALL PE, nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 if (F & FLAG_P) {
+		 push(PC);
+		 PC = WZ;
+		 delay(17);
+	 }
+	 else
+		 delay(10);
+	 break;
+
+ case 0xED:
+	 op = readb_m1(PC++);
+	 goto opcode_ed;
+		
+ case 0xEE:			/* XOR n */
+	 xor(A, readb(PC++));
+	 delay(7);
+	 break;
+ case 0xEF:			/* RST 28h */
+	 push(PC);
+	 PC = 0x0028;
+	 delay(11);
+	 break;
+
+ case 0xF0:			/* RET P */
+	 if (!(F & FLAG_S)) {
+		 pop(WZ);
+		 PC = WZ;
+		 delay(11);
+	 }
+	 else
+		 delay(5);
+	 break;
+ case 0xF1:			/* POP AF */
+	 pop(AF);
+	 delay(10);
+	 break;
+ case 0xF2:			/* JP P, nn */
+	 WZ = readw(PC);
+	 if (!(F & FLAG_S))
+		 PC = WZ;
+	 else
+		 PC += 2;
+	 delay(10);
+	 break;
+ case 0xF3:			/* DI */
+	 IFF1 = IFF2 = 0;
+	 delay(4);
+	 break;
+ case 0xF4:			/* CALL P, nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 if (!(F & FLAG_S)) {
+		 push(PC);
+		 PC = WZ;
+		 delay(17);
+	 }
+	 else
+		 delay(10);
+	 break;
+ case 0xF5:			/* PUSH AF */
+	 push(AF);
+	 delay(11);
+	 break;
+ case 0xF6:			/* OR n */
+	 or(A, readb(PC++));
+	 delay(7);
+	 break;
+ case 0xF7:			/* RST 30h */
+	 push(PC);
+	 PC = 0x0030;
+	 delay(11);
+	 break;
+		
+ case 0xF8:			/* RET M */
+	 if (F & FLAG_S) {
+		 pop(WZ);
+		 PC = WZ;
+		 delay(11);
+	 }
+	 else
+		 delay(5);
+	 break;
+ case 0xF9:			/* LD SP, HL */
+	 SP = HL;
+	 delay(4);
+	 break;
+ case 0xFA:			/* JP M, nn */
+	 WZ = readw(PC);
+	 if (F & FLAG_S)
+		 PC = WZ;
+	 else
+		 PC += 2;
+	 delay(10);
+	 break;
+ case 0xFB:			/* EI */
+	 IFF1 = IFF2 = 1;
+	 delay(4);
+	 break;
+ case 0xFC:			/* CALL M, nn */
+	 WZ = readw(PC);
+	 PC += 2;
+	 if (F & FLAG_S) {
+		 push(PC);
+		 PC = WZ;
+		 delay(17);
+	 }
+	 else
+		 delay(10);
+	 break;
+		
+ case 0xFD:
+	 op = readb_m1(PC++);
+	 delay(4);
+	 goto opcode_fd;
+		
+ case 0xFE:			/* CP n */
+	 cp(A, readb(PC++));
+	 delay(7);
+	 break;
+ case 0xFF:			/* RST 38h */
+	 push(PC);
+	 PC = 0x0038;
+	 delay(11);
+	 break;
+ }
diff --git a/tool/tilem-src/gui/Makefile.in b/tool/tilem-src/gui/Makefile.in
new file mode 100644
index 0000000..4057e5b
--- /dev/null
+++ b/tool/tilem-src/gui/Makefile.in
@@ -0,0 +1,263 @@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+bindir = @bindir@
+datadir = @datadir@
+pkgdatadir = @datadir@/tilem2
+mandir = @mandir@
+
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+@SET_MAKE@
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+DEFS = @DEFS@
+GUI_LDFLAGS = @GUI_LDFLAGS@
+INSTALL = @INSTALL@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+SHELL = @SHELL@
+WINDRES = @WINDRES@
+
+GTK_CFLAGS = @GTK_CFLAGS@
+GTK_LIBS = @GTK_LIBS@
+
+TICALCS_CFLAGS = @TICALCS_CFLAGS@
+TICALCS_LIBS = @TICALCS_LIBS@
+
+TILEMCORE_CFLAGS = -I$(top_srcdir)/emu
+TILEMCORE_LIBS = -L$(top_builddir)/emu -ltilemcore
+
+TILEMDB_CFLAGS = -I$(top_srcdir)/db
+TILEMDB_LIBS = -L$(top_builddir)/db -ltilemdb
+
+DEF_SHARE_DIR = -DSHARE_DIR=\"$(pkgdatadir)\" \
+	-DUNINSTALLED_SHARE_DIR=\"$(top_srcdir)/data\"
+
+gui_extra_objects = @gui_extra_objects@
+
+objects = tilem2.o \
+	address.o \
+	animatedgif.o \
+	animation.o \
+	breakpoints.o \
+	config.o \
+	charmap.o \
+	debugger.o \
+	disasmview.o \
+	emulator.o \
+	emucore.o \
+	emuwin.o \
+	event.o \
+	filedlg.o \
+	files.o \
+	fixedtreeview.o \
+	gifencod.o \
+	icons.o \
+	keybindings.o \
+	keypaddlg.o \
+	link.o \
+	macro.o \
+	memmodel.o \
+	memview.o \
+	memory.o \
+	pbar.o \
+	preferences.o \
+	sendfile.o \
+	screenshot.o \
+	skinops.o \
+	ti81prg.o \
+	menu.o \
+	rcvmenu.o \
+	tool.o \
+	$(gui_extra_objects)
+
+libs = $(TILEMDB_LIBS) $(TILEMCORE_LIBS) $(GTK_LIBS) $(TICALCS_LIBS) $(LIBS)
+
+compile = $(CC) -I$(top_builddir) -I$(srcdir) $(CFLAGS) $(CPPFLAGS) $(DEFS) \
+	$(TILEMCORE_CFLAGS) $(TILEMDB_CFLAGS) \
+	$(GTK_CFLAGS) $(TICALCS_CFLAGS)
+
+link = $(CC) $(CFLAGS) $(LDFLAGS) $(GUI_LDFLAGS) 
+
+common_headers = ../config.h ../emu/tilem.h ../db/tilemdb.h \
+	gui.h emulator.h debugger.h emuwin.h skinops.h animation.h \
+	gtk-compat.h msgbox.h fixedtreeview.h
+
+all: tilem2@EXEEXT@
+
+#Main emulator GUI
+tilem2@EXEEXT@: $(objects) ../emu/libtilemcore.a
+	$(link) -o tilem2@EXEEXT@ $(objects) $(libs)
+
+tilem2.o: tilem2.c icons.h files.h $(common_headers)
+	$(compile) -c $(srcdir)/tilem2.c
+
+# Debugger
+debugger.o: debugger.c disasmview.h $(common_headers)
+	$(compile) -c $(srcdir)/debugger.c
+
+# Disassembly view
+disasmview.o: disasmview.c disasmview.h $(common_headers)
+	$(compile) -c $(srcdir)/disasmview.c
+
+# Memory view
+memview.o: memview.c memmodel.h $(common_headers)
+	$(compile) -c $(srcdir)/memview.c
+
+# Tree model interface for calc memory
+memmodel.o: memmodel.c memmodel.h $(common_headers)
+	$(compile) -c $(srcdir)/memmodel.c
+
+# Breakpoint dialog
+breakpoints.o: breakpoints.c $(common_headers)
+	$(compile) -c $(srcdir)/breakpoints.c
+
+# Utility functions for debugging
+address.o: address.c $(common_headers)
+	$(compile) -c $(srcdir)/address.c
+
+# Keypad dialog
+keypaddlg.o: keypaddlg.c $(common_headers)
+	$(compile) -c $(srcdir)/keypaddlg.c
+
+# Memory management and messages
+memory.o: memory.c ../emu/tilem.h
+	$(compile) -c $(srcdir)/memory.c
+
+# Emulator management
+emulator.o: emulator.c emucore.h $(common_headers)
+	$(compile) -c $(srcdir)/emulator.c
+
+# Emulator main loop
+emucore.o: emucore.c emucore.h $(common_headers)
+	$(compile) -c $(srcdir)/emucore.c
+
+# Emulator GUI (main window)
+emuwin.o: emuwin.c $(common_headers)
+	$(compile) -c $(srcdir)/emuwin.c
+
+# Handle events
+event.o: event.c $(common_headers)
+	$(compile) -c $(srcdir)/event.c
+
+# Preferences dialog
+preferences.o: preferences.c $(common_headers)
+	$(compile) -c $(srcdir)/preferences.c 
+
+# Open skin (skn format file) originally created by Julien Blache and Romain Lievins
+skinops.o: skinops.c skinops.h 
+	$(compile) -c $(srcdir)/skinops.c
+
+# Popups and other stuff
+tool.o: tool.c $(common_headers)
+	$(compile) -c $(srcdir)/tool.c
+
+# Manage config.ini 
+config.o: config.c files.h $(common_headers)
+	$(compile) -c $(srcdir)/config.c
+
+# Handle internal link
+link.o: link.c emucore.h ti81prg.h $(common_headers)
+	$(compile) -c $(srcdir)/link.c
+
+# Handle macro
+macro.o: macro.c $(common_headers)
+	$(compile) -c $(srcdir)/macro.c
+
+# Create and modify animated gif
+gifencod.o: gifencod.c gifencod.h
+	$(compile) -c $(srcdir)/gifencod.c
+
+# Handle screenshot anim (animated gif)
+animatedgif.o: animatedgif.c $(common_headers)
+	$(compile) -c $(srcdir)/animatedgif.c
+
+# Screenshot widget
+screenshot.o: screenshot.c $(common_headers)
+	$(compile) -c $(srcdir)/screenshot.c
+
+# Progress bar widget
+pbar.o: pbar.c $(common_headers)
+	$(compile) -c $(srcdir)/pbar.c
+
+# Screenshot/animation recording
+animation.o: animation.c $(common_headers)
+	$(compile) -c $(srcdir)/animation.c
+
+
+# Shared/configuration files
+files.o: files.c files.h
+	$(compile) $(DEF_SHARE_DIR) -c $(srcdir)/files.c
+
+# Custom icons
+icons.o: icons.c icons.h
+	$(compile) $(DEF_SHARE_DIR) -c $(srcdir)/icons.c
+
+# Keybindings
+keybindings.o: keybindings.c files.h $(common_headers)
+	$(compile) -c $(srcdir)/keybindings.c
+
+# Menu
+menu.o: menu.c $(common_headers)
+	$(compile) -c $(srcdir)/menu.c
+
+# Link receive dialog
+rcvmenu.o: rcvmenu.c $(common_headers)
+	$(compile) -c $(srcdir)/rcvmenu.c
+
+# Link send dialog
+sendfile.o: sendfile.c emucore.h $(common_headers)
+	$(compile) -c $(srcdir)/sendfile.c
+
+# File open/save dialogs
+filedlg.o: filedlg.c filedlg.h
+	$(compile) -c $(srcdir)/filedlg.c
+
+# Fixed-width tree view
+fixedtreeview.o: fixedtreeview.c fixedtreeview.h
+	$(compile) -c $(srcdir)/fixedtreeview.c
+
+# TI-81 program file functions
+ti81prg.o: ti81prg.c ti81prg.h ../emu/tilem.h
+	$(compile) -c $(srcdir)/ti81prg.c
+
+# Character conversion
+charmap.o: charmap.c charmap.h ../emu/tilem.h
+	$(compile) -c $(srcdir)/charmap.c
+
+# Windows resource file
+tilem2rc.o: tilem2.rc
+	major=`echo "@PACKAGE_VERSION@" | sed 's/\..*//'` ; \
+	minor=`echo "@PACKAGE_VERSION@" | sed 's/.*\.//;s/[^0-9].*//'` ; \
+	svnver=`svnversion "$(top_srcdir)" 2>/dev/null | sed 's/[^0-9].*//'` ; \
+	[ -n "$$svnver" ] || svnver=0 ; \
+	$(WINDRES) -DBUILD_VERSION=$$major,$$minor,0,$$svnver tilem2.rc tilem2rc.o
+
+tilem2.rc: tilem2.rc.in $(top_builddir)/config.status
+	cd $(top_builddir) && $(SHELL) ./config.status gui/tilem2.rc
+
+install: tilem2@EXEEXT@
+	$(INSTALL) -d -m 755 $(DESTDIR)$(bindir)
+	$(INSTALL_PROGRAM) -m 755 tilem2@EXEEXT@ $(DESTDIR)$(bindir)
+
+uninstall:
+	rm -f $(DESTDIR)$(bindir)/tilem2@EXEEXT@
+
+clean:
+	rm -f *.o
+	rm -f tilem2@EXEEXT@
+
+Makefile: Makefile.in $(top_builddir)/config.status
+	cd $(top_builddir) && $(SHELL) ./config.status
+
+$(top_builddir)/config.status: $(top_srcdir)/configure
+	cd $(top_builddir) && $(SHELL) ./config.status --recheck
+
+.PRECIOUS: Makefile $(top_builddir)/config.status
+.PHONY: all clean install uninstall
diff --git a/tool/tilem-src/gui/address.c b/tool/tilem-src/gui/address.c
new file mode 100644
index 0000000..997d6bc
--- /dev/null
+++ b/tool/tilem-src/gui/address.c
@@ -0,0 +1,242 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include <tilemdb.h>
+
+#include "gui.h"
+
+char * tilem_format_addr(TilemDebugger *dbg, dword addr, gboolean physical)
+{
+	dword page, addr_l;
+
+	g_return_val_if_fail(dbg != NULL, NULL);
+	g_return_val_if_fail(dbg->emu != NULL, NULL);
+	g_return_val_if_fail(dbg->emu->calc != NULL, NULL);
+
+	if (!physical)
+		return g_strdup_printf("%04X", addr);
+
+	if (addr >= dbg->emu->calc->hw.romsize)
+		page = (((addr - dbg->emu->calc->hw.romsize) >> 14)
+		        + dbg->emu->calc->hw.rampagemask);
+	else
+		page = addr >> 14;
+
+	addr_l = (*dbg->emu->calc->hw.mem_ptol)(dbg->emu->calc, addr);
+	if (addr_l == 0xffffffff)
+		addr_l = (addr & 0x3fff) | 0x4000;
+
+	return g_strdup_printf("%02X:%04X", page, addr_l);
+}
+
+static gboolean parse_hex(const char *string, dword *value)
+{
+	const char *n;
+	char *e;
+	dword a;
+
+	if (string[0] == '$')
+		n = string + 1;
+	else if (string[0] == '0' && (string[1] == 'x' || string[1] == 'X'))
+		n = string + 2;
+	else
+		n = string;
+
+	a = strtol(n, &e, 16);
+	if (value)
+		*value = a;
+
+	if (e == n)
+		return FALSE;
+
+	if (*e == 'h' || *e == 'H')
+		e++;
+
+	return (*e == 0);
+}
+
+gboolean tilem_parse_paged_addr(TilemDebugger *dbg, const char *pagestr,
+                                const char *offsstr, dword *value)
+{
+	dword page, offs;
+
+	g_return_val_if_fail(dbg != NULL, FALSE);
+	g_return_val_if_fail(dbg->emu != NULL, FALSE);
+	g_return_val_if_fail(dbg->emu->calc != NULL, FALSE);
+
+	if (!parse_hex(pagestr, &page))
+		return FALSE;
+	if (!tilem_parse_addr(dbg, offsstr, &offs, NULL))
+		return FALSE;
+
+	offs &= 0x3fff;
+	if (page & dbg->emu->calc->hw.rampagemask) {
+		page &= ~dbg->emu->calc->hw.rampagemask;
+		offs += (offs << 14);
+		if (offs > dbg->emu->calc->hw.ramsize)
+			return FALSE;
+		offs += dbg->emu->calc->hw.romsize;
+	}
+	else {
+		offs += (page << 14);
+		if (offs > dbg->emu->calc->hw.romsize)
+			return FALSE;
+	}
+
+	if (value) *value = offs;
+	return TRUE;
+}
+
+gboolean tilem_parse_addr(TilemDebugger *dbg, const char *string,
+                          dword *value, gboolean *physical)
+{
+	const char *offstr;
+	char *pagestr;
+
+	g_return_val_if_fail(dbg != NULL, FALSE);
+	g_return_val_if_fail(dbg->emu != NULL, FALSE);
+	g_return_val_if_fail(dbg->emu->calc != NULL, FALSE);
+
+	if (parse_hex(string, value)) {
+		if (physical) *physical = FALSE;
+		return TRUE;
+	}
+
+	if (physical && (offstr = strchr(string, ':'))) {
+		pagestr = g_strndup(string, offstr - string);
+		offstr++;
+		if (tilem_parse_paged_addr(dbg, pagestr, offstr, value)) {
+			*physical = TRUE;
+			return TRUE;
+		}
+	}
+
+	if (dbg->dasm && tilem_disasm_get_label(dbg->dasm, string, value)) {
+		if (physical) *physical = FALSE;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+struct addrdlg {
+	GtkWidget *dlg;
+	TilemDebugger *dbg;
+	gboolean physical;
+};
+
+static void edited(GtkEntry *entry, gpointer data)
+{
+	struct addrdlg *adlg = data;
+	const char *text;
+	gboolean valid, phys;
+	
+	text = gtk_entry_get_text(entry);
+	valid = tilem_parse_addr(adlg->dbg, text, NULL,
+	                         adlg->physical ? &phys : NULL);
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(adlg->dlg),
+	                                  GTK_RESPONSE_OK,
+	                                  valid);
+}
+
+gboolean tilem_prompt_address(TilemDebugger *dbg, GtkWindow *parent,
+                              const char *title, const char *prompt,
+                              dword *value, gboolean physical,
+                              gboolean usedefault)
+{
+	GtkWidget *dlg, *hbox, *vbox, *lbl, *ent;
+	struct addrdlg adlg;
+	const char *text;
+	gboolean phys;
+	char *s;
+
+	g_return_val_if_fail(dbg != NULL, FALSE);
+	g_return_val_if_fail(dbg->emu != NULL, FALSE);
+	g_return_val_if_fail(dbg->emu->calc != NULL, FALSE);
+
+	dlg = gtk_dialog_new_with_buttons(title, parent, GTK_DIALOG_MODAL,
+	                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                                  GTK_STOCK_OK, GTK_RESPONSE_OK,
+	                                  NULL);
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
+	                                        GTK_RESPONSE_OK,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+	gtk_dialog_set_default_response(GTK_DIALOG(dlg),
+	                                GTK_RESPONSE_OK);
+
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_container_set_border_width(GTK_CONTAINER(hbox), 6);
+
+	lbl = gtk_label_new(prompt);
+	gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 0);
+
+	ent = gtk_entry_new();
+	gtk_entry_set_activates_default(GTK_ENTRY(ent), TRUE);
+	gtk_box_pack_start(GTK_BOX(hbox), ent, TRUE, TRUE, 0);
+
+	if (usedefault) {
+		s = tilem_format_addr(dbg, *value, physical);
+		gtk_entry_set_text(GTK_ENTRY(ent), s);
+		g_free(s);
+	}
+
+	adlg.dlg = dlg;
+	adlg.dbg = dbg;
+	adlg.physical = physical;
+
+	g_signal_connect(ent, "changed",
+	                 G_CALLBACK(edited), &adlg);
+	edited(GTK_ENTRY(ent), &adlg);
+
+	gtk_widget_show_all(hbox);
+	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+	if (gtk_dialog_run(GTK_DIALOG(dlg)) != GTK_RESPONSE_OK) {
+		gtk_widget_destroy(dlg);
+		return FALSE;
+	}
+
+	text = gtk_entry_get_text(GTK_ENTRY(ent));
+	if (!tilem_parse_addr(dbg, text, value, physical ? &phys : NULL)) {
+		gtk_widget_destroy(dlg);
+		return FALSE;
+	}
+
+	if (physical && !phys) {
+		tilem_calc_emulator_lock(dbg->emu);
+		*value &= 0xffff;
+		*value = (*dbg->emu->calc->hw.mem_ltop)(dbg->emu->calc, *value);
+		tilem_calc_emulator_unlock(dbg->emu);
+	}
+
+	gtk_widget_destroy(dlg);
+	return TRUE;
+}
diff --git a/tool/tilem-src/gui/animatedgif.c b/tool/tilem-src/gui/animatedgif.c
new file mode 100644
index 0000000..a45ba6b
--- /dev/null
+++ b/tool/tilem-src/gui/animatedgif.c
@@ -0,0 +1,251 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include "gui.h"
+
+
+static void write_global_header(FILE* fp, int width, int height, byte* palette, int palette_size);
+static void write_global_footer(FILE* fp);
+static void write_extension_block(FILE* fout, word delay);
+static void write_image_block_start(FILE *fp, int width, int height);
+static void write_image_block_end(FILE *fp);
+static void write_comment(FILE* fp);
+static void write_application_extension(FILE * fp) ;
+
+static void write_global_header(FILE* fp, int width, int height, byte* palette, int palette_size) {
+	
+	/* Magic number for Gif file format */
+    	char global_header_magic_number[] = {'G', 'I', 'F', '8', '9', 'a'};
+    	/* Size of canvas width on 2 bytes, heigth on 2 bytes */
+	char global_header_canvas[] = {96, 0, 64, 0 };
+
+	global_header_canvas[0] = width; 
+	global_header_canvas[1] = (width >> 8) ; 
+	global_header_canvas[2] = height; 
+	global_header_canvas[3] = (height >> 8); 
+
+	/* Flag */
+	/* The 11th byte is a set of flags  : 
+	bit 0:    Global Color Table Flag (GCTF)
+        bit 1..3: Color Resolution
+        bit 4:    Sort Flag to Global Color Table
+        bit 5..7: Size of Global Color Table: 2^(1+n)
+	It means "use the GCT wich is given after (from the size bit 5..7) and a resolution bit 1..3 
+	The Background color is an index in the Global Color Table
+	*/
+	/* FIXME : if we change the palette size, we need to change this flag too and I don't do this currently */
+    	char global_header_flag[] = { 0xf7 };
+	/* The index in global color table */
+	char global_header_background_index[] = {0x00};
+	/* Aspect pixel ratio (unknown) */
+	char global_header_aspect_pixel_ratio[] = {0x00};
+	
+	
+	fwrite(global_header_magic_number, 6, 1, fp);
+	fwrite(global_header_canvas, 4, 1, fp);
+	fwrite(global_header_flag, 1, 1, fp);
+	fwrite(global_header_background_index, 1, 1, fp);
+	fwrite(global_header_aspect_pixel_ratio, 1, 1, fp);
+	
+	//byte* palette = tilem_color_palette_new_packed(255, 255, 255, 0, 0, 0, 2.2);
+	
+	fwrite(palette, palette_size * 3, 1, fp);
+}
+
+static void write_global_footer(FILE* fp) {
+
+	/* This value means end of gif file */	
+	char footer_trailer[1] = { 0x3b};
+	
+	fwrite(footer_trailer, 1, 1,fp);
+}
+
+
+static void write_extension_block(FILE* fp, word delay) {
+
+	/* Extension block introduced by 0x21 ('!'), size before extension_block_terminator, flag byte, delay (10/100) 2 bytes   */
+	char extension_block_header[2] = {0x21, 0xf9};
+	/* Size before extension_block_terminator */
+	char extension_block_size[1] = { 0x04} ;
+	/* Flag (unknown) */
+	char extension_block_flag[1] = { 0x00} ;
+	/* Delay (x/100 sec) on 2 bytes*/
+	char extension_block_delay[2] = {10, 0} ;
+	extension_block_delay[0] = delay;
+	/* The index designed by this variable become transparent even if palette gives a black(or something else) color. */ 
+	char extension_block_transparent_index[1] = {0xff};
+	/* End of extension block */
+	char extension_block_terminator[1] = {0x00};
+
+	fwrite(extension_block_header, 2, 1, fp);
+    	fwrite(extension_block_size, 1, 1, fp);
+    	fwrite(extension_block_flag, 1, 1, fp);
+    	fwrite(extension_block_delay, 2, 1, fp);
+    	fwrite(extension_block_transparent_index, 1, 1, fp);
+    	fwrite(extension_block_terminator, 1, 1, fp);
+
+}
+
+static void write_image_block_start(FILE *fp, int width, int height) {
+
+	/* Header */
+	char image_block_header[] = { 0x2c};
+	/* Left corner x (2 bytes), left corner y (2 bytes), width (2 bytes), height (2 bytes) */
+	char image_block_canvas[] = { 0, 0, 0, 0, 96, 0, 64, 0};
+	
+	image_block_canvas[4] = width; 
+	image_block_canvas[5] = (width >> 8) ; 
+	image_block_canvas[6] = height; 
+	image_block_canvas[7] = (height >> 8); 
+	/* Flag */
+	char image_block_flag[] = { 0x00 };
+
+        fwrite(image_block_header, 1, 1, fp);
+    	fwrite(image_block_canvas, 8, 1, fp);
+    	fwrite(image_block_flag, 1, 1, fp);
+
+}
+
+static void write_image_block_end(FILE *fp) {
+	
+ 	/* Give an end to the image block */
+	char image_block_end[1] = {0x00};
+
+	fwrite(image_block_end, 1, 1,fp);
+}
+
+static void write_comment(FILE* fp) {
+
+	char comment[] = {0x21, 0xfe, 8, 'T', 'i', 'l', 'E', 'm', '2', 0, 0, 0};
+	fwrite(comment, 12, 1, fp);
+}
+
+static void write_application_extension(FILE * fp) {
+
+	/* Magic number to start the block */
+	char application_extension_magic_number[] = { 0x21, 0xff, 0x0b };
+	/* Application name */
+	char application_extension_application_name[] = { 'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0' };
+	/* magic number */
+	char application_extension_data_follow[] = { 0x03, 0x01 };
+	/* 0 to 65535 loop */
+	char application_extension_number_of_loop[] = { 0xff, 0xff};
+	/* the end of the block */	
+	char application_extension_terminator[] = { 0x00 };
+	
+	//char gif_infos[31] = {
+        //0x21, 0xff, 0x0b, 'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0', 3, 1, 0xff, 0xff, 0x00	};
+
+	//fwrite(gif_infos, 19, 1, fp);
+	fwrite(application_extension_magic_number, 3, 1, fp);
+	fwrite(application_extension_application_name, 11, 1, fp);
+	fwrite(application_extension_data_follow, 2, 1, fp);
+	fwrite(application_extension_number_of_loop, 2, 1, fp);
+	fwrite(application_extension_terminator, 1, 1, fp);
+}
+
+/* Apparently, most current web browsers are seriously and
+   deliberately broken in their handling of animated GIFs.  Internet
+   Explorer does not allow any frame to be shorter than 60 ms, and
+   Gecko does not allow any frame shorter than 20 ms.  Furthermore,
+   rather than simply imposing a lower limit, or skipping frames,
+   these browsers take any frame they deem "too short" and extend it
+   to a full 100 ms out of sheer spite.
+
+   If we want animations to look correct in all web browsers (which
+   is, after all, the main reason for using GIF animations in the
+   first place), we have to limit ourselves to 60-ms frames or
+   longer. */
+#define MIN_FRAME_DELAY 6
+
+void tilem_animation_write_gif(TilemAnimation *anim, byte* palette, int palette_size, FILE *fp)
+{
+	GdkPixbufAnimation *ganim;
+	int width, height, delay, n;
+	gdouble time_stretch, t;
+	byte *image;
+	TilemAnimFrame *frm, *next;
+	gboolean is_static;
+
+	g_return_if_fail(TILEM_IS_ANIMATION(anim));
+	g_return_if_fail(fp != NULL);
+
+	ganim = GDK_PIXBUF_ANIMATION(anim);
+	width = gdk_pixbuf_animation_get_width(ganim);
+	height = gdk_pixbuf_animation_get_height(ganim);
+	is_static = gdk_pixbuf_animation_is_static_image(ganim);
+	time_stretch = 1.0 / tilem_animation_get_speed(anim);
+
+	frm = tilem_animation_next_frame(anim, NULL);
+	g_return_if_fail(frm != NULL);
+
+	write_global_header(fp, width, height, palette, palette_size);
+
+	if (!is_static)
+		write_application_extension(fp);
+
+	write_comment(fp);
+
+	t = MIN_FRAME_DELAY * 5.0;
+
+	/* FIXME: combine multiple frames by averaging rather than
+	   simply taking the last one */
+
+	while (frm) {
+		next = tilem_animation_next_frame(anim, frm);
+
+		if (!is_static) {
+			delay = tilem_anim_frame_get_duration(frm);
+			t += delay * time_stretch;
+			n = t / 10.0;
+
+			if (n < MIN_FRAME_DELAY && next != NULL) {
+				frm = next;
+				continue;
+			}
+
+			t -= n * 10.0;
+			if (n > 0xffff)
+				n = 0xffff;
+			else if (n < MIN_FRAME_DELAY)
+				n = MIN_FRAME_DELAY;
+			write_extension_block(fp, n);
+		}
+
+		tilem_animation_get_indexed_image(anim, frm, &image,
+		                                  &width, &height);
+		write_image_block_start(fp, width, height);
+		GifEncode(fp, image, 8, width * height);
+		write_image_block_end(fp);
+		g_free(image);
+
+		frm = next;
+	}
+
+	write_global_footer(fp);
+}
diff --git a/tool/tilem-src/gui/animation.c b/tool/tilem-src/gui/animation.c
new file mode 100644
index 0000000..f40e727
--- /dev/null
+++ b/tool/tilem-src/gui/animation.c
@@ -0,0 +1,594 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#define GDK_PIXBUF_ENABLE_BACKEND
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+
+#define GAMMA 2.2
+
+struct _TilemAnimFrame {
+	struct _TilemAnimFrame *next;
+	unsigned duration : 24;
+	unsigned contrast : 8;
+	byte data[1];
+};
+
+struct _TilemAnimation {
+	GdkPixbufAnimation parent;
+
+	int num_frames;
+	TilemAnimFrame *start;
+	TilemAnimFrame *end;
+	dword last_stamp;
+
+	TilemLCDBuffer *temp_buffer;
+
+	GdkPixbuf *static_pixbuf;
+
+	int base_contrast;
+	int display_width;
+	int display_height;
+	int frame_rowstride;
+	int frame_size;
+	int image_width;
+	int image_height;
+	dword *palette;
+	gdouble speed;
+	gdouble time_stretch;
+
+	gboolean out_of_memory;
+};
+
+struct _TilemAnimationClass {
+	GdkPixbufAnimationClass parent_class;
+};
+
+#define TILEM_TYPE_ANIM_ITER           (tilem_anim_iter_get_type())
+#define TILEM_ANIM_ITER(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), TILEM_TYPE_ANIM_ITER, TilemAnimIter))
+#define TILEM_ANIM_ITER_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST((cls), TILEM_TYPE_ANIM_ITER, TilemAnimIterClass))
+#define TILEM_IS_ANIM_ITER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), TILEM_TYPE_ANIM_ITER))
+#define TILEM_IS_ANIM_ITER_CLASS(cls)  (G_TYPE_CHECK_CLASS_TYPE((cls), TILEM_TYPE_ANIM_ITER))
+#define TILEM_ANIM_ITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TILEM_TYPE_ANIM_ITER, TilemAnimIterClass))
+
+typedef struct _TilemAnimIter {
+	GdkPixbufAnimationIter parent;
+	GTimeVal current_time;
+	int time_elapsed;
+	TilemAnimation *anim;
+	TilemAnimFrame *frame;
+	GdkPixbuf *pixbuf;
+} TilemAnimIter;
+
+typedef struct _TilemAnimIterClass {
+	GdkPixbufAnimationIterClass parent_class;
+} TilemAnimIterClass;
+
+G_DEFINE_TYPE(TilemAnimation, tilem_animation,
+              GDK_TYPE_PIXBUF_ANIMATION);
+
+G_DEFINE_TYPE(TilemAnimIter, tilem_anim_iter,
+              GDK_TYPE_PIXBUF_ANIMATION_ITER);
+
+static TilemAnimFrame * alloc_frame(int bufsize)
+{
+	TilemAnimFrame *frm;
+
+	frm = g_try_malloc(sizeof(TilemAnimFrame) + bufsize - 1);
+	if (!frm)
+		return NULL;
+	frm->next = NULL;
+	return frm;
+}
+
+static void free_frame(TilemAnimFrame *frm)
+{
+	g_free(frm);
+}
+
+static int adjust_contrast(TilemAnimation *anim, int contrast)
+{
+	TilemAnimFrame *frm;
+
+	if (!contrast)
+		return 0;
+
+	if (!anim->base_contrast) {
+		for (frm = anim->start; frm; frm = frm->next) {
+			if (frm->contrast != 0) {
+				anim->base_contrast = frm->contrast;
+				break;
+			}
+		}
+	}
+
+	contrast = (contrast - anim->base_contrast + 32);
+	return CLAMP(contrast, 0, 63);
+}
+
+static void set_lcdbuf_from_frame(TilemAnimation *anim, 
+                                  TilemLCDBuffer *buf,
+                                  const TilemAnimFrame *frm)
+{
+	buf->width = anim->display_width;
+	buf->height = anim->display_height;
+	buf->rowstride = anim->frame_rowstride;
+	buf->contrast = adjust_contrast(anim, frm->contrast);
+	buf->data = (byte *) frm->data;
+}
+
+static GdkPixbuf * frame_to_pixbuf(TilemAnimation *anim,
+                                   const TilemAnimFrame *frm)
+{
+	GdkPixbuf *pb;
+
+	pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
+	                    anim->image_width, anim->image_height);
+
+	set_lcdbuf_from_frame(anim, anim->temp_buffer, frm);
+	tilem_draw_lcd_image_rgb(anim->temp_buffer,
+	                         gdk_pixbuf_get_pixels(pb),
+	                         anim->image_width,
+	                         anim->image_height,
+	                         gdk_pixbuf_get_rowstride(pb),
+	                         3, anim->palette,
+	                         TILEM_SCALE_SMOOTH);
+	anim->temp_buffer->data = NULL;
+
+	return pb;
+}
+
+static gboolean tilem_animation_is_static_image(GdkPixbufAnimation *ganim)
+{
+	TilemAnimation *anim = TILEM_ANIMATION(ganim);
+
+	g_return_val_if_fail(TILEM_IS_ANIMATION(ganim), FALSE);
+
+	if (anim->start == anim->end)
+		return TRUE;
+	else
+		return FALSE;
+}
+
+static GdkPixbuf * tilem_animation_get_static_image(GdkPixbufAnimation *ganim)
+{
+	TilemAnimation *anim = TILEM_ANIMATION(ganim);
+
+	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
+	g_return_val_if_fail(anim->start != NULL, NULL);
+
+	if (!anim->static_pixbuf)
+		anim->static_pixbuf = frame_to_pixbuf(anim, anim->start);
+
+	return anim->static_pixbuf;
+}
+
+static void tilem_animation_get_size(GdkPixbufAnimation *ganim,
+                                     int *width, int *height)
+{
+	TilemAnimation *anim = TILEM_ANIMATION(ganim);
+
+	g_return_if_fail(TILEM_IS_ANIMATION(anim));
+
+	if (width) *width = anim->image_width;
+	if (height) *height = anim->image_height;
+}
+
+static GdkPixbufAnimationIter *
+tilem_animation_get_iter(GdkPixbufAnimation *ganim,
+                         const GTimeVal *start_time)
+{
+	TilemAnimation *anim = TILEM_ANIMATION(ganim);
+	TilemAnimIter *iter;
+
+	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
+
+	iter = g_object_new(TILEM_TYPE_ANIM_ITER, NULL);
+	iter->anim = anim;
+	iter->frame = anim->start;
+	iter->current_time = *start_time;
+
+	g_object_ref(anim);
+
+	return GDK_PIXBUF_ANIMATION_ITER(iter);
+}
+
+static void tilem_animation_init(G_GNUC_UNUSED TilemAnimation *anim)
+{
+}
+
+static void tilem_animation_finalize(GObject *obj)
+{
+	TilemAnimation *anim = TILEM_ANIMATION(obj);
+	TilemAnimFrame *frm;
+
+	g_return_if_fail(TILEM_IS_ANIMATION(anim));
+
+	while (anim->start) {
+		frm = anim->start;
+		anim->start = frm->next;
+		free_frame(frm);
+	}
+
+	anim->start = anim->end = NULL;
+
+	if (anim->temp_buffer)
+		tilem_lcd_buffer_free(anim->temp_buffer);
+	anim->temp_buffer = NULL;
+
+	if (anim->palette)
+		tilem_free(anim->palette);
+	anim->palette = NULL;
+
+	if (anim->static_pixbuf)
+		g_object_unref(anim->static_pixbuf);
+	anim->static_pixbuf = NULL;
+
+	if (G_OBJECT_CLASS(tilem_animation_parent_class)->finalize)
+		(*G_OBJECT_CLASS(tilem_animation_parent_class)->finalize)(obj);
+}
+
+static void tilem_animation_class_init(TilemAnimationClass *klass)
+{
+	GdkPixbufAnimationClass *aclass = GDK_PIXBUF_ANIMATION_CLASS(klass);
+	GObjectClass *oclass = G_OBJECT_CLASS(klass);
+
+	aclass->is_static_image  = tilem_animation_is_static_image;
+	aclass->get_static_image = tilem_animation_get_static_image;
+	aclass->get_size         = tilem_animation_get_size;
+	aclass->get_iter         = tilem_animation_get_iter;
+
+	oclass->finalize = tilem_animation_finalize;
+}
+
+static int tilem_anim_iter_get_delay_time(GdkPixbufAnimationIter *giter)
+{
+	TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
+
+	g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), 0);
+	g_return_val_if_fail(iter->anim != NULL, 0);
+	g_return_val_if_fail(iter->frame != NULL, 0);
+
+	if (iter->anim->start == iter->anim->end)
+		return -1;
+	else
+		return ((iter->frame->duration - iter->time_elapsed)
+		        * iter->anim->time_stretch);
+}
+
+static GdkPixbuf * tilem_anim_iter_get_pixbuf(GdkPixbufAnimationIter *giter)
+{
+	TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
+
+	g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), NULL);
+	g_return_val_if_fail(iter->anim != NULL, NULL);
+	g_return_val_if_fail(iter->frame != NULL, NULL);
+
+	if (!iter->pixbuf)
+		iter->pixbuf = frame_to_pixbuf(iter->anim, iter->frame);
+
+	return iter->pixbuf;
+}
+
+static gboolean tilem_anim_iter_on_currently_loading_frame(G_GNUC_UNUSED GdkPixbufAnimationIter *giter)
+{
+	return FALSE;
+}
+
+static gboolean
+tilem_anim_iter_advance(GdkPixbufAnimationIter *giter,
+                        const GTimeVal *current_time)
+{
+	TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
+	int ms;
+
+	g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), FALSE);
+	g_return_val_if_fail(iter->anim != NULL, FALSE);
+	g_return_val_if_fail(iter->frame != NULL, FALSE);
+
+	ms = ((current_time->tv_usec - iter->current_time.tv_usec) / 1000
+	      + (current_time->tv_sec - iter->current_time.tv_sec) * 1000);
+
+	g_time_val_add(&iter->current_time, ms * 1000);
+
+	ms *= iter->anim->speed;
+
+	ms += iter->time_elapsed;
+	if (ms < iter->frame->duration) {
+		iter->time_elapsed = ms;
+		return FALSE;
+	}
+
+	if (iter->pixbuf)
+		g_object_unref(iter->pixbuf);
+	iter->pixbuf = NULL;
+
+	while (ms >= iter->frame->duration) {
+		ms -= iter->frame->duration;
+		if (iter->frame->next)
+			iter->frame = iter->frame->next;
+		else
+			iter->frame = iter->anim->start;
+	}
+
+	iter->time_elapsed = ms;
+	return TRUE;
+}
+
+static void tilem_anim_iter_init(G_GNUC_UNUSED TilemAnimIter *iter)
+{
+}
+
+static void tilem_anim_iter_finalize(GObject *obj)
+{
+	TilemAnimIter *iter = TILEM_ANIM_ITER(obj);
+
+	g_return_if_fail(TILEM_IS_ANIM_ITER(obj));
+
+	if (iter->anim)
+		g_object_unref(iter->anim);
+	iter->anim = NULL;
+
+	if (iter->pixbuf)
+		g_object_unref(iter->pixbuf);
+	iter->pixbuf = NULL;
+
+	if (G_OBJECT_CLASS(tilem_anim_iter_parent_class)->finalize)
+		(*G_OBJECT_CLASS(tilem_anim_iter_parent_class)->finalize)(obj);
+}
+
+static void tilem_anim_iter_class_init(TilemAnimIterClass *klass)
+{
+	GdkPixbufAnimationIterClass *iclass = GDK_PIXBUF_ANIMATION_ITER_CLASS(klass);
+	GObjectClass *oclass = G_OBJECT_CLASS(klass);
+
+	iclass->get_delay_time             = tilem_anim_iter_get_delay_time;
+	iclass->get_pixbuf                 = tilem_anim_iter_get_pixbuf;
+	iclass->on_currently_loading_frame = tilem_anim_iter_on_currently_loading_frame;
+	iclass->advance                    = tilem_anim_iter_advance;
+
+	oclass->finalize = tilem_anim_iter_finalize;
+}
+
+TilemAnimation * tilem_animation_new(int display_width, int display_height)
+{
+	TilemAnimation *anim;
+	TilemAnimFrame *dummy_frame;
+
+	g_return_val_if_fail(display_width > 0, NULL);
+	g_return_val_if_fail(display_height > 0, NULL);
+
+	anim = g_object_new(TILEM_TYPE_ANIMATION, NULL);
+	anim->display_width = display_width;
+	anim->display_height = display_height;
+	anim->frame_rowstride = (display_width + 7) & ~7;
+	anim->frame_size = anim->frame_rowstride * display_height;
+
+	anim->image_width = display_width;
+	anim->image_height = display_height;
+	anim->speed = 1.0;
+	anim->time_stretch = 1.0;
+
+	anim->temp_buffer = tilem_lcd_buffer_new();
+	anim->palette = tilem_color_palette_new(255, 255, 255, 0, 0, 0, GAMMA);
+
+	dummy_frame = alloc_frame(anim->frame_size);
+	dummy_frame->duration = 0;
+	dummy_frame->contrast = 0;
+	anim->start = anim->end = dummy_frame;
+
+	return anim;
+}
+
+gboolean tilem_animation_append_frame(TilemAnimation *anim,
+                                      const TilemLCDBuffer *buf,
+                                      int duration)
+{
+	TilemAnimFrame *frm;
+
+	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), FALSE);
+	g_return_val_if_fail(anim->end != NULL, FALSE);
+	g_return_val_if_fail(buf != NULL, FALSE);
+	g_return_val_if_fail(buf->data != NULL, FALSE);
+	g_return_val_if_fail(buf->height == anim->display_height, FALSE);
+	g_return_val_if_fail(buf->rowstride == anim->frame_rowstride, FALSE);
+
+	if (anim->out_of_memory)
+		return FALSE;
+
+	if (anim->end->contrast == buf->contrast
+	    && (anim->last_stamp == buf->stamp
+	        || !memcmp(anim->end->data, buf->data, anim->frame_size))) {
+		anim->end->duration += duration;
+	}
+	else {
+		if (anim->end->duration == 0) {
+			frm = anim->end;
+		}
+		else {
+			frm = alloc_frame(anim->frame_size);
+			if (!frm) {
+				anim->out_of_memory = TRUE;
+				return FALSE;
+			}
+			anim->end->next = frm;
+			anim->end = frm;
+		}
+
+		frm->contrast = buf->contrast;
+		frm->duration = duration;
+		memcpy(frm->data, buf->data, anim->frame_size);
+	}
+
+	anim->last_stamp = buf->stamp;
+	return TRUE;
+}
+
+void tilem_animation_set_size(TilemAnimation *anim, int width, int height)
+{
+	g_return_if_fail(TILEM_IS_ANIMATION(anim));
+	anim->image_width = width;
+	anim->image_height = height;
+}
+
+void tilem_animation_set_colors(TilemAnimation *anim,
+                                const GdkColor *foreground,
+                                const GdkColor *background)
+{
+	g_return_if_fail(TILEM_IS_ANIMATION(anim));
+	g_return_if_fail(foreground != NULL);
+	g_return_if_fail(background != NULL);
+
+	if (anim->palette)
+		tilem_free(anim->palette);
+
+	anim->palette = tilem_color_palette_new(background->red >> 8,
+	                                        background->green >> 8,
+	                                        background->blue >> 8,
+	                                        foreground->red >> 8,
+	                                        foreground->green >> 8,
+	                                        foreground->blue >> 8,
+	                                        GAMMA);
+}
+
+void tilem_animation_set_speed(TilemAnimation *anim, gdouble factor)
+{
+	g_return_if_fail(TILEM_IS_ANIMATION(anim));
+	g_return_if_fail(factor > 0.0);
+	anim->speed = factor;
+	anim->time_stretch = 1.0 / factor;
+}
+
+gdouble tilem_animation_get_speed(TilemAnimation *anim)
+{
+	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), 1.0);
+	return anim->speed;
+}
+
+TilemAnimFrame *tilem_animation_next_frame(TilemAnimation *anim,
+                                           TilemAnimFrame *frm)
+{
+	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
+	if (frm)
+		return frm->next;
+	else
+		return anim->start;
+}
+
+int tilem_anim_frame_get_duration(TilemAnimFrame *frm)
+{
+	g_return_val_if_fail(frm != NULL, 0);
+	return frm->duration;
+}
+
+void tilem_animation_get_indexed_image(TilemAnimation *anim,
+                                       TilemAnimFrame *frm,
+                                       byte **buffer,
+                                       int *width, int *height)
+{
+	g_return_if_fail(TILEM_IS_ANIMATION(anim));
+	g_return_if_fail(frm != NULL);
+	g_return_if_fail(buffer != NULL);
+	g_return_if_fail(width != NULL);
+	g_return_if_fail(height != NULL);
+
+	*width = anim->image_width;
+	*height = anim->image_height;
+	*buffer = g_new(byte, anim->image_width * anim->image_height);
+
+	set_lcdbuf_from_frame(anim, anim->temp_buffer, frm);
+	tilem_draw_lcd_image_indexed(anim->temp_buffer, *buffer,
+	                             anim->image_width, anim->image_height,
+	                             anim->image_width,
+	                             TILEM_SCALE_SMOOTH);
+	anim->temp_buffer->data = NULL;
+}
+
+gboolean tilem_animation_save(TilemAnimation *anim,
+                              const char *fname, const char *type,
+                              char **option_keys, char **option_values,
+                              GError **err)
+{
+	FILE *fp;
+	char *dname;
+	int errnum;
+	GdkPixbuf *pb;
+	gboolean status;
+	byte palette[768];
+	int i;
+
+	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), FALSE);
+	g_return_val_if_fail(fname != NULL, FALSE);
+	g_return_val_if_fail(type != NULL, FALSE);
+	g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+	if (strcmp(type, "gif") != 0) {
+		pb = gdk_pixbuf_animation_get_static_image
+			(GDK_PIXBUF_ANIMATION(anim));
+		status = gdk_pixbuf_savev(pb, fname, type,
+		                          option_keys, option_values,
+		                          err);
+		return status;
+	}
+
+	fp = g_fopen(fname, "wb");
+	if (!fp) {
+		errnum = errno;
+		dname = g_filename_display_name(fname);
+		g_set_error(err, G_FILE_ERROR,
+		            g_file_error_from_errno(errnum),
+		            "Failed to open '%s' for writing: %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		return FALSE;
+	}
+
+	for (i = 0; i < 256; i++) {
+		palette[3 * i] = anim->palette[i] >> 16;
+		palette[3 * i + 1] = anim->palette[i] >> 8;
+		palette[3 * i + 2] = anim->palette[i];
+	}
+
+	tilem_animation_write_gif(anim, palette, 256, fp);
+
+	if (fclose(fp)) {
+		errnum = errno;
+		dname = g_filename_display_name(fname);
+		g_set_error(err, G_FILE_ERROR,
+		            g_file_error_from_errno(errnum),
+		            "Error while closing '%s': %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		return FALSE;
+	}
+
+	return TRUE;
+}
diff --git a/tool/tilem-src/gui/animation.h b/tool/tilem-src/gui/animation.h
new file mode 100644
index 0000000..ec14391
--- /dev/null
+++ b/tool/tilem-src/gui/animation.h
@@ -0,0 +1,88 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TILEM_TYPE_ANIMATION           (tilem_animation_get_type())
+#define TILEM_ANIMATION(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), TILEM_TYPE_ANIMATION, TilemAnimation))
+#define TILEM_ANIMATION_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST((cls), TILEM_TYPE_ANIMATION, TilemAnimationClass))
+#define TILEM_IS_ANIMATION(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), TILEM_TYPE_ANIMATION))
+#define TILEM_IS_ANIMATION_CLASS(cls)  (G_TYPE_CHECK_CLASS_TYPE((cls), TILEM_TYPE_ANIMATION))
+#define TILEM_ANIMATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TILEM_TYPE_ANIMATION, TilemAnimationClass))
+
+typedef struct _TilemAnimation TilemAnimation;
+typedef struct _TilemAnimationClass TilemAnimationClass;
+typedef struct _TilemAnimFrame TilemAnimFrame;
+
+GType tilem_animation_get_type(void) G_GNUC_CONST;
+
+/* Create a new TilemAnimation for the given display dimensions. */
+TilemAnimation * tilem_animation_new(int display_width,
+                                     int display_height);
+
+/* Add a frame to the animation.  BUF holds the LCD contents, DURATION
+   is the length of time this frame should be displayed (in
+   milliseconds.) */
+gboolean tilem_animation_append_frame(TilemAnimation *anim,
+                                      const TilemLCDBuffer *buf,
+                                      int duration);
+
+/* Set output image size. */
+void tilem_animation_set_size(TilemAnimation *anim, int width, int height);
+
+/* Set output image colors. */
+void tilem_animation_set_colors(TilemAnimation *anim,
+                                const GdkColor *foreground,
+                                const GdkColor *background);
+
+/* Set animation speed factor */
+void tilem_animation_set_speed(TilemAnimation *anim, gdouble factor);
+
+/* Get animation speed factor */
+gdouble tilem_animation_get_speed(TilemAnimation *anim);
+
+/* Retrieve the next frame of the animation.  If FRM is NULL, retrieve
+   the first frame.  If FRM is non-null, it must be a frame belonging
+   to this animation. */
+TilemAnimFrame *tilem_animation_next_frame(TilemAnimation *anim,
+                                           TilemAnimFrame *frm);
+
+/* Get the duration of this frame (milliseconds by the original
+   clock.) */
+int tilem_anim_frame_get_duration(TilemAnimFrame *frm);
+
+/* Convert frame to an indexed-color image buffer.  FRM must be a
+   frame belonging to this animation.  The returned buffer must be
+   freed with g_free(). */
+void tilem_animation_get_indexed_image(TilemAnimation *anim,
+                                       TilemAnimFrame *frm,
+                                       byte **buffer,
+                                       int *width, int *height);
+
+/* Save animation to a file.  TYPE is an ASCII string describing the
+   type.  Options are specified by OPTION_KEYS and OPTION_VALUES (see
+   gdk_pixbuf_savev().) */
+gboolean tilem_animation_save(TilemAnimation *anim,
+                              const char *fname, const char *type,
+                              char **option_keys, char **option_values,
+                              GError **err);
+
+G_END_DECLS
diff --git a/tool/tilem-src/gui/breakpoints.c b/tool/tilem-src/gui/breakpoints.c
new file mode 100644
index 0000000..e1f9e5c
--- /dev/null
+++ b/tool/tilem-src/gui/breakpoints.c
@@ -0,0 +1,1060 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include <tilemdb.h>
+
+#include "gui.h"
+#include "disasmview.h"
+#include "fixedtreeview.h"
+
+/**************** Add/edit breakpoint dialog ****************/
+
+struct hex_entry {
+	GtkWidget *addr_label;
+	GtkWidget *addr_entry;
+	GtkWidget *page_label;
+	GtkWidget *page_entry;
+};
+
+struct breakpoint_dlg {
+	TilemDebugger *dbg;
+
+	GtkWidget *dlg;
+	GtkWidget *box;
+
+	GtkWidget *type_combo;
+	GtkWidget *access_cb[3];
+	GtkWidget *single_rb;
+	GtkWidget *range_rb;
+	GtkWidget *access_label;
+	GtkWidget *address_label;
+
+	struct hex_entry start;
+	struct hex_entry end;
+};
+
+static const struct {
+	char abbrev;
+	const char *desc;
+	const char *value_label;
+	int use_pages;
+	guint access_mask;
+} type_info[] = {
+	{ 'M', "Memory address (logical)", "Address", 0, 7 },
+	{ 'M', "Memory address (absolute)", "Address", 1, 7 },
+	{ 'P', "I/O port", "Port Number", 0, 6 },
+	{ 'I', "Z80 instruction", "Opcode", 0, 0 }
+};
+
+/* Determine currently selected address type */
+static guint get_bp_type(struct breakpoint_dlg *bpdlg)
+{
+	int i = gtk_combo_box_get_active(GTK_COMBO_BOX(bpdlg->type_combo));
+	return (i < 0 ? 0 : i);
+}
+
+/* Format address as a string */
+static void hex_entry_set_value(struct hex_entry *he, TilemDebugger *dbg,
+                                int type, dword value)
+{
+	const TilemCalc *calc;
+	char buf[20];
+	unsigned int page;
+
+	g_return_if_fail(dbg->emu != NULL);
+	g_return_if_fail(dbg->emu->calc != NULL);
+
+	calc = dbg->emu->calc;
+
+	switch (type) {
+	case TILEM_DB_BREAK_LOGICAL:
+		g_snprintf(buf, sizeof(buf), "%04X", value);
+		break;
+
+	case TILEM_DB_BREAK_PHYSICAL:
+		if (value >= calc->hw.romsize) {
+			value -= calc->hw.romsize;
+			page = (value >> 14) + calc->hw.rampagemask;
+		}
+		else {
+			page = (value >> 14);
+		}
+
+		g_snprintf(buf, sizeof(buf), "%02X", page);
+		gtk_entry_set_text(GTK_ENTRY(he->page_entry), buf);
+
+		g_snprintf(buf, sizeof(buf), "%04X", value & 0x3fff);
+		break;
+
+	case TILEM_DB_BREAK_PORT:
+		g_snprintf(buf, sizeof(buf), "%02X", value);
+		break;
+
+	case TILEM_DB_BREAK_OPCODE:
+		if (value < 0x100)
+			g_snprintf(buf, sizeof(buf), "%02X", value);
+		else if (value < 0x10000)
+			g_snprintf(buf, sizeof(buf), "%04X", value);
+		else if (value < 0x1000000)
+			g_snprintf(buf, sizeof(buf), "%06X", value);
+		else
+			g_snprintf(buf, sizeof(buf), "%08X", value);
+		break;
+
+	default:
+		g_return_if_reached();
+	}
+
+	gtk_entry_set_text(GTK_ENTRY(he->addr_entry), buf);
+}
+
+/* Parse contents of entry */
+static gboolean parse_num(TilemDebugger *dbg, const char *s, dword *a)
+{
+	const char *n;
+	char *e;
+
+	g_return_val_if_fail(s != NULL, FALSE);
+
+	if (s[0] == '$')
+		n = s + 1;
+	else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
+		n = s + 2;
+	else
+		n = s;
+
+	*a = strtol(n, &e, 16);
+	if (e != n) {
+		if (*e == 'h' || *e == 'H')
+			e++;
+		if (*e == 0)
+			return TRUE;
+	}
+
+	if (dbg->dasm && tilem_disasm_get_label(dbg->dasm, s, a))
+		return TRUE;
+
+	return FALSE;
+}
+
+/* Parse user input from hex entry */
+static gboolean hex_entry_parse_value(struct hex_entry *he, TilemDebugger *dbg,
+                                      int type, dword *value)
+{
+	const TilemCalc *calc = dbg->emu->calc;
+	dword page;
+	const char *s;
+
+	g_return_val_if_fail(calc != NULL, 0);
+
+	s = gtk_entry_get_text(GTK_ENTRY(he->addr_entry));
+	if (!parse_num(dbg, s, value))
+		return FALSE;
+
+	if (type != TILEM_DB_BREAK_PHYSICAL)
+		return TRUE;
+
+	s = gtk_entry_get_text(GTK_ENTRY(he->page_entry));
+	if (!parse_num(dbg, s, &page))
+		return FALSE;
+
+	*value &= 0x3fff;
+
+	if (page >= calc->hw.rampagemask) {
+		*value += ((page - calc->hw.rampagemask) << 14);
+		*value %= calc->hw.ramsize;
+		*value += calc->hw.romsize;
+	}
+	else {
+		*value += (page << 14);
+		*value %= calc->hw.romsize;
+	}
+
+	return TRUE;
+}
+
+/* Parse input fields and check if they make sense */
+static gboolean parse_input(struct breakpoint_dlg *bpdlg,
+                            TilemDebugBreakpoint *bp)
+{
+	int i;
+	dword addr0, addr1;
+
+	bp->mask = bp->start = bp->end = 0xffffffff;
+	bp->type = get_bp_type(bpdlg);
+	bp->mode = 0;
+
+	for (i = 0; i < 3; i++)
+		if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->access_cb[i])))
+			bp->mode += (1 << i);
+
+	bp->mode &= type_info[bp->type].access_mask;
+	if (bp->type == TILEM_DB_BREAK_OPCODE)
+		bp->mode = TILEM_DB_BREAK_EXEC;
+	else if (bp->mode == 0)
+		return FALSE;
+
+	if (!hex_entry_parse_value(&bpdlg->start, bpdlg->dbg,
+	                           bp->type, &addr0))
+		return FALSE;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->range_rb))) {
+		if (!hex_entry_parse_value(&bpdlg->end, bpdlg->dbg,
+		                           bp->type, &addr1))
+			return FALSE;
+	}
+	else {
+		addr1 = addr0;
+	}
+
+	if (bp->type == TILEM_DB_BREAK_LOGICAL)
+		bp->mask = 0xffff;
+	else if (bp->type == TILEM_DB_BREAK_PORT)
+		bp->mask = 0xff;
+	else
+		bp->mask = 0xffffffff;
+
+	bp->start = addr0 & bp->mask;
+	bp->end = addr1 & bp->mask;
+	if (bp->end < bp->start)
+		return FALSE;
+
+	return TRUE;
+}
+
+/* Check if input fields are valid, and enable/disable OK response as
+   appropriate */
+static void validate(struct breakpoint_dlg *bpdlg)
+{
+	TilemDebugBreakpoint tmpbp;
+
+	if (parse_input(bpdlg, &tmpbp))
+		gtk_dialog_set_response_sensitive(GTK_DIALOG(bpdlg->dlg),
+		                                  GTK_RESPONSE_OK, TRUE);
+	else
+		gtk_dialog_set_response_sensitive(GTK_DIALOG(bpdlg->dlg),
+		                                  GTK_RESPONSE_OK, FALSE);
+}
+
+/* Enable/disable check buttons for access mode */
+static void set_access_mask(struct breakpoint_dlg *bpdlg, guint mask)
+{
+	int i;
+
+	if (mask)
+		gtk_widget_show(bpdlg->access_label);
+	else
+		gtk_widget_hide(bpdlg->access_label);
+
+	for (i = 0; i < 3; i++) {
+		if (mask & (1 << i))
+			gtk_widget_show(bpdlg->access_cb[i]);
+		else
+			gtk_widget_hide(bpdlg->access_cb[i]);
+	}
+}
+
+/* Combo box changed */
+static void addr_type_changed(G_GNUC_UNUSED GtkComboBox *combo, gpointer data)
+{
+	struct breakpoint_dlg *bpdlg = data;
+	int type = get_bp_type(bpdlg);
+	gboolean range = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->range_rb));
+	char *s;
+
+	s = g_strdup_printf("<b>%s</b>", type_info[type].value_label);
+	gtk_label_set_markup(GTK_LABEL(bpdlg->address_label), s);
+	g_free(s);
+
+	set_access_mask(bpdlg, type_info[type].access_mask);
+
+	if (type_info[type].use_pages) {
+		gtk_widget_show(bpdlg->start.page_label);
+		gtk_widget_show(bpdlg->start.page_entry);
+	}
+	else {
+		gtk_widget_hide(bpdlg->start.page_label);
+		gtk_widget_hide(bpdlg->start.page_entry);
+	}
+
+	if (range) {
+		gtk_label_set_text_with_mnemonic(GTK_LABEL(bpdlg->start.addr_label),
+		                                 "_Start:");
+		gtk_widget_show(bpdlg->end.addr_label);
+		gtk_widget_show(bpdlg->end.addr_entry);
+	}
+	else {
+		gtk_label_set_text_with_mnemonic(GTK_LABEL(bpdlg->start.addr_label),
+		                                 "_Value:");
+		gtk_widget_hide(bpdlg->end.addr_label);
+		gtk_widget_hide(bpdlg->end.addr_entry);
+	}
+
+	if (type_info[type].use_pages && range) {
+		gtk_widget_show(bpdlg->end.page_label);
+		gtk_widget_show(bpdlg->end.page_entry);
+	}
+	else {
+		gtk_widget_hide(bpdlg->end.page_label);
+		gtk_widget_hide(bpdlg->end.page_entry);
+	}
+
+	validate(bpdlg);
+}
+
+/* Access mode changed */
+static void access_changed(G_GNUC_UNUSED GtkToggleButton *tb, gpointer data)
+{
+	struct breakpoint_dlg *bpdlg = data;
+	validate(bpdlg);
+}
+
+/* Single/range mode changed */
+static void range_mode_changed(G_GNUC_UNUSED GtkToggleButton *tb, gpointer data)
+{
+	struct breakpoint_dlg *bpdlg = data;
+	addr_type_changed(NULL, bpdlg);
+}
+
+/* Text of entry changed */
+static void entry_edited(G_GNUC_UNUSED GtkEntry *entry,
+                         gpointer data)
+{
+	struct breakpoint_dlg *bpdlg = data;
+	validate(bpdlg);
+}
+
+/* Key presssed in entry */
+static gboolean entry_key_event(G_GNUC_UNUSED GtkWidget *entry,
+                                GdkEventKey *ev, gpointer data)
+{
+	struct breakpoint_dlg *bpdlg = data;
+	TilemDebugBreakpoint tmpbp;
+
+	if (ev->state & GDK_MODIFIER_MASK)
+		return FALSE;
+
+	if (ev->keyval != GDK_Return
+	    && ev->keyval != GDK_KP_Enter
+	    && ev->keyval != GDK_ISO_Enter)
+		return FALSE;
+
+	if (parse_input(bpdlg, &tmpbp))
+		gtk_dialog_response(GTK_DIALOG(bpdlg->dlg), GTK_RESPONSE_OK);
+	else
+		gtk_widget_child_focus(bpdlg->box, GTK_DIR_TAB_FORWARD);
+
+	return TRUE;
+}
+
+static void init_hex_entry(struct breakpoint_dlg *bpdlg,
+                           struct hex_entry *he, const char *label,
+                           GtkTable *tbl, int ypos)
+{
+	GtkWidget *align, *lbl;
+
+	he->addr_label = lbl = gtk_label_new_with_mnemonic(label);
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 6);
+	gtk_container_add(GTK_CONTAINER(align), lbl);
+	gtk_table_attach(tbl, align, 0, 1, ypos, ypos + 1,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	he->addr_entry = gtk_entry_new();
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), he->addr_entry);
+	gtk_table_attach(tbl, he->addr_entry, 1, 2, ypos, ypos + 1,
+	                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	g_signal_connect(he->addr_entry, "changed",
+	                 G_CALLBACK(entry_edited), bpdlg);
+	g_signal_connect(he->addr_entry, "key-press-event",
+	                 G_CALLBACK(entry_key_event), bpdlg);
+
+	he->page_label = lbl = gtk_label_new("Page:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(tbl, lbl, 2, 3, ypos, ypos + 1,
+	                 GTK_FILL, GTK_FILL, 6, 0);
+
+	he->page_entry = gtk_entry_new();
+	gtk_entry_set_width_chars(GTK_ENTRY(he->page_entry), 5);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), he->page_entry);
+	gtk_table_attach(tbl, he->page_entry, 3, 4, ypos, ypos + 1,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	g_signal_connect(he->page_entry, "changed",
+	                 G_CALLBACK(entry_edited), bpdlg);
+	g_signal_connect(he->page_entry, "key-press-event",
+	                 G_CALLBACK(entry_key_event), bpdlg);
+}
+
+static gboolean edit_breakpoint(TilemDebugger *dbg,
+                                GtkWindow *parent_window,
+                                const char *title,
+                                TilemDebugBreakpoint *bp,
+                                gboolean edit_existing)
+{
+	GtkWidget *dlg, *vbox, *frame, *tbl, *hbox, *lbl, *combo, *cb, *rb;
+	struct breakpoint_dlg bpdlg;
+	gsize i;
+
+	g_return_val_if_fail(bp != NULL, FALSE);
+	g_return_val_if_fail(dbg != NULL, FALSE);
+	g_return_val_if_fail(dbg->emu != NULL, FALSE);
+	g_return_val_if_fail(dbg->emu->calc != NULL, FALSE);
+
+	bpdlg.dbg = dbg;
+
+	dlg = gtk_dialog_new_with_buttons(title, parent_window,
+	                                  GTK_DIALOG_MODAL,
+	                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                                  GTK_STOCK_OK, GTK_RESPONSE_OK,
+	                                  NULL);
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
+	                                        GTK_RESPONSE_OK,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+	gtk_dialog_set_default_response(GTK_DIALOG(dlg),
+	                                GTK_RESPONSE_OK);
+
+	bpdlg.dlg = dlg;
+
+	bpdlg.box = gtk_vbox_new(FALSE, 6);
+	gtk_container_set_border_width(GTK_CONTAINER(bpdlg.box), 6);
+
+	tbl = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
+	gtk_table_set_col_spacings(GTK_TABLE(tbl), 6);
+
+	/* Breakpoint type */
+
+	lbl = gtk_label_new_with_mnemonic("Breakpoint _type:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 1, 0, 1,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	combo = gtk_combo_box_new_text();
+	for (i = 0; i < G_N_ELEMENTS(type_info); i++)
+		gtk_combo_box_append_text(GTK_COMBO_BOX(combo), type_info[i].desc);
+
+	bpdlg.type_combo = combo;
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), combo);
+	gtk_table_attach(GTK_TABLE(tbl), combo, 1, 2, 0, 1,
+	                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), bp->type);
+
+	/* Access mode */
+
+	bpdlg.access_label = lbl = gtk_label_new("Break when:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 1, 1, 2,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	hbox = gtk_hbox_new(FALSE, 6);
+
+	cb = gtk_check_button_new_with_mnemonic("_Reading");
+	gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
+	bpdlg.access_cb[2] = cb;
+
+	cb = gtk_check_button_new_with_mnemonic("_Writing");
+	gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
+	bpdlg.access_cb[1] = cb;
+
+	cb = gtk_check_button_new_with_mnemonic("E_xecuting");
+	gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
+	bpdlg.access_cb[0] = cb;
+
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[0]),
+	                             bp->mode & 1);
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[1]),
+	                             bp->mode & 2);
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[2]),
+	                             bp->mode & 4);
+
+	gtk_table_attach(GTK_TABLE(tbl), hbox, 1, 2, 1, 2,
+	                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	frame = new_frame("Breakpoint Condition", tbl);
+	gtk_box_pack_start(GTK_BOX(bpdlg.box), frame, FALSE, FALSE, 0);
+
+	/* Addresses */
+
+	tbl = gtk_table_new(3, 4, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
+
+	hbox = gtk_hbox_new(FALSE, 6);
+
+	rb = gtk_radio_button_new_with_mnemonic(NULL, "Si_ngle");
+	gtk_box_pack_start(GTK_BOX(hbox), rb, FALSE, FALSE, 0);
+	bpdlg.single_rb = rb;
+
+	rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(rb), "R_ange");
+	gtk_box_pack_start(GTK_BOX(hbox), rb, FALSE, FALSE, 0);
+	bpdlg.range_rb = rb;
+
+	if (edit_existing && bp->end != bp->start)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
+
+	gtk_table_attach(GTK_TABLE(tbl), hbox, 0, 2, 0, 1,
+	                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	init_hex_entry(&bpdlg, &bpdlg.start, "S_tart:", GTK_TABLE(tbl), 1);
+	init_hex_entry(&bpdlg, &bpdlg.end, "_End:", GTK_TABLE(tbl), 2);
+
+	frame = new_frame("Address", tbl);
+	bpdlg.address_label = gtk_frame_get_label_widget(GTK_FRAME(frame));
+	gtk_box_pack_start(GTK_BOX(bpdlg.box), frame, FALSE, FALSE, 0);
+	gtk_widget_show_all(bpdlg.box);
+
+	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
+	gtk_box_pack_start(GTK_BOX(vbox), bpdlg.box, FALSE, FALSE, 0);
+
+	if (edit_existing) {
+		hex_entry_set_value(&bpdlg.start, dbg, bp->type, bp->start);
+		hex_entry_set_value(&bpdlg.end, dbg, bp->type, bp->end);
+	}
+
+	g_signal_connect(combo, "changed",
+	                 G_CALLBACK(addr_type_changed), &bpdlg);
+	g_signal_connect(bpdlg.access_cb[0], "toggled",
+	                 G_CALLBACK(access_changed), &bpdlg);
+	g_signal_connect(bpdlg.access_cb[1], "toggled",
+	                 G_CALLBACK(access_changed), &bpdlg);
+	g_signal_connect(bpdlg.access_cb[2], "toggled",
+	                 G_CALLBACK(access_changed), &bpdlg);
+	g_signal_connect(bpdlg.single_rb, "toggled",
+	                 G_CALLBACK(range_mode_changed), &bpdlg);
+
+	addr_type_changed(NULL, &bpdlg);
+
+	gtk_widget_grab_focus(bpdlg.start.addr_entry);
+
+	do {
+		if (gtk_dialog_run(GTK_DIALOG(dlg)) != GTK_RESPONSE_OK) {
+			gtk_widget_destroy(dlg);
+			return FALSE;
+		}
+	} while (!parse_input(&bpdlg, bp));
+
+	gtk_widget_destroy(dlg);
+	return TRUE;
+}
+
+/**************** Breakpoint list dialog ****************/
+
+enum {
+	COL_BP,
+	COL_START,
+	COL_END,
+	COL_TYPE_STR,
+	COL_START_STR,
+	COL_END_STR,
+	COL_ENABLED,
+	N_COLUMNS
+};
+
+struct bplist_dlg {
+	TilemDebugger *dbg;
+	GtkWidget *dlg;
+	GtkListStore *store;
+	GtkWidget *treeview;
+	GtkWidget *remove_btn;
+	GtkWidget *edit_btn;
+	GtkWidget *clear_btn;
+};
+
+/* Convert address into a displayable string */
+static void format_address(TilemDebugger *dbg,
+                           char *buf, int bufsize,
+                           int type, dword value)
+{
+	const TilemCalc *calc;
+	unsigned int page;
+
+	g_return_if_fail(dbg->emu != NULL);
+	g_return_if_fail(dbg->emu->calc != NULL);
+
+	calc = dbg->emu->calc;
+
+	switch (type) {
+	case TILEM_DB_BREAK_LOGICAL:
+		g_snprintf(buf, bufsize, "%04X", value);
+		break;
+
+	case TILEM_DB_BREAK_PHYSICAL:
+		if (value >= calc->hw.romsize) {
+			value -= calc->hw.romsize;
+			page = (value >> 14) + calc->hw.rampagemask;
+		}
+		else {
+			page = (value >> 14);
+		}
+
+		g_snprintf(buf, bufsize, "%02X:%04X", page, value & 0x3fff);
+		break;
+
+	case TILEM_DB_BREAK_PORT:
+		g_snprintf(buf, bufsize, "%02X", value);
+		break;
+
+	case TILEM_DB_BREAK_OPCODE:
+		if (value < 0x100)
+			g_snprintf(buf, bufsize, "%02X", value);
+		else if (value < 0x10000)
+			g_snprintf(buf, bufsize, "%04X", value);
+		else if (value < 0x1000000)
+			g_snprintf(buf, bufsize, "%06X", value);
+		else
+			g_snprintf(buf, bufsize, "%08X", value);
+		break;
+
+	default:
+		g_return_if_reached();
+	}
+}
+
+/* Store breakpoint properties in tree model */
+static void set_iter_from_bp(struct bplist_dlg *bpldlg, GtkTreeIter *iter,
+                             const TilemDebugBreakpoint *bp)
+{
+	char tbuf[5], sbuf[10], ebuf[10];
+	int i, j;
+
+	g_return_if_fail(bp != NULL);
+
+	tbuf[0] = type_info[bp->type].abbrev;
+	j = 1;
+	for (i = 0; i < 3; i++)
+		if (bp->mode & (4 >> i))
+			tbuf[j++] = "RWX"[i];
+	tbuf[j] = 0;
+
+	format_address(bpldlg->dbg, sbuf, sizeof(sbuf), bp->type, bp->start);
+	format_address(bpldlg->dbg, ebuf, sizeof(ebuf), bp->type, bp->end);
+
+	gtk_list_store_set(bpldlg->store, iter,
+	                   COL_BP, bp,
+	                   COL_START, bp->start,
+	                   COL_END, bp->end,
+	                   COL_TYPE_STR, tbuf,
+	                   COL_START_STR, sbuf,
+	                   COL_END_STR, ebuf,
+	                   COL_ENABLED, !bp->disabled,
+	                   -1);
+}
+
+/* Get breakpoint pointer for the given tree model row */
+static TilemDebugBreakpoint *get_iter_bp(struct bplist_dlg *bpldlg,
+                                         GtkTreeIter *iter)
+{
+	gpointer ptr;
+	gtk_tree_model_get(GTK_TREE_MODEL(bpldlg->store), iter,
+	                   COL_BP, &ptr, -1);
+	return (TilemDebugBreakpoint *) ptr;
+}
+
+/* Set buttons sensitive or insensitive depending on whether any BPs
+   are selected */
+static void update_buttons(struct bplist_dlg *bpldlg)
+{
+	GtkTreeSelection *sel;
+	gboolean any_sel;
+
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
+
+	any_sel = gtk_tree_selection_get_selected(sel, NULL, NULL);
+	gtk_widget_set_sensitive(bpldlg->remove_btn, any_sel);
+	gtk_widget_set_sensitive(bpldlg->edit_btn, any_sel);
+
+	gtk_widget_set_sensitive(bpldlg->clear_btn,
+	                         bpldlg->dbg->breakpoints != NULL);
+}
+
+/* "Add breakpoint" button clicked */
+static void add_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
+{
+	struct bplist_dlg *bpldlg = data;
+	TilemDebugBreakpoint tmpbp, *newbp;
+	TilemDebugger *dbg = bpldlg->dbg;
+	GtkTreeIter iter;
+
+	memset(&tmpbp, 0, sizeof(tmpbp));
+	tmpbp.type = dbg->last_bp_type;
+	tmpbp.mode = dbg->last_bp_mode;
+
+	if (!edit_breakpoint(dbg, GTK_WINDOW(bpldlg->dlg),
+	                     "Add Breakpoint", &tmpbp, FALSE))
+		return;
+
+	dbg->last_bp_type = tmpbp.type;
+	dbg->last_bp_mode = tmpbp.mode;
+
+	newbp = tilem_debugger_add_breakpoint(dbg, &tmpbp);
+	gtk_list_store_append(bpldlg->store, &iter);
+	set_iter_from_bp(bpldlg, &iter, newbp);
+
+	update_buttons(bpldlg);
+}
+
+/* "Remove breakpoint" button clicked */
+static void remove_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
+{
+	struct bplist_dlg *bpldlg = data;
+	GtkTreeSelection *sel;
+	GtkTreeIter iter;
+	TilemDebugBreakpoint *bp;
+
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
+
+	if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
+		return;
+
+	bp = get_iter_bp(bpldlg, &iter);
+	g_return_if_fail(bp != NULL);
+	gtk_list_store_remove(bpldlg->store, &iter);
+	tilem_debugger_remove_breakpoint(bpldlg->dbg, bp);
+
+	update_buttons(bpldlg);
+}
+
+/* Edit an existing breakpoint */
+static void edit_row(struct bplist_dlg *bpldlg, GtkTreeIter *iter)
+{
+	TilemDebugBreakpoint *bp, tmpbp;
+
+	bp = get_iter_bp(bpldlg, iter);
+	g_return_if_fail(bp != NULL);
+	tmpbp = *bp;
+
+	if (!edit_breakpoint(bpldlg->dbg, GTK_WINDOW(bpldlg->dlg),
+	                     "Edit Breakpoint", &tmpbp, TRUE))
+		return;
+
+	tmpbp.disabled = 0;
+	tilem_debugger_change_breakpoint(bpldlg->dbg, bp, &tmpbp);
+	set_iter_from_bp(bpldlg, iter, bp);
+
+	update_buttons(bpldlg);
+}
+
+/* "Edit breakpoint" button clicked */
+static void edit_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
+{
+	struct bplist_dlg *bpldlg = data;
+	GtkTreeSelection *sel;
+	GtkTreeIter iter;
+
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
+
+	if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
+		return;
+
+	edit_row(bpldlg, &iter);
+}
+
+/* "Clear breakpoints" button clicked */
+static void clear_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
+{
+	struct bplist_dlg *bpldlg = data;
+	GtkWidget *dlg;
+	TilemDebugBreakpoint *bp;
+
+	dlg = gtk_message_dialog_new(GTK_WINDOW(bpldlg->dlg),
+	                             GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
+	                             GTK_BUTTONS_NONE,
+	                             "Clear all breakpoints?");
+	gtk_message_dialog_format_secondary_markup
+		(GTK_MESSAGE_DIALOG(dlg),
+		 "All existing breakpoints will be deleted and"
+		 " cannot be restored.");
+	gtk_dialog_add_buttons(GTK_DIALOG(dlg),
+	                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                       GTK_STOCK_CLEAR, GTK_RESPONSE_ACCEPT,
+	                       NULL);
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
+	                                        GTK_RESPONSE_ACCEPT,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+
+	if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT) {
+		while (bpldlg->dbg->breakpoints) {
+			bp = bpldlg->dbg->breakpoints->data;
+			tilem_debugger_remove_breakpoint(bpldlg->dbg, bp);
+		}
+		gtk_list_store_clear(bpldlg->store);
+		update_buttons(bpldlg);
+	}
+
+	gtk_widget_destroy(dlg);
+}
+
+/* Row activated (double-clicked, usually) */
+static void row_activated(G_GNUC_UNUSED GtkTreeView *treeview,
+                          GtkTreePath *path,
+                          G_GNUC_UNUSED GtkTreeViewColumn *col,
+                          gpointer data)
+{
+	struct bplist_dlg *bpldlg = data;
+	GtkTreeIter iter;
+
+	if (gtk_tree_model_get_iter(GTK_TREE_MODEL(bpldlg->store),
+	                            &iter, path))
+		edit_row(bpldlg, &iter);
+}
+
+/* Toggle button clicked for a breakpoint */
+static void enabled_toggled(G_GNUC_UNUSED GtkCellRendererToggle *cell,
+                            gchar *pathstr, gpointer data)
+{
+	struct bplist_dlg *bpldlg = data;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	TilemDebugBreakpoint *bp, tmpbp;
+
+	path = gtk_tree_path_new_from_string(pathstr);
+	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(bpldlg->store),
+	                             &iter, path)) {
+		gtk_tree_path_free(path);
+		return;
+	}
+	gtk_tree_path_free(path);
+
+	bp = get_iter_bp(bpldlg, &iter);
+	g_return_if_fail(bp != NULL);
+	tmpbp = *bp;
+	tmpbp.disabled = !tmpbp.disabled;
+	tilem_debugger_change_breakpoint(bpldlg->dbg, bp, &tmpbp);
+	set_iter_from_bp(bpldlg, &iter, bp);
+}
+
+/* Selection changed */
+static void selection_changed(G_GNUC_UNUSED GtkTreeSelection *sel,
+                              gpointer data)
+{
+	struct bplist_dlg *bpldlg = data;
+	update_buttons(bpldlg);
+}
+
+/* Show a dialog letting the user add, remove, and edit breakpoints */
+void tilem_debugger_edit_breakpoints(TilemDebugger *dbg)
+{
+	struct bplist_dlg bpldlg;
+	GtkWidget *dlg, *hbox, *treeview, *sw, *bbox, *btn, *vbox, *vbox2,
+		*invalid_cb, *undoc_cb;
+	GtkListStore *store;
+	GtkTreeViewColumn *col;
+	GtkCellRenderer *cell;
+	GtkTreeIter iter;
+	GSList *l;
+	GtkTreeSelection *sel;
+	unsigned int flags;
+
+	g_return_if_fail(dbg != NULL);
+	g_return_if_fail(dbg->emu != NULL);
+	g_return_if_fail(dbg->emu->calc != NULL);
+
+	bpldlg.dbg = dbg;
+
+	dlg = gtk_dialog_new_with_buttons("Breakpoints",
+	                                  GTK_WINDOW(dbg->window),
+	                                  GTK_DIALOG_MODAL,
+	                                  GTK_STOCK_CLOSE,
+	                                  GTK_RESPONSE_ACCEPT,
+	                                  NULL);
+
+	gtk_window_set_default_size(GTK_WINDOW(dlg), -1, 300);
+
+	store = gtk_list_store_new(N_COLUMNS,
+	                           G_TYPE_POINTER,
+	                           G_TYPE_INT,
+	                           G_TYPE_INT,
+	                           G_TYPE_STRING,
+	                           G_TYPE_STRING,
+	                           G_TYPE_STRING,
+	                           G_TYPE_BOOLEAN);
+
+	bpldlg.dlg = dlg;
+	bpldlg.store = store;
+
+	vbox = gtk_vbox_new(FALSE, 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+
+	hbox = gtk_hbox_new(FALSE, 6);
+
+	for (l = dbg->breakpoints; l; l = l->next) {
+		gtk_list_store_append(store, &iter);
+		set_iter_from_bp(&bpldlg, &iter, l->data);
+	}
+
+	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
+	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
+	bpldlg.treeview = treeview;
+
+	fixed_tree_view_init(treeview, 0,
+	                     COL_TYPE_STR, "MRWX ",
+	                     COL_START_STR, "DD:DDDD ",
+	                     COL_END_STR, "DD:DDDD ",
+	                     COL_ENABLED, TRUE,
+	                     -1);
+
+	g_signal_connect(treeview, "row-activated",
+	                 G_CALLBACK(row_activated), &bpldlg);
+
+	/* Enabled/type column */
+
+	col = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(col, "Type");
+	gtk_tree_view_column_set_sort_column_id(col, COL_TYPE_STR);
+	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+
+	cell = gtk_cell_renderer_toggle_new();
+	gtk_tree_view_column_pack_start(col, cell, FALSE);
+	gtk_tree_view_column_set_attributes(col, cell,
+	                                    "active", COL_ENABLED,
+	                                    NULL);
+	g_signal_connect(cell, "toggled",
+	                 G_CALLBACK(enabled_toggled), &bpldlg);
+
+	cell = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(col, cell, TRUE);
+	gtk_tree_view_column_set_attributes(col, cell,
+	                                    "text", COL_TYPE_STR,
+	                                    NULL);
+
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
+
+	/* Start column */
+
+	cell = gtk_cell_renderer_text_new();
+	col = gtk_tree_view_column_new_with_attributes
+		("Start", cell, "text", COL_START_STR, NULL);
+	gtk_tree_view_column_set_sort_column_id(col, COL_START);
+	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
+
+	/* End column */
+
+	cell = gtk_cell_renderer_text_new();
+	col = gtk_tree_view_column_new_with_attributes
+		("End", cell, "text", COL_END_STR, NULL);
+	gtk_tree_view_column_set_sort_column_id(col, COL_END);
+	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
+
+	sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+	                               GTK_POLICY_NEVER,
+	                               GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+	                                    GTK_SHADOW_IN);
+	gtk_container_add(GTK_CONTAINER(sw), treeview);
+
+	gtk_box_pack_start(GTK_BOX(hbox), sw, TRUE, TRUE, 0);
+
+	/* Buttons */
+
+	bbox = gtk_vbutton_box_new();
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
+	gtk_box_set_spacing(GTK_BOX(bbox), 6);
+
+	btn = gtk_button_new_from_stock(GTK_STOCK_ADD);
+	g_signal_connect(btn, "clicked",
+	                 G_CALLBACK(add_clicked), &bpldlg);
+	gtk_container_add(GTK_CONTAINER(bbox), btn);
+
+	btn = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
+	g_signal_connect(btn, "clicked",
+	                 G_CALLBACK(remove_clicked), &bpldlg);
+	gtk_container_add(GTK_CONTAINER(bbox), btn);
+	bpldlg.remove_btn = btn;
+
+	btn = gtk_button_new_from_stock(GTK_STOCK_EDIT);
+	g_signal_connect(btn, "clicked",
+	                 G_CALLBACK(edit_clicked), &bpldlg);
+	gtk_container_add(GTK_CONTAINER(bbox), btn);
+	bpldlg.edit_btn = btn;
+
+	btn = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
+	g_signal_connect(btn, "clicked",
+	                 G_CALLBACK(clear_clicked), &bpldlg);
+	gtk_container_add(GTK_CONTAINER(bbox), btn);
+	bpldlg.clear_btn = btn;
+
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+	g_signal_connect(sel, "changed",
+	                 G_CALLBACK(selection_changed), &bpldlg);
+
+	update_buttons(&bpldlg);
+
+	gtk_box_pack_start(GTK_BOX(hbox), bbox, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
+
+	invalid_cb = gtk_check_button_new_with_mnemonic
+		("Break on _invalid instructions");
+	undoc_cb = gtk_check_button_new_with_mnemonic
+		("Break on _undocumented instructions");
+
+	tilem_calc_emulator_lock(dbg->emu);
+	flags = dbg->emu->calc->z80.emuflags;
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(invalid_cb),
+	                             (flags & TILEM_Z80_BREAK_INVALID));
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(undoc_cb),
+	                             (flags & TILEM_Z80_BREAK_UNDOCUMENTED));
+	tilem_calc_emulator_unlock(dbg->emu);
+
+	gtk_box_pack_start(GTK_BOX(vbox), invalid_cb, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), undoc_cb, FALSE, FALSE, 0);
+
+	gtk_widget_show_all(vbox);
+
+	gtk_widget_grab_focus(treeview);
+
+	vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
+	gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
+
+	gtk_dialog_run(GTK_DIALOG(dlg));
+
+	tilem_calc_emulator_lock(dbg->emu);
+	flags = dbg->emu->calc->z80.emuflags;
+	flags &= ~(TILEM_Z80_BREAK_INVALID | TILEM_Z80_BREAK_UNDOCUMENTED);
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(invalid_cb)))
+		flags |= TILEM_Z80_BREAK_INVALID;
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(undoc_cb)))
+		flags |= TILEM_Z80_BREAK_UNDOCUMENTED;
+	dbg->emu->calc->z80.emuflags = flags;
+	tilem_calc_emulator_unlock(dbg->emu);
+
+	gtk_widget_destroy(dlg);
+}
diff --git a/tool/tilem-src/gui/charmap.c b/tool/tilem-src/gui/charmap.c
new file mode 100644
index 0000000..c3acf69
--- /dev/null
+++ b/tool/tilem-src/gui/charmap.c
@@ -0,0 +1,127 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <ticonv.h>
+#include <tilem.h>
+
+#include "charmap.h"
+
+#define UNDEF 0xfffd
+
+static const unsigned long ti81chars[256] = {
+	0,      ' ',    0x2192, 0x2191, 0x2193, 0x25B6, '<',    0x2264,
+	'=',    0x2260, '>',    0x2265, 0x3D20DE, 0x207C, '+',  '-',
+	'*',    '/',    '^',    0x221A, '(',    ')',    '[',    ']',
+	'{',    '}',    '?',    '!',    ':',    ',',    0x2026, 0x207B00B9,
+	0x207B, 0xB7,   0x2070, 0xB9,   0xB2,   0xB3,   0x2074, 0x2075,
+	0x2076, 0x2077, 0x2078, 0x2079, 'E',    0x2081, 0x2082, 0x2083,
+	0x2084, 0x23E8, 0x209C, '"',    0x207B, '.',    '0',    '1',
+	'2',    '3',    '4',    '5',    '6',    '7',    '8',    '9',
+	'E',    0x2B3,  0xB0,   0x3B8,  'R',    'T',    0x2E3,  0x2B8,
+	0x780305, 0x790305, 0x3A3, 0x3C3, 0x3C0, 'A',   'B',    'C',
+	'D',    'E',    'F',    'G',    'H',    'I',    'J',    'K',
+	'L',    'M',    'N',    'O',    'P',    'Q',    'R',    'S',
+	'T',    'U',    'V',    'W',    'X',    'Y',    'Z',    0x3B8,
+	'a',    'b',    'c',    'd',    'e',    'f',    'g',    'h',
+	'i',    'l',    'm',    'n',    'o',    'p',    'q',    'r',
+	's',    't',    'u',    'v',    'w',    'x',    'y',    0xD7,
+	0x2588, 0x219120DE, 0x4120DE, '_', 0x21910332, 0x410332, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF,
+	UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF, UNDEF };
+
+static const unsigned long * getmap(int model)
+{
+	switch (model) {
+	case TILEM_CALC_TI73:
+		return ti73_charset;
+	case TILEM_CALC_TI81:
+		return ti81chars;
+	case TILEM_CALC_TI82:
+		return ti82_charset;
+	case TILEM_CALC_TI76:
+	case TILEM_CALC_TI83:
+		return ti83_charset;
+	case TILEM_CALC_TI83P:
+	case TILEM_CALC_TI83P_SE:
+	case TILEM_CALC_TI84P:
+	case TILEM_CALC_TI84P_SE:
+	case TILEM_CALC_TI84P_NSPIRE:
+		return ti83p_charset;
+	case TILEM_CALC_TI85:
+		return ti85_charset;
+	case TILEM_CALC_TI86:
+		return ti86_charset;
+	default:
+		return ti83p_charset;
+	}
+}
+
+/* Convert a byte value from the calculator large-font character set
+   into a printable UTF-8 string. */
+char *ti_to_unicode(int model, unsigned int value)
+{
+	const unsigned long *map = getmap(model);
+	unsigned long v;
+	char buf[12];
+	int n;
+
+	v = map[value];
+	if (v == 0)
+		v = 0x2400;	/* SYMBOL FOR NULL */
+	else if (v == '\n')
+		v = 0x240A;	/* SYMBOL FOR LINE FEED */
+	else if (v == ' ')
+		v = 0x2423;	/* OPEN BOX */
+
+	/* in the ticonv character tables, non-BMP characters are
+	   represented by a surrogate pair */
+	if ((v & 0xfc00fc00) == 0xd800dc00) {
+		v = (((v & 0x3ff0000) >> 6) | (v & 0x3ff)) + 0x10000;
+		n = g_unichar_to_utf8(v, buf);
+	}
+	else if (v & 0xffff0000) {
+		n = g_unichar_to_utf8(v >> 16, buf);
+		n += g_unichar_to_utf8(v & 0xffff, buf + n);
+	}
+	else {
+		n = g_unichar_to_utf8(v, buf);
+	}
+
+	return g_strndup(buf, n);
+}
diff --git a/tool/tilem-src/gui/charmap.h b/tool/tilem-src/gui/charmap.h
new file mode 100644
index 0000000..c383a94
--- /dev/null
+++ b/tool/tilem-src/gui/charmap.h
@@ -0,0 +1,22 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Convert a byte value from the calculator large-font character set
+   into a printable UTF-8 string. */
+char *ti_to_unicode(int model, unsigned int value);
diff --git a/tool/tilem-src/gui/config.c b/tool/tilem-src/gui/config.c
new file mode 100644
index 0000000..4a00827
--- /dev/null
+++ b/tool/tilem-src/gui/config.c
@@ -0,0 +1,338 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "files.h"
+#include "msgbox.h"
+
+#ifndef CONFIG_FILE
+#define CONFIG_FILE "config.ini"
+#endif
+
+#define MAX_RECENT_FILES 10
+
+/* Store a filename in a GKeyFile.  Any control characters or
+   non-UTF-8 filenames are stored in octal.  Note that
+   g_key_file_set/get_string() can't be used because they only allow
+   UTF-8 */
+static void key_file_set_filename(GKeyFile *gkf, const char *group,
+                                  const char *key, const char *value)
+{
+	char *escaped;
+	const char *p;
+	char *q;
+	gunichar uc;
+	int b;
+
+	q = escaped = g_new(char, strlen(value) * 4 + 1);
+
+	while (*value != 0) {
+		uc = g_utf8_get_char_validated(value, -1);
+		if (uc < 0x20 || uc == 0x7F || !g_unichar_validate(uc)) {
+			b = (unsigned char) *value;
+			q[0] = '\\';
+			q[1] = '0' + (b >> 6);
+			q[2] = '0' + ((b >> 3) & 7);
+			q[3] = '0' + (b & 7);
+			q += 4;
+			value++;
+		}
+		else if (uc == '\\') {
+			q[0] = q[1] = '\\';
+			q += 2;
+			value++;
+		}
+		else {
+			p = g_utf8_next_char(value);
+			while (value != p)
+				*q++ = *value++;
+		}
+	}
+
+	*q = 0;
+
+	g_key_file_set_value(gkf, group, key, escaped);
+	g_free(escaped);
+}
+
+/* Retrieve a filename from a GKeyFile. */
+static char *key_file_get_filename(GKeyFile *gkf, const char *group,
+                                   const char *key, GError **error)
+{
+	char *value, *unescaped;
+
+	value = g_key_file_get_value(gkf, group, key, error);
+	if (!value)
+		return NULL;
+
+	unescaped = g_strcompress(value);
+	g_free(value);
+	return unescaped;
+}
+
+/* Load and parse the configuration file. */
+static GKeyFile *load_config(gboolean writable)
+{
+	static gboolean warned;
+	GKeyFile *gkf;
+	GKeyFileFlags flags;
+	char *cfname, *dname;
+	GError *err = NULL;
+
+	gkf = g_key_file_new();
+
+	cfname = get_shared_file_path(CONFIG_FILE, NULL);
+	if (!cfname)
+		return gkf;
+
+	if (writable)
+		flags = (G_KEY_FILE_KEEP_COMMENTS
+		         | G_KEY_FILE_KEEP_TRANSLATIONS);
+	else
+		flags = 0;
+
+	if (!g_key_file_load_from_file(gkf, cfname, flags, &err)) {
+		/* don't bother the user more than once */
+		if (!warned) {
+			dname = g_filename_display_name(cfname);
+			messagebox02(NULL, GTK_MESSAGE_ERROR,
+			             "Unable to read settings",
+			             "An error occurred while reading %s: %s",
+			             dname, err->message);
+			g_free(dname);
+			warned = TRUE;
+		}
+		g_error_free(err);
+	}
+
+	g_free(cfname);
+	return gkf;
+}
+
+/* Save the configuration file. */
+static void save_config(GKeyFile *gkf)
+{
+	static gboolean warned;
+	char *cfname, *dname;
+	char *data;
+	gsize length;
+	GError *err = NULL;
+
+	data = g_key_file_to_data(gkf, &length, NULL);
+	
+	cfname = get_config_file_path(CONFIG_FILE, NULL);
+
+	if (!g_file_set_contents(cfname, data, length, &err)) {
+		/* don't bother the user more than once */
+		if (!warned) {
+			dname = g_filename_display_name(cfname);
+			messagebox02(NULL, GTK_MESSAGE_ERROR,
+			             "Unable to save settings",
+			             "An error occurred while writing %s: %s",
+			             dname, err->message);
+			g_free(dname);
+			warned = TRUE;
+		}
+		g_error_free(err);
+	}
+
+	g_free(cfname);
+	g_free(data);
+}
+
+/* Retrieve settings from the configuration file. */
+void tilem_config_get(const char *group, const char *option, ...)
+{
+	va_list ap;
+	GKeyFile *gkf;
+	const char *type, *defvalue;
+	GError *err = NULL;
+	char *key, *p;
+	char **strp;
+	int *intp;
+	double *dblp;
+	GdkColor *colorp;
+
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(option != NULL);
+
+	gkf = load_config(FALSE);
+
+	va_start(ap, option);
+	while (option != NULL) {
+		type = strrchr(option, '/');
+		if (type == NULL || type[1] == 0
+		    || (type[2] != 0 && type[2] != '=')) {
+			g_critical("invalid argument\n");
+			break;
+		}
+
+		if (type[2] == '=')
+			defvalue = &type[3];
+		else
+			defvalue = NULL;
+
+		key = g_strndup(option, type - option);
+
+		if (type[1] == 'f') {
+			strp = va_arg(ap, char **);
+			*strp = key_file_get_filename(gkf, group, key, &err);
+			if (err && defvalue)
+				*strp = g_strdup(defvalue);
+		}
+		else if (type[1] == 's') {
+			strp = va_arg(ap, char **);
+			*strp = g_key_file_get_string(gkf, group, key, &err);
+			if (err && defvalue)
+				*strp = g_strdup(defvalue);
+		}
+		else if (type[1] == 'i') {
+			intp = va_arg(ap, int *);
+			*intp = g_key_file_get_integer(gkf, group, key, &err);
+			if (err && defvalue)
+				*intp = g_ascii_strtoll(defvalue, NULL, 10);
+		}
+		else if (type[1] == 'r') {
+			dblp = va_arg(ap, double *);
+			*dblp = g_key_file_get_double(gkf, group, key, &err);
+			if (err && defvalue)
+				*dblp = g_ascii_strtod(defvalue, NULL);
+		}
+		else if (type[1] == 'b') {
+			intp = va_arg(ap, int *);
+			*intp = g_key_file_get_boolean(gkf, group, key, &err);
+			if (err && defvalue)
+				*intp = g_ascii_strtoll(defvalue, NULL, 10);
+		}
+		else if (type[1] == 'c') {
+			colorp = va_arg(ap, GdkColor *);
+			p = g_key_file_get_string(gkf, group, key, &err);
+			if (p == NULL || !gdk_color_parse(p, colorp)) {
+				if (defvalue) {
+					gdk_color_parse(defvalue, colorp);
+				}
+				else {
+					colorp->red = 0;
+					colorp->green = 0;
+					colorp->blue = 0;
+				}
+			}
+			g_free(p);
+		}
+		else {
+			g_critical("invalid argument\n");
+			g_free(key);
+			break;
+		}
+
+		g_clear_error(&err);
+		g_free(key);
+		option = va_arg(ap, const char *);
+	}
+	va_end(ap);
+
+	g_key_file_free(gkf);
+}
+
+/* Save settings to the configuration file. */
+void tilem_config_set(const char *group, const char *option, ...)
+{
+	va_list ap;
+	GKeyFile *gkf;
+	const char *type;
+	char *key;
+	const char *strv;
+	int intv;
+	double dblv;
+	const GdkColor *colorv;
+	char *p;
+
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(option != NULL);
+
+	gkf = load_config(TRUE);
+
+	va_start(ap, option);
+	while (option != NULL) {
+		type = strrchr(option, '/');
+		if (type == NULL || type[1] == 0 || type[2] != 0) {
+			g_critical("invalid argument\n");
+			break;
+		}
+
+		key = g_strndup(option, type - option);
+
+		if (type[1] == 'f') {
+			strv = va_arg(ap, const char *);
+			key_file_set_filename(gkf, group, key, strv);
+		}
+		else if (type[1] == 's') {
+			strv = va_arg(ap, const char *);
+			g_key_file_set_string(gkf, group, key, strv);
+		}
+		else if (type[1] == 'i') {
+			intv = va_arg(ap, int);
+			g_key_file_set_integer(gkf, group, key, intv);
+		}
+		else if (type[1] == 'r') {
+			dblv = va_arg(ap, double);
+			g_key_file_set_double(gkf, group, key, dblv);
+		}
+		else if (type[1] == 'b') {
+			intv = va_arg(ap, int);
+			g_key_file_set_boolean(gkf, group, key, !!intv);
+		}
+		else if (type[1] == 'c') {
+			colorv = va_arg(ap, const GdkColor *);
+			p = g_strdup_printf("#%02x%02x%02x",
+			                    colorv->red >> 8,
+			                    colorv->green >> 8,
+			                    colorv->blue >> 8);
+			g_key_file_set_string(gkf, group, key, p);
+			g_free(p);
+		}
+		else {
+			g_critical("invalid argument\n");
+			g_free(key);
+			break;
+		}
+
+		g_free(key);
+
+		option = va_arg(ap, const char *);
+	}
+	va_end(ap);
+
+	save_config(gkf);
+	g_key_file_free(gkf);
+}
+
diff --git a/tool/tilem-src/gui/debugger.c b/tool/tilem-src/gui/debugger.c
new file mode 100644
index 0000000..aac8708
--- /dev/null
+++ b/tool/tilem-src/gui/debugger.c
@@ -0,0 +1,1420 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include <tilemdb.h>
+
+#include "gui.h"
+#include "disasmview.h"
+#include "files.h"
+#include "msgbox.h"
+#include "fixedtreeview.h"
+#include "memmodel.h"
+
+/* Stack list */
+enum
+{
+	COL_OFFSET_STK = 0,
+	COL_VALUE_STK,
+  	NUM_COLS_STK
+};
+
+/* Indices in reg_entries */
+enum {
+	R_AF, R_BC, R_DE, R_HL, R_IX, R_SP,
+	R_AF2, R_BC2, R_DE2, R_HL2, R_IY, R_PC,
+	R_IM, R_I,
+	NUM_REGS
+};
+
+/* Labels for the entries */
+static const char * const reg_labels[] = {
+	"A_F:", "B_C:", "D_E:", "H_L:", "I_X:", "SP:",
+	"AF':", "BC':", "DE':", "HL':", "I_Y:", "PC:",
+	"IM:", "I:"
+};
+
+/* Labels for the flag buttons */
+static const char flag_labels[][2] = {
+	"C", "N", "P", "X", "H", "Y", "Z", "S"
+};
+
+/* Read a word */
+static dword read_mem_word(TilemCalc *calc, dword addr)
+{
+	dword phys, v;
+
+	phys = (*calc->hw.mem_ltop)(calc, addr & 0xffff);
+	v = calc->mem[phys];
+	phys = (*calc->hw.mem_ltop)(calc, (addr + 1) & 0xffff);
+	v += calc->mem[phys] << 8;
+	return v;
+}
+
+/* Determine model name for the purpose of looking up default system
+   symbols */
+static const char *get_sys_name(const TilemCalc *calc)
+{
+	g_return_val_if_fail(calc != NULL, NULL);
+
+	switch (calc->hw.model_id) {
+	case TILEM_CALC_TI83:
+	case TILEM_CALC_TI76:
+		return "ti83";
+
+	case TILEM_CALC_TI83P:
+	case TILEM_CALC_TI83P_SE:
+	case TILEM_CALC_TI84P:
+	case TILEM_CALC_TI84P_SE:
+	case TILEM_CALC_TI84P_NSPIRE:
+		return "ti83p";
+
+	default:
+		return calc->hw.name;
+	}
+}
+
+/* Load default system symbols */
+static void load_default_symbols(TilemDebugger *dbg)
+{
+	char *base, *path, *dname;
+	const char *errstr;
+	FILE *symfile;
+
+	base = g_strdup_printf("%s.sym", get_sys_name(dbg->emu->calc));
+	path = get_shared_file_path("symbols", base, NULL);
+	g_free(base);
+	if (!path)
+		return;
+
+	symfile = g_fopen(path, "rb");
+	if (!symfile) {
+		errstr = g_strerror(errno);
+		dname = g_filename_display_name(path);
+		messagebox02(NULL, GTK_MESSAGE_ERROR,
+		             "Unable to read symbols",
+		             "An error occurred while reading %s: %s",
+		             dname, errstr);
+		g_free(dname);
+		g_free(path);
+		return;
+	}
+
+	tilem_disasm_read_symbol_file(dbg->dasm, symfile);
+
+	fclose(symfile);
+	g_free(path);
+}
+
+/* Cancel temporary breakpoint */
+static void cancel_step_bp(TilemDebugger *dbg)
+{
+	if (!dbg->step_bp)
+		return;
+
+	g_return_if_fail(dbg->emu->calc != NULL);
+	tilem_calc_emulator_lock(dbg->emu);
+	tilem_z80_remove_breakpoint(dbg->emu->calc, dbg->step_bp);
+	tilem_calc_emulator_unlock(dbg->emu);
+	dbg->step_bp = 0;
+}
+
+/* Actions */
+
+/* Run (but leave debugger window open) */
+static void action_run(G_GNUC_UNUSED GtkAction *a, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	cancel_step_bp(dbg);
+	tilem_calc_emulator_run(dbg->emu);
+	tilem_debugger_refresh(dbg, TRUE);
+}
+
+/* Pause */
+static void action_pause(G_GNUC_UNUSED GtkAction *a, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	tilem_debugger_show(dbg);
+	cancel_step_bp(dbg);
+}
+
+/* Stepping */
+
+static int bptest_step(TilemCalc *calc, dword op, G_GNUC_UNUSED void *data)
+{
+	/* Single step condition: if calculator is halted, wait until
+	   an interrupt occurs; otherwise, stop after any
+	   instruction. */
+
+	if (op != 0x76 && (op & ~0x2000) != 0xdd76)
+		/* not a HALT instruction */
+		return 1;
+	else if (calc->z80.interrupts != 0 && calc->z80.r.iff1)
+		return 1;
+	else
+		return 0;
+}
+
+static int bptest_step_over(TilemCalc *calc, dword op, void *data)
+{
+	TilemDebugger *dbg = data;
+	dword destaddr;
+
+	/* Step-over condition: behavior depends on what instruction
+	   is executed.
+
+	   For most instructions, stop when we reach the "next line"
+	   as determined by disassembly.  This means skipping over
+	   CALLs, RSTs, HALTs, and macros.
+
+	   For jump and return instructions, stop at the current PC,
+	   whatever that is.
+
+	   In both cases, wait until we actually reach the target PC,
+	   rather than halting immediately; the effect of this is that
+	   if an interrupt has occurred, we also "step over" the
+	   ISR. */
+
+	if ((op & ~0x20ff) == 0xdd00)
+		op &= 0xff;
+
+	if (op == 0xc3 /* JP */
+	    || op == 0xc9 /* RET */
+	    || op == 0xe9 /* JP HL/IX/IY */
+	    || (op & ~0x38) == 0 /* JR, DJNZ, NOP, or EX AF,AF' */
+	    || (op & ~0x38) == 0xc2 /* conditional JP */
+	    || (op & ~0x38) == 0xc0 /* conditional RET */
+	    || (op & ~0x38) == 0xed45) /* RETI/RETN */
+		destaddr = calc->z80.r.pc.d;
+	else
+		destaddr = dbg->step_next_addr;
+
+	destaddr &= 0xffff;
+
+	/* Delete this breakpoint, and replace it with a simple exec
+	   breakpoint at the target address. */
+
+	tilem_z80_remove_breakpoint(calc, dbg->step_bp);
+	dbg->step_bp = tilem_z80_add_breakpoint(calc, TILEM_BREAK_MEM_EXEC,
+	                                        destaddr, destaddr, 0xffff,
+	                                        NULL, NULL);
+	return 0;
+}
+
+static int bptest_finish(TilemCalc *calc, dword op, void *data)
+{
+	dword exitsp = TILEM_PTR_TO_DWORD(data);
+	byte f;
+
+	/* Finish condition: wait until stack pointer is greater than
+	   a certain value, and we execute a return instruction.  JP
+	   HL/IX/IY are also considered return instructions. */
+
+	if (calc->z80.r.sp.w.l <= exitsp)
+		return 0;
+
+	if ((op & ~0x20ff) == 0xdd00)
+		op &= 0xff;
+
+	f = calc->z80.r.af.b.l;
+
+	switch (op) {
+	case 0xc9: /* RET */
+	case 0xe9: /* JP HL/IX/IY */
+	case 0xed45: /* RETN */
+	case 0xed4d: /* RETI */
+	case 0xed55:
+	case 0xed5d:
+	case 0xed65:
+	case 0xed6d:
+	case 0xed75:
+	case 0xed7d:
+		return 1;
+
+	/* conditionals: check if condition was true */
+	case 0xc0: return !(f & 0x40);
+	case 0xc8: return (f & 0x40);
+	case 0xd0: return !(f & 0x01);
+	case 0xd8: return (f & 0x01);
+	case 0xe0: return !(f & 0x04);
+	case 0xe8: return (f & 0x04);
+	case 0xf0: return !(f & 0x80);
+	case 0xf8: return (f & 0x80);
+
+	default:
+		return 0;
+	}
+}
+
+static gboolean post_resume_refresh(gpointer data)
+{
+	TilemDebugger *dbg = data;
+	tilem_debugger_refresh(dbg, FALSE);
+	return FALSE;
+}
+
+static void run_with_step_condition(TilemDebugger *dbg,
+                                    TilemZ80BreakpointFunc func,
+                                    void *data)
+{
+	tilem_calc_emulator_lock(dbg->emu);
+	dbg->step_bp = tilem_z80_add_breakpoint(dbg->emu->calc,
+	                                        TILEM_BREAK_EXECUTE, 0, 0, 0,
+	                                        func, data);
+	tilem_calc_emulator_unlock(dbg->emu);
+	tilem_calc_emulator_run(dbg->emu);
+	/* Don't refresh right away, to avoid flickering */
+	g_timeout_add(10, &post_resume_refresh, dbg);
+}
+
+/* Execute one instruction */
+static void action_step(G_GNUC_UNUSED GtkAction *a, gpointer data)
+{
+	TilemDebugger *dbg = data;
+
+	if (!dbg->emu->paused)
+		return;
+
+	g_return_if_fail(dbg->emu->calc != NULL);
+
+	cancel_step_bp(dbg);
+
+	run_with_step_condition(dbg, &bptest_step, NULL);
+}
+
+/* Skip over an instruction */
+static void action_step_over(G_GNUC_UNUSED GtkAction *a, gpointer data)
+{
+	TilemDebugger *dbg = data;
+
+	if (!dbg->emu->paused)
+		return;
+
+	g_return_if_fail(dbg->emu->calc != NULL);
+
+	cancel_step_bp(dbg);
+
+	tilem_calc_emulator_lock(dbg->emu);
+	tilem_disasm_disassemble(dbg->dasm, dbg->emu->calc, 0,
+	                         dbg->emu->calc->z80.r.pc.w.l,
+	                         &dbg->step_next_addr,
+	                         NULL, 0);
+	tilem_calc_emulator_unlock(dbg->emu);
+
+	run_with_step_condition(dbg, &bptest_step_over, dbg);
+}
+
+/* Run until current subroutine finishes */
+static void action_finish(G_GNUC_UNUSED GtkAction *a, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	dword sp;
+
+	if (!dbg->emu->paused)
+		return;
+
+	g_return_if_fail(dbg->emu->calc != NULL);
+
+	cancel_step_bp(dbg);
+
+	tilem_calc_emulator_lock(dbg->emu);
+	sp = dbg->emu->calc->z80.r.sp.w.l;
+	tilem_calc_emulator_unlock(dbg->emu);
+
+	run_with_step_condition(dbg, &bptest_finish,
+	                        TILEM_DWORD_TO_PTR(sp));
+}
+
+/* Toggle breakpoint at selected line */
+static void action_toggle_breakpoint(G_GNUC_UNUSED GtkAction *a, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	GtkWidget *focus;
+
+	focus = gtk_window_get_focus(GTK_WINDOW(dbg->window));
+	if (TILEM_IS_DISASM_VIEW(focus))
+		tilem_disasm_view_toggle_breakpoint(TILEM_DISASM_VIEW(focus));
+}
+
+/* Edit breakpoints */
+static void action_edit_breakpoints(G_GNUC_UNUSED GtkAction *a, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	tilem_debugger_edit_breakpoints(dbg);
+}
+
+/* Close debugger window */
+static void action_close(G_GNUC_UNUSED GtkAction *a, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	tilem_debugger_hide(dbg);
+}
+
+static void keypad_dlg_response(G_GNUC_UNUSED GtkDialog *dlg,
+                                G_GNUC_UNUSED int response,
+                                gpointer data)
+{
+	gtk_toggle_action_set_active(data, FALSE);
+}
+
+/* Show/hide keypad dialog */
+static void action_view_keypad(GtkToggleAction *action, gpointer data)
+{
+	TilemDebugger *dbg = data;
+
+	if (!dbg->keypad_dialog) {
+		dbg->keypad_dialog = tilem_keypad_dialog_new(dbg);
+		g_signal_connect(dbg->keypad_dialog->window, "response",
+		                 G_CALLBACK(keypad_dlg_response), action);
+	}
+
+	if (gtk_toggle_action_get_active(action))
+		gtk_window_present(GTK_WINDOW(dbg->keypad_dialog->window));
+	else
+		gtk_widget_hide(dbg->keypad_dialog->window);
+}
+
+/* Set memory addressing mode */
+static void action_mem_mode(GtkRadioAction *action,
+                            G_GNUC_UNUSED GtkRadioAction *current,
+                            gpointer data)
+{
+	TilemDebugger *dbg = data;
+
+	dbg->mem_logical = gtk_radio_action_get_current_value(action);
+
+	tilem_disasm_view_set_logical(TILEM_DISASM_VIEW(dbg->disasm_view),
+	                              dbg->mem_logical);
+
+	tilem_debugger_mem_view_configure(dbg->mem_view,
+	                                  dbg->emu, dbg->mem_rowsize,
+	                                  dbg->mem_start, dbg->mem_logical);
+
+	tilem_config_set("debugger",
+	                 "mem_logical/b", dbg->mem_logical,
+	                 NULL);
+}
+
+/* Prompt for an address to view */
+static void action_go_to_address(G_GNUC_UNUSED GtkAction *action, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	TilemDisasmView *dv = TILEM_DISASM_VIEW(dbg->disasm_view);
+	dword addr;
+	gboolean addr_set, logical;
+
+	addr_set = tilem_disasm_view_get_cursor(dv, &addr, &logical);
+
+	if (!tilem_prompt_address(dbg, GTK_WINDOW(dbg->window),
+	                          "Go to Address", "Address:",
+	                          &addr, !logical, addr_set))
+		return;
+
+	tilem_disasm_view_go_to_address(dv, addr, logical);
+	gtk_widget_grab_focus(dbg->disasm_view);
+}
+
+/* Jump to address at given stack index */
+static void go_to_stack_pos(TilemDebugger *dbg, int pos)
+{
+	dword addr;
+	GtkTreePath *path;
+	GtkTreeSelection *sel;
+
+	dbg->stack_index = pos;
+
+	tilem_calc_emulator_lock(dbg->emu);
+	if (pos < 0)
+		addr = dbg->emu->calc->z80.r.pc.d;
+	else
+		addr = read_mem_word(dbg->emu->calc,
+		                     dbg->emu->calc->z80.r.sp.d + 2 * pos);
+	tilem_calc_emulator_unlock(dbg->emu);
+
+	tilem_disasm_view_go_to_address(TILEM_DISASM_VIEW(dbg->disasm_view),
+	                                addr, TRUE);
+	gtk_widget_grab_focus(dbg->disasm_view);
+
+	if (pos >= 0) {
+		path = gtk_tree_path_new_from_indices(pos, -1);
+		gtk_tree_view_set_cursor(GTK_TREE_VIEW(dbg->stack_view),
+		                         path, NULL, FALSE);
+		gtk_tree_path_free(path);
+	}
+	else {
+		sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(dbg->stack_view));
+		gtk_tree_selection_unselect_all(sel);
+	}
+
+	gtk_action_set_sensitive(dbg->prev_stack_action, (pos >= 0));
+}
+
+/* Jump to current PC */
+static void action_go_to_pc(G_GNUC_UNUSED GtkAction *action, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	go_to_stack_pos(dbg, -1);
+}
+
+/* Jump to previous stack entry */
+static void action_prev_stack_entry(G_GNUC_UNUSED GtkAction *action,
+                                        gpointer data)
+{
+	TilemDebugger *dbg = data;
+	if (dbg->stack_index >= 0)
+		go_to_stack_pos(dbg, dbg->stack_index - 1);
+}
+
+/* Jump to next stack entry */
+static void action_next_stack_entry(G_GNUC_UNUSED GtkAction *action,
+                                    gpointer data)
+{
+	TilemDebugger *dbg = data;
+	go_to_stack_pos(dbg, dbg->stack_index + 1);
+}
+
+
+static const GtkActionEntry run_action_ents[] =
+	{{ "pause", GTK_STOCK_MEDIA_PAUSE, "_Pause", "Escape",
+	   "Pause emulation", G_CALLBACK(action_pause) }};
+
+static const GtkActionEntry paused_action_ents[] =
+	{{ "run", GTK_STOCK_MEDIA_PLAY, "_Run", "F5",
+	   "Resume emulation", G_CALLBACK(action_run) },
+	 { "step", "tilem-db-step", "_Step", "F7",
+	   "Execute one instruction", G_CALLBACK(action_step) },
+	 { "step-over", "tilem-db-step-over", "Step _Over", "F8",
+	   "Run to the next line (skipping over subroutines)",
+	   G_CALLBACK(action_step_over) },
+	 { "finish", "tilem-db-finish", "_Finish Subroutine", "F9",
+	   "Run to end of the current subroutine", G_CALLBACK(action_finish) },
+	 { "toggle-breakpoint", NULL, "Toggle Breakpoint", "F2",
+	   "Enable or disable breakpoint at the selected address",
+	   G_CALLBACK(action_toggle_breakpoint) },
+	 { "edit-breakpoints", NULL, "_Breakpoints", "<control>B",
+	   "Add, remove, or modify breakpoints",
+	   G_CALLBACK(action_edit_breakpoints) },
+	 { "go-to-address", GTK_STOCK_JUMP_TO, "_Address...", "<control>L",
+	   "Jump to an address",
+	   G_CALLBACK(action_go_to_address) },
+	 { "go-to-pc", NULL, "Current P_C", "<alt>Home",
+	   "Jump to the current program counter",
+	   G_CALLBACK(action_go_to_pc) },
+	 { "prev-stack-entry", GTK_STOCK_GO_UP, "_Previous Stack Entry", "<alt>Page_Up",
+	   "Jump to the previous address in the stack",
+	   G_CALLBACK(action_prev_stack_entry) },
+	 { "next-stack-entry", GTK_STOCK_GO_DOWN, "_Next Stack Entry", "<alt>Page_Down",
+	   "Jump to the next address in the stack",
+	   G_CALLBACK(action_next_stack_entry) }};
+
+static const GtkRadioActionEntry mem_mode_ents[] =
+	{{ "view-logical", 0, "_Logical Addresses", 0,
+	   "Show contents of the current Z80 address space", 1 },
+	 { "view-absolute", 0, "_Absolute Addresses", 0,
+	   "Show all memory contents", 0 }};
+
+static const GtkActionEntry misc_action_ents[] =
+	{{ "debug-menu", 0, "_Debug", 0, 0, 0 },
+	 { "view-menu", 0, "_View", 0, 0, 0 },
+	 { "go-menu", 0, "_Go", 0, 0, 0 },
+	 { "close", GTK_STOCK_CLOSE, 0, 0,
+	   "Close the debugger", G_CALLBACK(action_close) }};
+
+static const GtkToggleActionEntry misc_toggle_ents[] =
+	{{ "view-keypad", 0, "_Keypad", 0,
+	   "Show the calculator keypad state",
+	   G_CALLBACK(action_view_keypad), FALSE }};
+
+/* Callbacks */
+
+/* Register edited */
+static void reg_edited(GtkEntry *ent, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	TilemCalc *calc;
+	const char *text;
+	char *end;
+	dword value;
+	int i;
+
+	if (dbg->refreshing)
+		return;
+
+	calc = dbg->emu->calc;
+	g_return_if_fail(calc != NULL);
+
+	text = gtk_entry_get_text(ent);
+	value = strtol(text, &end, 16);
+
+	for (i = 0; i < NUM_REGS; i++)
+		if (ent == (GtkEntry*) dbg->reg_entries[i])
+			break;
+
+	tilem_calc_emulator_lock(dbg->emu);
+	switch (i) {
+	case R_AF: calc->z80.r.af.d = value; break;
+	case R_BC: calc->z80.r.bc.d = value; break;
+	case R_DE: calc->z80.r.de.d = value; break;
+	case R_HL: calc->z80.r.hl.d = value; break;
+	case R_AF2: calc->z80.r.af2.d = value; break;
+	case R_BC2: calc->z80.r.bc2.d = value; break;
+	case R_DE2: calc->z80.r.de2.d = value; break;
+	case R_HL2: calc->z80.r.hl2.d = value; break;
+	case R_SP: calc->z80.r.sp.d = value; break;
+	case R_PC: calc->z80.r.pc.d = value; break;
+	case R_IX: calc->z80.r.ix.d = value; break;
+	case R_IY: calc->z80.r.iy.d = value; break;
+	case R_I: calc->z80.r.ir.b.h = value; break;
+	}
+	tilem_calc_emulator_unlock(dbg->emu);
+
+	/* Set the value of the register immediately, but don't
+	   refresh the display: refreshing the registers themselves
+	   while user is trying to edit them would just be obnoxious,
+	   and refreshing stack and disassembly would be at least
+	   distracting.  Instead, we'll refresh only when focus
+	   changes. */
+
+	dbg->delayed_refresh = TRUE;
+}
+
+/* Flag button toggled */
+static void flag_edited(GtkToggleButton *btn, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	TilemCalc *calc;
+	int i;
+
+	if (dbg->refreshing)
+		return;
+
+	calc = dbg->emu->calc;
+	g_return_if_fail(calc != NULL);
+
+	for (i = 0; i < 8; i++)
+		if (btn == (GtkToggleButton*) dbg->flag_buttons[i])
+			break;
+
+	tilem_calc_emulator_lock(dbg->emu);
+	if (gtk_toggle_button_get_active(btn))
+		calc->z80.r.af.d |= (1 << i);
+	else
+		calc->z80.r.af.d &= ~(1 << i);
+	tilem_calc_emulator_unlock(dbg->emu);
+
+	/* refresh AF */
+	tilem_debugger_refresh(dbg, FALSE);
+}
+
+/* IM edited */
+static void im_edited(GtkEntry *ent, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	TilemCalc *calc;
+	const char *text;
+	char *end;
+	int value;
+
+	if (dbg->refreshing)
+		return;
+
+	calc = dbg->emu->calc;
+	g_return_if_fail(calc != NULL);
+
+	text = gtk_entry_get_text(ent);
+	value = strtol(text, &end, 0);
+
+	tilem_calc_emulator_lock(dbg->emu);
+	if (value >= 0 && value <= 2)
+		calc->z80.r.im = value;
+	tilem_calc_emulator_unlock(dbg->emu);
+	/* no need to refresh */
+}
+
+/* IFF button toggled */
+static void iff_edited(GtkToggleButton *btn, gpointer data)
+{
+	TilemDebugger *dbg = data;
+	TilemCalc *calc;
+
+	if (dbg->refreshing)
+		return;
+
+	calc = dbg->emu->calc;
+	g_return_if_fail(calc != NULL);
+
+	tilem_calc_emulator_lock(dbg->emu);
+	if (gtk_toggle_button_get_active(btn))
+		calc->z80.r.iff1 = calc->z80.r.iff2 = 1;
+	else
+		calc->z80.r.iff1 = calc->z80.r.iff2 = 0;
+	tilem_calc_emulator_unlock(dbg->emu);
+	/* no need to refresh */
+}
+
+/* Main window's focus widget changed */
+static void focus_changed(G_GNUC_UNUSED GtkWindow *win,
+                          G_GNUC_UNUSED GtkWidget *widget,
+                          gpointer data)
+{
+	TilemDebugger *dbg = data;
+
+	/* delayed refresh - see reg_edited() above */
+	if (dbg->delayed_refresh)
+		tilem_debugger_refresh(dbg, FALSE);
+}
+
+/* Main window received a "delete" message */
+static gboolean delete_win(G_GNUC_UNUSED GtkWidget *w,
+                           G_GNUC_UNUSED GdkEvent *ev,
+                           gpointer data)
+{
+	TilemDebugger *dbg = data;
+	tilem_debugger_hide(dbg);
+	return TRUE;
+}
+
+/* Create table of widgets for editing registers */
+static GtkWidget *create_registers(TilemDebugger *dbg)
+{
+	GtkWidget *vbox, *tbl, *lbl, *hbox, *ent, *btn;
+	int i;
+
+	vbox = gtk_vbox_new(FALSE, 6);
+
+	tbl = gtk_table_new(6, 4, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
+	gtk_table_set_col_spacings(GTK_TABLE(tbl), 6);
+	gtk_table_set_col_spacing(GTK_TABLE(tbl), 1, 12);
+
+	for (i = 0; i < 12; i++) {
+		lbl = gtk_label_new_with_mnemonic(reg_labels[i]);
+		gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+		gtk_table_attach(GTK_TABLE(tbl), lbl,
+		                 2 * (i / 6), 2 * (i / 6) + 1,
+		                 (i % 6), (i % 6) + 1,
+		                 GTK_FILL, GTK_FILL, 0, 0);
+
+		dbg->reg_entries[i] = ent = gtk_entry_new();
+		gtk_entry_set_width_chars(GTK_ENTRY(ent), 5);
+		g_signal_connect(ent, "changed", G_CALLBACK(reg_edited), dbg);
+		gtk_table_attach(GTK_TABLE(tbl), ent,
+		                 2 * (i / 6) + 1, 2 * (i / 6) + 2,
+		                 (i % 6), (i % 6) + 1,
+		                 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+		gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ent);
+	}
+
+	gtk_box_pack_start(GTK_BOX(vbox), tbl, FALSE, FALSE, 0);
+
+	hbox = gtk_hbox_new(TRUE, 0);
+
+	for (i = 7; i >= 0; i--) {
+		btn = gtk_toggle_button_new_with_label(flag_labels[i]);
+		dbg->flag_buttons[i] = btn;
+		g_signal_connect(btn, "toggled", G_CALLBACK(flag_edited), dbg);
+		gtk_box_pack_start(GTK_BOX(hbox), btn, TRUE, TRUE, 0);
+	}
+
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+	hbox = gtk_hbox_new(FALSE, 6);
+
+	for (i = 12; i < 14; i++) {
+		lbl = gtk_label_new(reg_labels[i]);
+		gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 0);
+
+		dbg->reg_entries[i] = ent = gtk_entry_new();
+		gtk_box_pack_start(GTK_BOX(hbox), ent, TRUE, TRUE, 0);
+	}
+
+	g_signal_connect(dbg->reg_entries[R_I], "changed",
+	                 G_CALLBACK(reg_edited), dbg);
+	g_signal_connect(dbg->reg_entries[R_IM], "changed",
+	                 G_CALLBACK(im_edited), dbg);
+
+	gtk_entry_set_width_chars(GTK_ENTRY(dbg->reg_entries[R_IM]), 2);
+	gtk_entry_set_width_chars(GTK_ENTRY(dbg->reg_entries[R_I]), 3);
+
+	dbg->iff_checkbox = btn = gtk_check_button_new_with_label("EI");
+	g_signal_connect(btn, "toggled", G_CALLBACK(iff_edited), dbg);
+	gtk_box_pack_start(GTK_BOX(hbox), btn, TRUE, TRUE, 0);
+
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+	return vbox;
+}
+
+/* Create the GtkTreeView to show the stack */
+static GtkWidget *create_stack_view()
+{
+	GtkCellRenderer   *renderer;
+	GtkWidget         *treeview;
+	GtkTreeViewColumn *column;
+
+	/* Create the stack list tree view and set title invisible */
+	treeview = gtk_tree_view_new();
+	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
+	gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview),
+	                                COL_VALUE_STK);
+
+	/* Create the columns */
+	renderer = gtk_cell_renderer_text_new ();
+	column = gtk_tree_view_column_new_with_attributes
+		("ADDR", renderer, "text", COL_OFFSET_STK, NULL);
+
+	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+	
+	renderer = gtk_cell_renderer_text_new ();
+	column = gtk_tree_view_column_new_with_attributes
+		("VAL", renderer, "text", COL_VALUE_STK, NULL);
+
+	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+	return treeview;
+}
+
+/* Create a new scrolled window with sensible default settings. */
+static GtkWidget *new_scrolled_window(GtkWidget *contents)
+{
+	GtkWidget *sw;
+	sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+	                               GTK_POLICY_AUTOMATIC,
+	                               GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+	                                    GTK_SHADOW_IN);
+	gtk_container_add(GTK_CONTAINER(sw), contents);
+	return sw;
+}
+
+static const char uidesc[] =
+	"<menubar name='menu-bar'>"
+	" <menu action='debug-menu'>"
+	"  <menuitem action='run'/>"
+	"  <menuitem action='pause'/>"
+	"  <separator/>"
+	"  <menuitem action='step'/>"
+	"  <menuitem action='step-over'/>"
+	"  <menuitem action='finish'/>"
+	"  <separator/>"
+	"  <menuitem action='edit-breakpoints'/>"
+	"  <separator/>"
+	"  <menuitem action='close'/>"
+	" </menu>"
+	" <menu action='view-menu'>"
+	"  <menuitem action='view-keypad'/>"
+	"  <separator/>"
+	"  <menuitem action='view-logical'/>"
+	"  <menuitem action='view-absolute'/>"
+	" </menu>"
+	" <menu action='go-menu'>"
+	"  <menuitem action='go-to-address'/>"
+	"  <menuitem action='go-to-pc'/>"
+	"  <separator/>"
+	"  <menuitem action='prev-stack-entry'/>"
+	"  <menuitem action='next-stack-entry'/>"
+	" </menu>"
+	"</menubar>"
+	"<toolbar name='toolbar'>"
+	" <toolitem action='run'/>"
+	" <toolitem action='pause'/>"
+	" <separator/>"
+	" <toolitem action='step'/>"
+	" <toolitem action='step-over'/>"
+	" <toolitem action='finish'/>"
+	"</toolbar>"
+	"<accelerator action='toggle-breakpoint'/>";
+
+/* Create a new TilemDebugger. */
+TilemDebugger *tilem_debugger_new(TilemCalcEmulator *emu)
+{
+	TilemDebugger *dbg;
+	GtkWidget *hbox, *vbox, *vbox2, *vpaned, *sw, *menubar, *toolbar;
+	GtkUIManager *uimgr;
+	GtkAccelGroup *accelgrp;
+	GError *err = NULL;
+	int defwidth, defheight;
+
+	g_return_val_if_fail(emu != NULL, NULL);
+
+	dbg = g_slice_new0(TilemDebugger);
+	dbg->emu = emu;
+	dbg->dasm = tilem_disasm_new();
+
+	dbg->last_bp_type = TILEM_DB_BREAK_LOGICAL;
+	dbg->last_bp_mode = TILEM_DB_BREAK_EXEC;
+
+	dbg->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_title(GTK_WINDOW(dbg->window), "TilEm Debugger");
+	gtk_window_set_role(GTK_WINDOW(dbg->window), "Debugger");
+
+	tilem_config_get("debugger",
+	                 "width/i", &defwidth,
+	                 "height/i", &defheight,
+	                 "mem_width/i", &dbg->mem_rowsize,
+	                 "mem_start/i", &dbg->mem_start,
+	                 "mem_logical/b=1", &dbg->mem_logical,
+	                 NULL);
+
+	if (dbg->mem_rowsize <= 0)
+		dbg->mem_rowsize = 8;
+	if (dbg->mem_start < 0 || dbg->mem_start >= dbg->mem_rowsize)
+		dbg->mem_start = 0;
+
+	if (defwidth <= 0 || defheight <= 0) {
+		defwidth = 600;
+		defheight = 400;
+	}
+
+	gtk_window_set_default_size(GTK_WINDOW(dbg->window),
+	                            defwidth, defheight);
+
+	g_signal_connect(dbg->window, "set-focus",
+	                 G_CALLBACK(focus_changed), dbg);
+	g_signal_connect(dbg->window, "delete-event",
+	                 G_CALLBACK(delete_win), dbg);
+
+	vbox2 = gtk_vbox_new(FALSE, 0);
+
+	/* Actions and menu bar */
+
+	uimgr = gtk_ui_manager_new();
+	dbg->run_actions = gtk_action_group_new("Debug");
+	gtk_action_group_add_actions(dbg->run_actions, run_action_ents,
+	                             G_N_ELEMENTS(run_action_ents), dbg);
+	gtk_ui_manager_insert_action_group(uimgr, dbg->run_actions, 0);
+
+	dbg->paused_actions = gtk_action_group_new("Debug");
+	gtk_action_group_add_actions(dbg->paused_actions, paused_action_ents,
+	                             G_N_ELEMENTS(paused_action_ents), dbg);
+	gtk_action_group_add_radio_actions(dbg->paused_actions, mem_mode_ents,
+	                                   G_N_ELEMENTS(mem_mode_ents),
+	                                   dbg->mem_logical,
+	                                   G_CALLBACK(action_mem_mode), dbg);
+	gtk_ui_manager_insert_action_group(uimgr, dbg->paused_actions, 0);
+
+	dbg->misc_actions = gtk_action_group_new("Debug");
+	gtk_action_group_add_actions(dbg->misc_actions, misc_action_ents,
+	                             G_N_ELEMENTS(misc_action_ents), dbg);
+	gtk_action_group_add_toggle_actions(dbg->misc_actions, misc_toggle_ents,
+	                                    G_N_ELEMENTS(misc_toggle_ents), dbg);
+	gtk_ui_manager_insert_action_group(uimgr, dbg->misc_actions, 0);
+
+	dbg->prev_stack_action = gtk_action_group_get_action(dbg->paused_actions,
+	                                                     "prev-stack-entry");
+
+	accelgrp = gtk_ui_manager_get_accel_group(uimgr);
+	gtk_window_add_accel_group(GTK_WINDOW(dbg->window), accelgrp);
+
+	if (!gtk_ui_manager_add_ui_from_string(uimgr, uidesc, -1, &err))
+		g_error("Failed to create menus: %s", err->message);
+
+	menubar = gtk_ui_manager_get_widget(uimgr, "/menu-bar");
+	gtk_box_pack_start(GTK_BOX(vbox2), menubar, FALSE, FALSE, 0);
+
+	toolbar = gtk_ui_manager_get_widget(uimgr, "/toolbar");
+	gtk_box_pack_start(GTK_BOX(vbox2), toolbar, FALSE, FALSE, 0);
+
+	g_object_unref(uimgr);
+
+	hbox = gtk_hbox_new(FALSE, 6);
+
+	vpaned = gtk_vpaned_new();
+
+	/* Disassembly view */
+
+	dbg->disasm_view = tilem_disasm_view_new(dbg);
+	tilem_disasm_view_set_logical(TILEM_DISASM_VIEW(dbg->disasm_view),
+	                              dbg->mem_logical);
+	sw = new_scrolled_window(dbg->disasm_view);
+	gtk_paned_pack1(GTK_PANED(vpaned), sw, TRUE, TRUE);
+
+	/* Memory view */
+
+	dbg->mem_view = tilem_debugger_mem_view_new(dbg);
+	sw = new_scrolled_window(dbg->mem_view);
+	gtk_paned_pack2(GTK_PANED(vpaned), sw, TRUE, TRUE);
+
+	gtk_box_pack_start(GTK_BOX(hbox), vpaned, TRUE, TRUE, 0);
+
+	vbox = gtk_vbox_new(FALSE, 6);
+
+	/* Registers */
+
+	dbg->regbox = create_registers(dbg);
+	gtk_box_pack_start(GTK_BOX(vbox), dbg->regbox, FALSE, FALSE, 0);
+
+	/* Stack view */
+
+	dbg->stack_view = create_stack_view();
+	sw = new_scrolled_window(dbg->stack_view);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+
+	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
+
+	gtk_box_pack_start(GTK_BOX(vbox2), hbox, TRUE, TRUE, 0);
+	gtk_widget_show_all(vbox2);
+
+	gtk_container_add(GTK_CONTAINER(dbg->window), vbox2);
+
+	tilem_debugger_calc_changed(dbg);
+
+	return dbg;
+}
+
+/* Save the dimension for the debugger */
+static void save_debugger_dimension(TilemDebugger *dbg)
+{
+	gint width, height;
+
+	if (!dbg->window)
+		return;
+
+	gtk_window_get_size(GTK_WINDOW(dbg->window), &width, &height);
+
+	if (width <= 0 || height <= 0)
+		return;
+
+	tilem_config_set("debugger",
+	                 "width/i", width,
+	                 "height/i", height,
+	                 NULL);
+}
+
+static void free_all_breakpoints(TilemDebugger *dbg)
+{
+	GSList *l;
+	TilemDebugBreakpoint *bp;
+
+	for (l = dbg->breakpoints; l; l = l->next) {
+		bp = l->data;
+		g_slice_free(TilemDebugBreakpoint, bp);
+	}
+
+	g_slist_free(dbg->breakpoints);
+	dbg->breakpoints = NULL;
+}
+
+/* Free a TilemDebugger. */
+void tilem_debugger_free(TilemDebugger *dbg)
+{
+	g_return_if_fail(dbg != NULL);
+
+	save_debugger_dimension(dbg);
+
+	if (dbg->emu && dbg->emu->dbg == dbg)
+		dbg->emu->dbg = NULL;
+	if (dbg->window) {
+		gtk_widget_destroy(dbg->window);
+	}
+	if (dbg->dasm)
+		tilem_disasm_free(dbg->dasm);
+	if (dbg->run_actions)
+		g_object_unref(dbg->run_actions);
+	if (dbg->paused_actions)
+		g_object_unref(dbg->paused_actions);
+	if (dbg->misc_actions)
+		g_object_unref(dbg->misc_actions);
+
+	free_all_breakpoints(dbg);
+
+	g_slice_free(TilemDebugger, dbg);
+}
+
+static void entry_printf(GtkWidget *ent, const char *s, ...)
+{
+	char buf[20];
+	va_list ap;
+	va_start(ap, s);
+	g_vsnprintf(buf, sizeof(buf), s, ap);
+	va_end(ap);
+	gtk_entry_set_text(GTK_ENTRY(ent), buf);
+}
+
+static void refresh_regs(TilemDebugger *dbg)
+{
+	TilemCalc *calc = dbg->emu->calc;
+	int i;
+	GtkToggleButton *btn;
+
+	entry_printf(dbg->reg_entries[R_AF], "%04X", calc->z80.r.af.w.l);
+	entry_printf(dbg->reg_entries[R_BC], "%04X", calc->z80.r.bc.w.l);
+	entry_printf(dbg->reg_entries[R_DE], "%04X", calc->z80.r.de.w.l);
+	entry_printf(dbg->reg_entries[R_HL], "%04X", calc->z80.r.hl.w.l);
+	entry_printf(dbg->reg_entries[R_AF2], "%04X", calc->z80.r.af2.w.l);
+	entry_printf(dbg->reg_entries[R_BC2], "%04X", calc->z80.r.bc2.w.l);
+	entry_printf(dbg->reg_entries[R_DE2], "%04X", calc->z80.r.de2.w.l);
+	entry_printf(dbg->reg_entries[R_HL2], "%04X", calc->z80.r.hl2.w.l);
+	entry_printf(dbg->reg_entries[R_SP], "%04X", calc->z80.r.sp.w.l);
+	entry_printf(dbg->reg_entries[R_PC], "%04X", calc->z80.r.pc.w.l);
+	entry_printf(dbg->reg_entries[R_IX], "%04X", calc->z80.r.ix.w.l);
+	entry_printf(dbg->reg_entries[R_IY], "%04X", calc->z80.r.iy.w.l);
+	entry_printf(dbg->reg_entries[R_I], "%02X", calc->z80.r.ir.b.h);
+	entry_printf(dbg->reg_entries[R_IM], "%d", calc->z80.r.im);
+
+	for (i = 0; i < 8; i++) {
+		btn = GTK_TOGGLE_BUTTON(dbg->flag_buttons[i]);
+		gtk_toggle_button_set_active(btn, calc->z80.r.af.d & (1 << i));
+	}
+
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dbg->iff_checkbox),
+	                             calc->z80.r.iff1);
+}
+
+/* Create GtkListStore and attach it */
+static GtkTreeModel* fill_stk_list(TilemDebugger *dbg)
+{
+	GtkListStore  *store;
+	GtkTreeIter    iter;
+	char stack_offset[10];
+	char stack_value[10];
+	dword i, v;
+	int n = 0;
+
+	store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+	i = dbg->emu->calc->z80.r.sp.w.l;
+	while  (i < 0x10000 && n < 512) {
+	        g_snprintf(stack_offset, sizeof(stack_offset), "%04X:", i);
+
+		v = read_mem_word(dbg->emu->calc, i);
+		g_snprintf(stack_value, sizeof(stack_value), "%04X", v);
+
+		gtk_list_store_append(store, &iter);
+		gtk_list_store_set(store, &iter,
+		                   COL_OFFSET_STK, stack_offset,
+		                   COL_VALUE_STK, stack_value, -1);
+ 
+                i += 0x0002;
+                n++;
+        }
+    
+	return GTK_TREE_MODEL (store);
+}
+
+static void refresh_stack(TilemDebugger *dbg)
+{
+	GtkTreeModel *model = fill_stk_list(dbg);
+	gtk_tree_view_set_model(GTK_TREE_VIEW(dbg->stack_view), model);
+	g_object_unref(model);
+
+	fixed_tree_view_init(dbg->stack_view, 0,
+	                     COL_OFFSET_STK, "DDDD: ",
+	                     COL_VALUE_STK, "DDDD ",
+	                     -1);
+}
+
+static void unselect_all(GtkTreeView *tv)
+{
+	GtkTreeSelection *sel = gtk_tree_view_get_selection(tv);
+	gtk_tree_selection_unselect_all(sel);
+}
+
+static void refresh_all(TilemDebugger *dbg, gboolean updatemem)
+{
+	TilemCalc *calc;
+	gboolean paused;
+	gboolean updatedasm = FALSE;
+	GtkTreeModel *model;
+
+	dbg->refreshing = TRUE;
+	dbg->delayed_refresh = FALSE;
+
+	tilem_calc_emulator_lock(dbg->emu);
+	calc = dbg->emu->calc;
+	paused = dbg->emu->paused;
+
+	if (calc) {
+		refresh_regs(dbg);
+
+		if (dbg->lastwrite != calc->z80.lastwrite)
+			updatemem = TRUE;
+
+		if (updatemem || dbg->lastsp != calc->z80.r.sp.d)
+			refresh_stack(dbg);
+
+		if (paused && dbg->lastpc != calc->z80.r.pc.d)
+			updatedasm = TRUE;
+
+		dbg->lastwrite = calc->z80.lastwrite;
+		dbg->lastsp = calc->z80.r.sp.d;
+		dbg->lastpc = calc->z80.r.pc.d;
+	}
+
+	tilem_calc_emulator_unlock(dbg->emu);
+	
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(dbg->mem_view));
+	tilem_mem_model_clear_cache(TILEM_MEM_MODEL(model));
+
+	gtk_widget_queue_draw(dbg->mem_view);
+
+	if (paused != dbg->paused) {
+		dbg->paused = paused;
+		gtk_widget_set_sensitive(dbg->regbox, paused);
+		gtk_widget_set_sensitive(dbg->disasm_view, paused);
+		gtk_widget_set_sensitive(dbg->mem_view, paused);
+		gtk_widget_set_sensitive(dbg->stack_view, paused);
+		gtk_action_group_set_sensitive(dbg->run_actions, !paused);
+		gtk_action_group_set_sensitive(dbg->paused_actions, paused);
+		updatedasm = TRUE; /* need to redraw icons */
+	}
+
+	if (updatemem || updatedasm)
+		tilem_disasm_view_refresh(TILEM_DISASM_VIEW(dbg->disasm_view));
+
+	if (!paused) {
+		unselect_all(GTK_TREE_VIEW(dbg->disasm_view));
+		unselect_all(GTK_TREE_VIEW(dbg->mem_view));
+		unselect_all(GTK_TREE_VIEW(dbg->stack_view));
+	}
+
+	if (dbg->keypad_dialog)
+		tilem_keypad_dialog_refresh(dbg->keypad_dialog);
+
+	dbg->refreshing = FALSE;
+}
+
+/* Show debugger, and pause emulator if not already paused. */
+void tilem_debugger_show(TilemDebugger *dbg)
+{
+	g_return_if_fail(dbg != NULL);
+	g_return_if_fail(dbg->emu->calc != NULL);
+	tilem_calc_emulator_pause(dbg->emu);
+	cancel_step_bp(dbg);
+	refresh_all(dbg, TRUE);
+	go_to_stack_pos(dbg, -1);
+	gtk_window_present(GTK_WINDOW(dbg->window));
+}
+
+/* Hide debugger, and resume emulation if not already running. */
+void tilem_debugger_hide(TilemDebugger *dbg)
+{
+	g_return_if_fail(dbg != NULL);
+	gtk_widget_hide(dbg->window);
+	tilem_calc_emulator_run(dbg->emu);
+	save_debugger_dimension(dbg);
+}
+
+/* New calculator loaded. */
+void tilem_debugger_calc_changed(TilemDebugger *dbg)
+{
+	TilemCalc *calc;
+
+	g_return_if_fail(dbg != NULL);
+
+	tilem_disasm_free(dbg->dasm);
+	dbg->dasm = tilem_disasm_new();
+
+	dbg->step_bp = 0;
+
+	free_all_breakpoints(dbg);
+
+	calc = dbg->emu->calc;
+	if (!calc)
+		return;
+
+	load_default_symbols(dbg);
+
+	tilem_debugger_mem_view_configure(dbg->mem_view,
+	                                  dbg->emu, dbg->mem_rowsize,
+	                                  dbg->mem_start, dbg->mem_logical);
+
+	tilem_debugger_refresh(dbg, TRUE);
+
+	if (dbg->keypad_dialog)
+		tilem_keypad_dialog_calc_changed(dbg->keypad_dialog);
+}
+
+/* Update display. */
+void tilem_debugger_refresh(TilemDebugger *dbg, gboolean updatemem)
+{
+	g_return_if_fail(dbg != NULL);
+
+	if (!gtk_widget_get_visible(dbg->window))
+		return;
+
+	refresh_all(dbg, updatemem);
+}
+
+/* Breakpoint manipulation */
+
+/* Convert debugger type/mode into a core breakpoint type (core
+   breakpoints have only a single access type, for efficiency, so a
+   single TilemDebugBreakpoint may correspond to up to 3 core
+   breakpoints) */
+static int get_core_bp_type(int type, int mode)
+{
+	switch (type) {
+	case TILEM_DB_BREAK_LOGICAL:
+		switch (mode) {
+		case TILEM_DB_BREAK_READ:
+			return TILEM_BREAK_MEM_READ;
+		case TILEM_DB_BREAK_WRITE:
+			return TILEM_BREAK_MEM_WRITE;
+		case TILEM_DB_BREAK_EXEC:
+			return TILEM_BREAK_MEM_EXEC;
+		}
+		break;
+
+	case TILEM_DB_BREAK_PHYSICAL:
+		switch (mode) {
+		case TILEM_DB_BREAK_READ:
+			return TILEM_BREAK_MEM_READ | TILEM_BREAK_PHYSICAL;
+		case TILEM_DB_BREAK_WRITE:
+			return TILEM_BREAK_MEM_WRITE | TILEM_BREAK_PHYSICAL;
+		case TILEM_DB_BREAK_EXEC:
+			return TILEM_BREAK_MEM_EXEC | TILEM_BREAK_PHYSICAL;
+		}
+		break;
+
+	case TILEM_DB_BREAK_PORT:
+		switch (mode) {
+		case TILEM_DB_BREAK_READ:
+			return TILEM_BREAK_PORT_READ;
+		case TILEM_DB_BREAK_WRITE:
+			return TILEM_BREAK_PORT_WRITE;
+		}
+		break;
+
+	case TILEM_DB_BREAK_OPCODE:
+		return TILEM_BREAK_EXECUTE;
+	}
+
+	g_return_val_if_reached(0);
+}
+
+/* Install core breakpoint(s) */
+static void set_bp(TilemDebugger *dbg, TilemDebugBreakpoint *bp)
+{
+	int i, t, n;
+
+	tilem_calc_emulator_lock(dbg->emu);
+	for (i = 0; i < 3; i++) {
+		if (!bp->disabled && (bp->mode & (1 << i))) {
+			t = get_core_bp_type(bp->type, (1 << i));
+			n = tilem_z80_add_breakpoint(dbg->emu->calc, t,
+			                             bp->start, bp->end,
+			                             bp->mask,
+			                             NULL, NULL);
+			bp->id[i] = n;
+		}
+		else {
+			bp->id[i] = 0;
+		}
+	}
+	tilem_calc_emulator_unlock(dbg->emu);
+}
+
+/* Remove core breakpoint(s) */
+static void unset_bp(TilemDebugger *dbg, TilemDebugBreakpoint *bp)
+{
+	int i;
+	tilem_calc_emulator_lock(dbg->emu);
+	for (i = 0; i < 3; i++) {
+		if (bp->id[i])
+			tilem_z80_remove_breakpoint(dbg->emu->calc, bp->id[i]);
+		bp->id[i] = 0;
+	}
+	tilem_calc_emulator_unlock(dbg->emu);
+}
+
+static gboolean is_mem_exec_bp(const TilemDebugBreakpoint *bp)
+{
+	return ((bp->type == TILEM_DB_BREAK_LOGICAL
+	         || bp->type == TILEM_DB_BREAK_PHYSICAL)
+	        && (bp->mode & TILEM_DB_BREAK_EXEC));
+}
+
+/* Add a new debugger breakpoint */
+TilemDebugBreakpoint * tilem_debugger_add_breakpoint(TilemDebugger *dbg,
+                                                     const TilemDebugBreakpoint *bp)
+{
+	TilemDebugBreakpoint *bp2;
+
+	g_return_val_if_fail(dbg != NULL, NULL);
+	g_return_val_if_fail(bp != NULL, NULL);
+	g_return_val_if_fail(bp->mode != 0, NULL);
+
+	bp2 = g_slice_new(TilemDebugBreakpoint);
+	*bp2 = *bp;
+	dbg->breakpoints = g_slist_append(dbg->breakpoints, bp2);
+	set_bp(dbg, bp2);
+
+	if (is_mem_exec_bp(bp) && dbg->disasm_view)
+		tilem_disasm_view_refresh(TILEM_DISASM_VIEW(dbg->disasm_view));
+
+	return bp2;
+}
+
+/* Remove a debugger breakpoint */
+void tilem_debugger_remove_breakpoint(TilemDebugger *dbg,
+                                      TilemDebugBreakpoint *bp)
+{
+	gboolean isexec;
+
+	g_return_if_fail(dbg != NULL);
+	g_return_if_fail(bp != NULL);
+	g_return_if_fail(g_slist_index(dbg->breakpoints, bp) != -1);
+
+	isexec = is_mem_exec_bp(bp);
+
+	unset_bp(dbg, bp);
+	dbg->breakpoints = g_slist_remove(dbg->breakpoints, bp);
+	g_slice_free(TilemDebugBreakpoint, bp);
+
+	if (isexec && dbg->disasm_view)
+		tilem_disasm_view_refresh(TILEM_DISASM_VIEW(dbg->disasm_view));
+}
+
+/* Modify a debugger breakpoint */
+void tilem_debugger_change_breakpoint(TilemDebugger *dbg,
+                                      TilemDebugBreakpoint *bp,
+                                      const TilemDebugBreakpoint *newbp)
+{
+	gboolean isexec;
+
+	g_return_if_fail(dbg != NULL);
+	g_return_if_fail(bp != NULL);
+	g_return_if_fail(newbp != NULL);
+	g_return_if_fail(g_slist_index(dbg->breakpoints, bp) != -1);
+
+	isexec = (is_mem_exec_bp(bp) || is_mem_exec_bp(newbp));
+
+	unset_bp(dbg, bp);
+	*bp = *newbp;
+	set_bp(dbg, bp);
+
+	if (isexec && dbg->disasm_view)
+		tilem_disasm_view_refresh(TILEM_DISASM_VIEW(dbg->disasm_view));
+}
diff --git a/tool/tilem-src/gui/debugger.h b/tool/tilem-src/gui/debugger.h
new file mode 100644
index 0000000..a0e9823
--- /dev/null
+++ b/tool/tilem-src/gui/debugger.h
@@ -0,0 +1,164 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+typedef struct _TilemDebugBreakpoint {
+	enum {
+		TILEM_DB_BREAK_LOGICAL,
+		TILEM_DB_BREAK_PHYSICAL,
+		TILEM_DB_BREAK_PORT,
+		TILEM_DB_BREAK_OPCODE
+	} type;
+
+	enum {
+		TILEM_DB_BREAK_READ = 4,
+		TILEM_DB_BREAK_WRITE = 2,
+		TILEM_DB_BREAK_EXEC = 1
+	} mode;
+
+	dword start;
+	dword end;
+	dword mask;
+
+	int disabled;
+	int id[3];
+} TilemDebugBreakpoint;
+
+typedef struct _TilemDebugger {
+	struct _TilemCalcEmulator *emu;
+	struct _TilemDisasm *dasm;
+
+	/* Debugger widgets */
+	GtkWidget *window; /* Main debugger window */
+
+	GtkWidget *disasm_view;	 /* Disassembly view */
+	GtkWidget *mem_view;	 /* Memory view */
+	GtkWidget *stack_view;	 /* Stack view */
+
+	GtkWidget *regbox;	    /* Box containing registers */
+	GtkWidget *reg_entries[14]; /* Entries for registers */
+	GtkWidget *iff_checkbox;    /* Checkbox for IFF */
+	GtkWidget *flag_buttons[8]; /* Buttons for flags */
+
+	/* Action groups */
+	GtkActionGroup *run_actions;
+	GtkActionGroup *paused_actions;
+	GtkActionGroup *misc_actions;
+
+	/* Memory settings */
+	int mem_rowsize;
+	int mem_start;
+	gboolean mem_logical;
+
+	/* Stack navigation */
+	GtkAction *prev_stack_action;
+	int stack_index;
+
+	/* Breakpoints */
+	GSList *breakpoints;
+	int last_bp_type;
+	int last_bp_mode;
+
+	/* Temporary breakpoint info */
+	int step_bp; /* Breakpoint ID */
+	dword step_next_addr; /* Target address */
+
+	dword lastwrite;
+	dword lastsp;
+	dword lastpc;
+	gboolean paused;
+	gboolean refreshing;
+	gboolean delayed_refresh;
+
+	/* Other windows */
+	struct _TilemKeypadDialog *keypad_dialog;
+} TilemDebugger;
+
+/* Create a new TilemDebugger. */
+TilemDebugger *tilem_debugger_new(TilemCalcEmulator *emu);
+
+/* Free a TilemDebugger. */
+void tilem_debugger_free(TilemDebugger *dbg);
+
+/* Show debugger, and pause emulator if not already paused. */
+void tilem_debugger_show(TilemDebugger *dbg);
+
+/* Hide debugger, and resume emulation if not already running. */
+void tilem_debugger_hide(TilemDebugger *dbg);
+
+/* New calculator loaded. */
+void tilem_debugger_calc_changed(TilemDebugger *dbg);
+
+/* Update display. */
+void tilem_debugger_refresh(TilemDebugger *dbg, gboolean updatemem);
+
+/* Add a new breakpoint. */
+TilemDebugBreakpoint * tilem_debugger_add_breakpoint(TilemDebugger *dbg,
+                                                     const TilemDebugBreakpoint *bp);
+
+/* Remove an existing breakpoint. */
+void tilem_debugger_remove_breakpoint(TilemDebugger *dbg,
+                                      TilemDebugBreakpoint *bp);
+
+/* Modify an existing breakpoint. */
+void tilem_debugger_change_breakpoint(TilemDebugger *dbg,
+                                      TilemDebugBreakpoint *bp,
+                                      const TilemDebugBreakpoint *newbp);
+
+/* Show a dialog letting the user add, remove, and edit breakpoints. */
+void tilem_debugger_edit_breakpoints(TilemDebugger *dbg);
+
+
+/* Memory view */
+
+/* Create the memory view */
+GtkWidget *tilem_debugger_mem_view_new(TilemDebugger *dbg);
+
+/* Set memory view settings */
+void tilem_debugger_mem_view_configure(GtkWidget *mem_view,
+                                       TilemCalcEmulator *emu,
+                                       int rowsize, int start,
+                                       gboolean logical);
+
+
+/* Keypad dialog */
+
+typedef struct _TilemKeypadDialog {
+	TilemDebugger *dbg;
+
+	gboolean refreshing;
+
+	GtkWidget *window;
+	GtkWidget *output[7];
+	GtkWidget *keys[7][8];
+	GtkWidget *input[8];
+} TilemKeypadDialog;
+
+/* Create a new TilemKeypadDialog. */
+TilemKeypadDialog *tilem_keypad_dialog_new(TilemDebugger *dbg);
+
+/* Free a TilemKeypadDialog. */
+void tilem_keypad_dialog_free(TilemKeypadDialog *kpdlg);
+
+/* New calculator loaded. */
+void tilem_keypad_dialog_calc_changed(TilemKeypadDialog *kpdlg);
+
+/* Refresh key states. */
+void tilem_keypad_dialog_refresh(TilemKeypadDialog *kpdlg);
+
diff --git a/tool/tilem-src/gui/disasmview.c b/tool/tilem-src/gui/disasmview.c
new file mode 100644
index 0000000..a664b90
--- /dev/null
+++ b/tool/tilem-src/gui/disasmview.c
@@ -0,0 +1,1200 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include <tilemdb.h>
+
+#include "gui.h"
+#include "disasmview.h"
+
+G_DEFINE_TYPE(TilemDisasmView, tilem_disasm_view, GTK_TYPE_TREE_VIEW);
+
+/*
+  This is a HORRIBLE kludge.  Don't ever do anything like this.  ;)
+
+  We want a widget that has the look and feel of a GtkTreeView.  But
+  our "data model" isn't consistent with a GtkTreeModel, since it
+  changes depending on where we are.
+
+  This widget keeps track of how high each row will be once rendered,
+  and uses that to construct a GtkListStore with the appropriate
+  number of rows to fill the window.  We also override the move-cursor
+  signal so that we can handle the boundaries.
+ */
+
+/* Model columns */
+enum {
+	COL_POSITION,
+	COL_ADDRESS,
+	COL_MNEMONIC,
+	COL_ARGUMENTS,
+	COL_SHOW_MNEMONIC,
+	COL_ICON,
+	NUM_COLUMNS
+};
+
+static GtkTreeViewClass *parent_class;
+
+/* We define two "positions" for each actual address; the second is
+   used if there's a label to be displayed at that address. */
+
+#define POS_TO_ADDR(x) ((x) >> 1)
+#define ADDR_TO_POS(x) ((x) << 1)
+
+/* Disassembly */
+
+/* Convert physical to logical address; if address is not currently
+   mapped, use the bank-A address */
+static dword default_ptol(TilemCalc *calc, dword addr)
+{
+	dword addr_l;
+
+	g_return_val_if_fail(calc != NULL, 0);
+
+	addr_l = (*calc->hw.mem_ptol)(calc, addr);
+	if (addr_l == 0xffffffff)
+		addr_l = (addr & 0x3fff) | 0x4000;
+
+	return addr_l;
+}
+
+/* Check for a label at the given address (physical or logical
+   depending on the mode of the DisasmView) */
+static const char *get_label(TilemDisasmView *dv, TilemCalc *calc,
+                             dword addr)
+{
+	g_return_val_if_fail(calc != NULL, NULL);
+	g_return_val_if_fail(dv->dbg->dasm != NULL, NULL);
+
+	if (!dv->use_logical)
+		addr = default_ptol(calc, addr);
+
+	return tilem_disasm_get_label_at_address(dv->dbg->dasm, addr);
+}
+
+/* Disassemble a line */
+static void disassemble(TilemDisasmView *dv, TilemCalc *calc, dword pos,
+                        dword *nextpos, char **mnemonic, char **args)
+{
+	dword addr = POS_TO_ADDR(pos);
+	const char *lbl;
+	char buf[500], *p;
+
+	g_return_if_fail(calc != NULL);
+	g_return_if_fail(dv->dbg->dasm != NULL);
+
+	if (!(pos & 1) && (lbl = get_label(dv, calc, addr))) {
+		if (mnemonic) {
+			*mnemonic = NULL;
+			*args = g_strdup_printf("%s:", lbl);
+		}
+
+		if (nextpos)
+			*nextpos = pos + 1;
+	}
+	else if (mnemonic) {
+		tilem_disasm_disassemble(dv->dbg->dasm, calc,
+		                         !dv->use_logical, addr,
+		                         &addr, buf, sizeof(buf));
+
+		p = strchr(buf, '\t');
+		if (p) {
+			*mnemonic = g_strndup(buf, p - buf);
+			*args = g_strdup(p + 1);
+		}
+		else {
+			*mnemonic = g_strdup(buf);
+			*args = NULL;
+		}
+
+		if (nextpos)
+			*nextpos = ADDR_TO_POS(addr);
+	}
+	else {
+		tilem_disasm_disassemble(dv->dbg->dasm, calc,
+		                         !dv->use_logical, addr,
+		                         &addr, NULL, 0);
+		if (nextpos)
+			*nextpos = ADDR_TO_POS(addr);
+	}
+}
+
+/* Get "next" position */
+static dword get_next_pos(TilemDisasmView *dv, TilemCalc *calc, dword pos)
+{
+	disassemble(dv, calc, pos, &pos, NULL, NULL);
+	return pos;
+}
+
+/* Get "previous" position */
+static dword get_prev_pos(TilemDisasmView *dv, TilemCalc *calc, dword pos)
+{
+	dword addr = POS_TO_ADDR(pos);
+
+	g_return_val_if_fail(calc != NULL, 0);
+
+	if (pos & 1) {
+		return pos - 1;
+	}
+	else {
+		if (addr > 0)
+			addr--;
+		else if (dv->use_logical)
+			addr = 0xffff;
+		else
+			addr = (calc->hw.romsize + calc->hw.ramsize - 1);
+
+		if (get_label(dv, calc, addr))
+			return ADDR_TO_POS(addr) + 1;
+		else
+			return ADDR_TO_POS(addr);
+	}
+}
+
+/* Convert physical to logical position */
+static dword pos_ptol(TilemCalc *calc, dword pos)
+{
+	dword addr;
+
+	g_return_val_if_fail(calc != NULL, 0);
+
+	if (pos == (dword) -1)
+		return pos;
+
+	addr = default_ptol(calc, POS_TO_ADDR(pos));
+	return ADDR_TO_POS(addr) + (pos & 1);
+}
+
+/* Convert logical to physical position */
+static dword pos_ltop(TilemCalc *calc, dword pos)
+{
+	dword addr;
+
+	g_return_val_if_fail(calc != NULL, 0);
+
+	if (pos == (dword) -1)
+		return pos;
+
+	addr = (*calc->hw.mem_ltop)(calc, POS_TO_ADDR(pos));
+	return ADDR_TO_POS(addr) + (pos & 1);
+}
+
+/* Icons */
+
+static GdkPixbuf *get_icon(TilemDisasmView *dv, gboolean ispc, gboolean isbp)
+{
+	const char *name;
+
+	if (ispc && isbp)
+		name = "tilem-disasm-break-pc";
+	else if (isbp)
+		name = "tilem-disasm-break";
+	else if (ispc)
+		name = "tilem-disasm-pc";
+	else
+		return NULL;
+
+	return gtk_widget_render_icon(GTK_WIDGET(dv), name,
+	                              GTK_ICON_SIZE_MENU, NULL);
+}
+
+/* List model management */
+
+/* Create a new list store for disassembly */
+static GtkTreeModel * new_dasm_model()
+{
+	GtkListStore *store;
+
+	g_assert(NUM_COLUMNS == 6);
+	store = gtk_list_store_new(6,
+	                           G_TYPE_INT,
+	                           G_TYPE_STRING,
+	                           G_TYPE_STRING,
+	                           G_TYPE_STRING,
+	                           G_TYPE_BOOLEAN,
+	                           GDK_TYPE_PIXBUF);
+
+	return GTK_TREE_MODEL(store);
+}
+
+/* Append dummy data to the model; used for sizing */
+static void append_dummy_line(TilemDisasmView *dv, GtkTreeModel *model,
+                              GtkTreeIter *iter)
+{
+	GtkTreeIter iter1;
+	GdkPixbuf *icon;
+
+	gtk_list_store_append(GTK_LIST_STORE(model), &iter1);
+
+	icon = get_icon(dv, TRUE, FALSE);
+
+	gtk_list_store_set(GTK_LIST_STORE(model), &iter1,
+	                   COL_ICON, icon,
+	                   COL_ADDRESS, "DD:DDDD",
+	                   COL_MNEMONIC, "ROM_CALL",
+	                   COL_ARGUMENTS, "_fnord",
+	                   COL_SHOW_MNEMONIC, TRUE,
+	                   -1);
+
+	if (icon)
+		g_object_unref(icon);
+
+	if (iter)
+		*iter = iter1;
+}
+
+/* Check if given logical address is a breakpoint (according to
+   current mapping) */
+static TilemDebugBreakpoint *find_bp_logical(TilemDebugger *dbg,
+                                             TilemCalc *calc,
+                                             dword addr)
+{
+	GSList *l;
+	TilemDebugBreakpoint *bp;
+	dword pa = (*calc->hw.mem_ltop)(calc, addr);
+
+	for (l = dbg->breakpoints; l; l = l->next) {
+		bp = l->data;
+		if (!(bp->mode & TILEM_DB_BREAK_EXEC))
+			continue;
+
+		if (bp->type == TILEM_DB_BREAK_LOGICAL
+		    && bp->start <= addr
+		    && bp->end >= addr
+		    && !bp->disabled)
+			return bp;
+
+		if (bp->type == TILEM_DB_BREAK_PHYSICAL
+		    && bp->start <= pa
+		    && bp->end >= pa
+		    && !bp->disabled)
+			return bp;
+	}
+
+	return NULL;
+}
+
+/* Check if given physical address is a breakpoint (according to
+   current mapping) */
+static TilemDebugBreakpoint *find_bp_physical(TilemDebugger *dbg,
+                                              TilemCalc *calc,
+                                              dword addr)
+{
+	GSList *l;
+	TilemDebugBreakpoint *bp;
+	dword la, pa;
+	int i, mapped[4];
+
+	/* NOTE: this assumes that bits 0-13 are unaffected by the
+	   mapping!  This is true for all current models, but might
+	   need to be changed in the future */
+	for (i = 0; i < 4; i++) {
+		la = (i << 14) + (addr & 0x3fff);
+		pa = (*calc->hw.mem_ltop)(calc, la);
+		mapped[i] = (addr == pa);
+	}
+
+	for (l = dbg->breakpoints; l; l = l->next) {
+		bp = l->data;
+		if (!(bp->mode & TILEM_DB_BREAK_EXEC))
+			continue;
+
+		if (bp->type == TILEM_DB_BREAK_PHYSICAL
+		    && bp->start <= addr
+		    && bp->end >= addr
+		    && !bp->disabled)
+			return bp;
+
+		if (bp->type == TILEM_DB_BREAK_LOGICAL
+		    && !bp->disabled) {
+			for (i = 0; i < 4; i++) {
+				la = (i << 14) + (addr & 0x3fff);
+				if (bp->start <= la
+				    && bp->end >= la
+				    && mapped[i])
+					return bp;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+/* Check if line has a breakpoint set */
+static TilemDebugBreakpoint *find_line_bp(TilemDisasmView *dv, dword pos)
+{
+	TilemDebugBreakpoint *bp;
+	dword addr = POS_TO_ADDR(pos);
+	TilemCalc *calc;
+
+	tilem_calc_emulator_lock(dv->dbg->emu);
+	calc = dv->dbg->emu->calc;
+
+	if (dv->use_logical)
+		bp = find_bp_logical(dv->dbg, calc, addr);
+	else
+		bp = find_bp_physical(dv->dbg, calc, addr);
+
+	tilem_calc_emulator_unlock(dv->dbg->emu);
+
+	return bp;
+}
+
+/* Enable breakpoint on the given line */
+static void enable_line_bp(TilemDisasmView *dv, dword pos)
+{
+	TilemDebugBreakpoint *bp, tmpbp;
+
+	if ((bp = find_line_bp(dv, pos)))
+		return;
+
+	tmpbp.type = (dv->use_logical
+	              ? TILEM_DB_BREAK_LOGICAL
+	              : TILEM_DB_BREAK_PHYSICAL);
+	tmpbp.mode = TILEM_DB_BREAK_EXEC;
+	tmpbp.start = POS_TO_ADDR(pos);
+	tmpbp.end = POS_TO_ADDR(pos);
+	tmpbp.mask = (dv->use_logical ? 0xffff : 0xffffffff);
+	tmpbp.disabled = 0;
+	tilem_debugger_add_breakpoint(dv->dbg, &tmpbp);
+}
+
+/* Disable breakpoint on the given line */
+static void disable_line_bp(TilemDisasmView *dv, dword pos)
+{
+	TilemDebugBreakpoint *bp, tmpbp;
+
+	if (!(bp = find_line_bp(dv, pos)))
+		return;
+
+	if (bp->mode != TILEM_DB_BREAK_EXEC || bp->start != bp->end) {
+		/* special breakpoint; do not delete it, just disable it */
+		tmpbp = *bp;
+		tmpbp.disabled = 1;
+		tilem_debugger_change_breakpoint(dv->dbg, bp, &tmpbp);
+	}
+	else {
+		/* regular breakpoint */
+		tilem_debugger_remove_breakpoint(dv->dbg, bp);
+	}
+}
+
+/* Append a line to the dasm model */
+static void append_dasm_line(TilemDisasmView *dv, TilemCalc *calc,
+                             GtkTreeModel *model, GtkTreeIter *iter,
+                             dword pos, dword *nextpos)
+{
+	GtkTreeIter iter1;
+	char *astr, *mnem, *args;
+	dword addr, pc;
+	gboolean ispc;
+	TilemDebugBreakpoint *bp;
+	GdkPixbuf *icon;
+
+	g_return_if_fail(calc != NULL);
+
+	gtk_list_store_append(GTK_LIST_STORE(model), &iter1);
+
+	addr = POS_TO_ADDR(pos);
+	astr = tilem_format_addr(dv->dbg, addr, !dv->use_logical);
+
+	disassemble(dv, calc, pos, nextpos, &mnem, &args);
+
+	if (!mnem)
+		bp = NULL;
+	else if (dv->use_logical)
+		bp = find_bp_logical(dv->dbg, calc, addr);
+	else
+		bp = find_bp_physical(dv->dbg, calc, addr);
+
+	if (!mnem || !dv->dbg->emu->paused) {
+		ispc = FALSE;
+	}
+	else {
+		pc = calc->z80.r.pc.w.l;
+		if (!dv->use_logical)
+			pc = (*calc->hw.mem_ltop)(calc, pc);
+		ispc = (addr == pc);
+	}
+
+	icon = get_icon(dv, ispc, (bp != NULL));
+
+	gtk_list_store_set(GTK_LIST_STORE(model), &iter1,
+	                   COL_POSITION, (int) pos,
+	                   COL_ADDRESS, astr,
+	                   COL_MNEMONIC, mnem,
+	                   COL_SHOW_MNEMONIC, (mnem ? TRUE : FALSE),
+	                   COL_ARGUMENTS, args,
+	                   COL_ICON, icon,
+	                   -1);
+
+	if (icon)
+		g_object_unref(icon);
+
+	g_free(astr);
+	g_free(mnem);
+	g_free(args);
+
+	if (iter)
+		*iter = iter1;
+}
+
+/* Refresh the view by creating and populating a new model */
+static void refresh_disassembly(TilemDisasmView *dv, dword pos, int nlines,
+                                dword selectpos)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GtkTreePath *selectpath = NULL;
+	TilemCalc *calc;
+	dword nextpos;
+	int i;
+
+	model = new_dasm_model();
+
+	dv->startpos = pos;
+
+	tilem_calc_emulator_lock(dv->dbg->emu);
+	calc = dv->dbg->emu->calc;
+
+	if (!calc)
+		nlines = 0;
+
+	for (i = 0; i < nlines; i++) {
+		append_dasm_line(dv, calc, model, &iter, pos, &nextpos);
+
+		if (pos == selectpos)
+			selectpath = gtk_tree_model_get_path(model, &iter);
+
+		pos = nextpos;
+	}
+
+	tilem_calc_emulator_unlock(dv->dbg->emu);
+
+	dv->endpos = pos;
+	dv->nlines = nlines;
+
+	gtk_tree_view_set_model(GTK_TREE_VIEW(dv), model);
+	g_object_unref(model);
+
+	if (selectpath) {
+		gtk_tree_view_set_cursor(GTK_TREE_VIEW(dv), selectpath,
+		                         NULL, FALSE);
+		gtk_tree_path_free(selectpath);
+	}
+}
+
+/* Determine the (absolute) position and (display-relative) line
+   number of the cursor, if any */
+static gboolean get_cursor_line(TilemDisasmView *dv, dword *pos,
+                                int *linenum)
+{
+	GtkTreePath *path;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gint *i, p;
+
+	gtk_tree_view_get_cursor(GTK_TREE_VIEW(dv), &path, NULL);
+	if (!path) {
+		if (pos) *pos = (dword) -1;
+		if (linenum) *linenum = -1;
+		return FALSE;
+	}
+
+	if (pos) {
+		model = gtk_tree_view_get_model(GTK_TREE_VIEW(dv));
+		if (gtk_tree_model_get_iter(model, &iter, path)) {
+			gtk_tree_model_get(model, &iter,
+			                   COL_POSITION, &p, -1);	
+			*pos = p;
+		}
+		else {
+			*pos = (dword) -1;
+		}
+	}
+
+	if (linenum) {
+		i = gtk_tree_path_get_indices(path);
+		*linenum = i[0];
+	}
+
+	gtk_tree_path_free(path);
+
+	return TRUE;
+}
+
+/* Size allocation */
+
+/* Get the desired height for the tree view (based on size of the data
+   we've inserted into the model) */
+static int get_parent_request_height(GtkWidget *w)
+{
+	GtkRequisition req;
+	(*GTK_WIDGET_CLASS(parent_class)->size_request)(w, &req);
+	return req.height;
+}
+
+/* Widget is assigned a size and position */
+static void tilem_disasm_view_size_allocate(GtkWidget *w,
+                                            GtkAllocation *alloc)
+{
+	TilemDisasmView *dv;
+	GtkTreeModel *model;
+	dword curpos;
+	int n, height1, height2;
+
+	g_return_if_fail(TILEM_IS_DISASM_VIEW(w));
+	dv = TILEM_DISASM_VIEW(w);
+
+	(*GTK_WIDGET_CLASS(parent_class)->size_allocate)(w, alloc);
+
+	if (alloc->height < 1)
+		return;
+
+	get_cursor_line(dv, &curpos, NULL);
+
+	/* Calculate line height */
+	if (!dv->line_height) {
+		model = new_dasm_model();
+
+		append_dummy_line(dv, model, NULL);
+		gtk_tree_view_set_model(GTK_TREE_VIEW(dv), model);
+		height1 = get_parent_request_height(w);
+
+		append_dummy_line(dv, model, NULL);
+		height2 = get_parent_request_height(w);
+
+		dv->line_height = height2 - height1;
+		dv->base_height = height1 - dv->line_height;
+
+		g_object_unref(model);
+
+		dv->nlines = 0;
+
+		if (dv->line_height <= 0) {
+			dv->line_height = 0;
+			return;
+		}
+	}
+
+	n = (alloc->height - dv->base_height) / dv->line_height;
+
+	if (n < 1)
+		n = 1;
+
+	if (n != dv->nlines)
+		refresh_disassembly(dv, dv->startpos, n, curpos);
+}
+
+/* Get widget's desired size */
+static void tilem_disasm_view_size_request(GtkWidget *w, GtkRequisition *req)
+{
+	(*GTK_WIDGET_CLASS(parent_class)->size_request)(w, req);
+	req->height = 100;	/* ignore requested height */
+}
+
+/* Widget style set */
+static void tilem_disasm_view_style_set(GtkWidget *w, GtkStyle *oldstyle)
+{
+	TilemDisasmView *dv;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GList *cols, *cp;
+	GtkTreeViewColumn *col;
+	int width;
+
+	g_return_if_fail(TILEM_IS_DISASM_VIEW(w));
+	dv = TILEM_DISASM_VIEW(w);
+
+	(*GTK_WIDGET_CLASS(parent_class)->style_set)(w, oldstyle);
+
+	/* line height must be recalculated */
+	dv->line_height = 0;
+
+	/* set column widths based on a dummy model */
+
+	model = new_dasm_model();
+	append_dummy_line(dv, model, &iter);
+
+	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(dv));
+	for (cp = cols; cp; cp = cp->next) {
+		col = cp->data;
+		gtk_tree_view_column_cell_set_cell_data(col, model, &iter,
+		                                        FALSE, FALSE);
+		gtk_tree_view_column_cell_get_size(col, NULL, NULL, NULL,
+		                                   &width, NULL);
+		gtk_tree_view_column_set_fixed_width(col, width + 2);
+	}
+	g_list_free(cols);
+
+	g_object_unref(model);
+}
+
+/* Cursor movement commands */
+
+/* Move up by COUNT lines */
+static gboolean move_up_lines(TilemDisasmView *dv, int count)
+{
+	TilemCalc *calc;
+	dword pos;
+	int linenum;
+
+	if (!get_cursor_line(dv, NULL, &linenum))
+		linenum = 0;
+
+	if (linenum >= count)
+		return FALSE;
+
+	tilem_calc_emulator_lock(dv->dbg->emu);
+	calc = dv->dbg->emu->calc;
+
+	pos = dv->startpos;
+	count -= linenum;
+	while (count > 0) {
+		pos = get_prev_pos(dv, calc, pos);
+		count--;
+	}
+
+	tilem_calc_emulator_unlock(dv->dbg->emu);
+
+	refresh_disassembly(dv, pos, dv->nlines, pos);
+
+	return TRUE;
+}
+
+/* Move down by COUNT lines */
+static gboolean move_down_lines(TilemDisasmView *dv, int count)
+{
+	TilemCalc *calc;
+	dword startpos, selpos;
+	int linenum;
+
+	if (!get_cursor_line(dv, NULL, &linenum))
+		linenum = -1;
+
+	if (linenum + count < dv->nlines)
+		return FALSE;
+
+	tilem_calc_emulator_lock(dv->dbg->emu);
+	calc = dv->dbg->emu->calc;
+
+	startpos = get_next_pos(dv, calc, dv->startpos);
+	selpos = dv->endpos;
+	count -= dv->nlines - linenum;
+
+	while (count > 0) {
+		startpos = get_next_pos(dv, calc, startpos);
+		selpos = get_next_pos(dv, calc, selpos);
+		count--;
+	}
+
+	tilem_calc_emulator_unlock(dv->dbg->emu);
+
+	refresh_disassembly(dv, startpos, dv->nlines, selpos);
+
+	return TRUE;
+}
+
+/* Move down by COUNT bytes */
+static void move_bytes(TilemDisasmView *dv, int count)
+{
+	dword pos, addr;
+	const TilemCalc *calc = dv->dbg->emu->calc;
+
+	g_return_if_fail(calc != NULL);
+
+	if (!get_cursor_line(dv, &pos, NULL))
+		pos = dv->startpos;
+
+	addr = POS_TO_ADDR(pos);
+
+	if (dv->use_logical)
+		addr = (addr + count) & 0xffff;
+	else {
+		addr += calc->hw.romsize + calc->hw.ramsize + count;
+		addr %= calc->hw.romsize + calc->hw.ramsize;
+	}
+
+	pos = ADDR_TO_POS(addr);
+	refresh_disassembly(dv, pos, dv->nlines, pos - 1);
+}
+
+/* Move the cursor (action signal) */
+static gboolean tilem_disasm_view_move_cursor(GtkTreeView *tv,
+                                              GtkMovementStep step,
+                                              gint count)
+{
+	TilemDisasmView *dv;
+
+	g_return_val_if_fail(TILEM_IS_DISASM_VIEW(tv), FALSE);
+	dv = TILEM_DISASM_VIEW(tv);
+
+	if (!dv->dbg->emu->calc)
+		return FALSE;
+
+	switch (step) {
+	case GTK_MOVEMENT_DISPLAY_LINES:
+		if (count < 0) {
+			if (move_up_lines(dv, -count))
+				return TRUE;
+		}
+		else {
+			if (move_down_lines(dv, count))
+				return TRUE;
+		}
+		break;
+
+	case GTK_MOVEMENT_PARAGRAPHS:
+	case GTK_MOVEMENT_PARAGRAPH_ENDS:
+	case GTK_MOVEMENT_PAGES:
+		/* FIXME: might be better to move by actual "pages" of code */
+		move_bytes(dv, count * 0x100);
+		return TRUE;
+
+	case GTK_MOVEMENT_BUFFER_ENDS:
+		move_bytes(dv, count * 0x4000);
+		return TRUE;
+
+	case GTK_MOVEMENT_LOGICAL_POSITIONS:
+	case GTK_MOVEMENT_VISUAL_POSITIONS:
+	case GTK_MOVEMENT_WORDS:
+	case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
+	case GTK_MOVEMENT_HORIZONTAL_PAGES:
+	default:
+		break;
+	}
+
+	return (*GTK_TREE_VIEW_CLASS(parent_class)->move_cursor)(tv, step, count);
+}
+
+/* Popup menu */
+
+static void toggle_bp(G_GNUC_UNUSED GtkCheckMenuItem *item, gpointer data)
+{
+	TilemDisasmView *dv = data;
+	tilem_disasm_view_toggle_breakpoint(dv);
+}
+
+void tilem_disasm_view_toggle_breakpoint(TilemDisasmView *dv)
+{
+	dword curpos;
+
+	g_return_if_fail(TILEM_IS_DISASM_VIEW(dv));
+
+	get_cursor_line(dv, &curpos, NULL);
+	if (curpos == (dword) -1)
+		return;
+
+	if (find_line_bp(dv, curpos))
+		disable_line_bp(dv, curpos);
+	else
+		enable_line_bp(dv, curpos);
+}
+
+static void prompt_go_to(G_GNUC_UNUSED GtkMenuItem *item, gpointer data)
+{
+	TilemDisasmView *dv = data;
+	GtkWidget *window;
+	dword curpos, addr;
+
+	window = gtk_widget_get_toplevel(GTK_WIDGET(dv));
+
+	get_cursor_line(dv, &curpos, NULL);
+	addr = POS_TO_ADDR(curpos);
+
+	if (tilem_prompt_address(dv->dbg, GTK_WINDOW(window),
+	                         "Go to Address", "Address:",
+	                         &addr, !dv->use_logical,
+	                         (curpos != (dword) -1)))
+		tilem_disasm_view_go_to_address(dv, addr, dv->use_logical);
+}
+
+static void go_to_pc(G_GNUC_UNUSED GtkMenuItem *item, gpointer data)
+{
+	TilemDisasmView *dv = data;
+	TilemCalc *calc;
+	dword pc;
+
+	g_return_if_fail(dv->dbg != NULL);
+	g_return_if_fail(dv->dbg->emu != NULL);
+	g_return_if_fail(dv->dbg->emu->calc != NULL);
+
+	tilem_calc_emulator_lock(dv->dbg->emu);
+	calc = dv->dbg->emu->calc;
+	pc = calc->z80.r.pc.w.l;
+	tilem_calc_emulator_unlock(dv->dbg->emu);
+
+	tilem_disasm_view_go_to_address(dv, pc, TRUE);
+}
+
+/* Determine where to pop up menu (if not activated by a mouse event) */
+static void place_menu(GtkMenu *menu, gint *x, gint *y,
+                       gboolean *push_in, gpointer data)
+{
+	TilemDisasmView *dv = data;
+	GtkTreePath *path;
+	GdkRectangle rect;
+	GdkWindow *win;
+	GdkScreen *screen;
+	int n;
+
+	win = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(dv));
+	gdk_window_get_origin(win, x, y);
+
+	gtk_tree_view_get_cursor(GTK_TREE_VIEW(dv), &path, NULL);
+	if (path) {
+		gtk_tree_view_get_cell_area(GTK_TREE_VIEW(dv), path, NULL, &rect);
+		gtk_tree_path_free(path);
+		*y += rect.y + rect.height;
+	}
+
+	screen = gdk_drawable_get_screen(win);
+	n = gdk_screen_get_monitor_at_point(screen, *x, *y);
+	gtk_menu_set_monitor(menu, n);
+
+	*push_in = FALSE;
+}
+
+/* Create and show the popup menu */
+static void show_popup_menu(TilemDisasmView *dv, GdkEventButton *event)
+{
+	GtkWidget *menu, *item;
+	dword curpos;
+
+	if (dv->popup_menu)
+		gtk_widget_destroy(dv->popup_menu);
+	dv->popup_menu = menu = gtk_menu_new();
+
+	/* Enable/disable breakpoint */
+
+	item = gtk_check_menu_item_new_with_mnemonic("_Breakpoint Here");
+
+	get_cursor_line(dv, &curpos, NULL);
+	if (curpos == (dword) -1)
+		gtk_widget_set_sensitive(item, FALSE);
+	else if (find_line_bp(dv, curpos))
+		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
+
+	g_signal_connect(item, "toggled",
+	                 G_CALLBACK(toggle_bp), dv);
+
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+
+	item = gtk_separator_menu_item_new();
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+
+	/* Jump to address */
+
+	item = gtk_menu_item_new_with_mnemonic("_Go to Address...");
+	g_signal_connect(item, "activate", G_CALLBACK(prompt_go_to), dv);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+
+	item = gtk_menu_item_new_with_mnemonic("Go to P_C");
+	g_signal_connect(item, "activate", G_CALLBACK(go_to_pc), dv);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+
+	if (event)
+		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+		               event->button, event->time);
+	else
+		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, &place_menu, dv,
+		               0, gtk_get_current_event_time());
+}
+
+/* Button pressed */
+static gboolean tilem_disasm_view_button_press(GtkWidget *w,
+                                               GdkEventButton *event)
+{
+	g_return_val_if_fail(TILEM_IS_DISASM_VIEW(w), FALSE);
+
+	(*GTK_WIDGET_CLASS(parent_class)->button_press_event)(w, event);
+
+	if (event->button == 3)
+		show_popup_menu(TILEM_DISASM_VIEW(w), event);
+
+	return TRUE;
+}
+
+/* Key pressed to activate context menu */
+static gboolean tilem_disasm_view_popup_menu(GtkWidget *w)
+{
+	g_return_val_if_fail(TILEM_IS_DISASM_VIEW(w), FALSE);
+	show_popup_menu(TILEM_DISASM_VIEW(w), NULL);
+	return TRUE;
+}
+
+/* Row activated (double-clicked) */
+static void tilem_disasm_view_row_activated(GtkTreeView *tv, GtkTreePath *path,
+                                            GtkTreeViewColumn *col)
+{
+	TilemDisasmView *dv = TILEM_DISASM_VIEW(tv);
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gint pos;
+
+	model = gtk_tree_view_get_model(tv);
+	if (!gtk_tree_model_get_iter(model, &iter, path))
+		return;
+
+	gtk_tree_model_get(model, &iter, COL_POSITION, &pos, -1);
+
+	if (col == dv->icon_column) {
+		if (find_line_bp(dv, pos))
+			disable_line_bp(dv, pos);
+		else
+			enable_line_bp(dv, pos);
+	}
+}
+
+/* Unrealize widget */
+static void tilem_disasm_view_unrealize(GtkWidget *w)
+{
+	TilemDisasmView *dv = TILEM_DISASM_VIEW(w);
+
+	if (dv->popup_menu)
+		gtk_widget_destroy(dv->popup_menu);
+	dv->popup_menu = NULL;
+
+	(*GTK_WIDGET_CLASS(parent_class)->unrealize)(w);
+}
+
+/* Initialize a new TilemDisasmView */
+static void tilem_disasm_view_init(TilemDisasmView *dv)
+{
+	GtkTreeView *tv = GTK_TREE_VIEW(dv);
+	GtkCellRenderer *cell;
+	GtkTreeViewColumn *col;
+
+	dv->use_logical = TRUE;
+
+	gtk_tree_view_set_enable_search(tv, FALSE);
+	gtk_tree_view_set_fixed_height_mode(tv, TRUE);
+	gtk_tree_view_set_headers_visible(tv, FALSE);
+
+	cell = gtk_cell_renderer_pixbuf_new();
+	col = gtk_tree_view_column_new_with_attributes(NULL, cell,
+	                                               "pixbuf", COL_ICON,
+	                                               NULL);
+	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_append_column(tv, col);
+	dv->icon_column = col;
+
+	cell = gtk_cell_renderer_text_new();
+	col = gtk_tree_view_column_new_with_attributes("Addr", cell,
+	                                               "text", COL_ADDRESS,
+	                                               NULL);
+	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_append_column(tv, col);
+
+	col = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(col, "Disassembly");
+
+	cell = gtk_cell_renderer_text_new();
+	g_object_set(cell, "xpad", 10, NULL);
+	gtk_tree_view_column_pack_start(col, cell, FALSE);
+	gtk_tree_view_column_set_attributes(col, cell,
+	                                    "text", COL_MNEMONIC,
+	                                    "visible", COL_SHOW_MNEMONIC,
+	                                    NULL);
+
+	cell = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(col, cell, TRUE);
+	gtk_tree_view_column_set_attributes(col, cell,
+	                                    "text", COL_ARGUMENTS,
+	                                    NULL);
+
+	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_expand(col, TRUE);
+	gtk_tree_view_append_column(tv, col);
+}
+
+static const char default_style[] =
+	"style \"tilem-disasm-default\" { font_name = \"Monospace\" } "
+	"widget \"*.TilemDisasmView\" style:application \"tilem-disasm-default\"";
+
+/* Initialize the TilemDisasmView class */
+static void tilem_disasm_view_class_init(TilemDisasmViewClass *klass)
+{
+	GtkTreeViewClass *tv_class = GTK_TREE_VIEW_CLASS(klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+	gtk_rc_parse_string(default_style);
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	widget_class->style_set = &tilem_disasm_view_style_set;
+	widget_class->size_request = &tilem_disasm_view_size_request;
+	widget_class->size_allocate = &tilem_disasm_view_size_allocate;
+	widget_class->button_press_event = &tilem_disasm_view_button_press;
+	widget_class->popup_menu = &tilem_disasm_view_popup_menu;
+	widget_class->unrealize = &tilem_disasm_view_unrealize;
+	tv_class->move_cursor = &tilem_disasm_view_move_cursor;
+	tv_class->row_activated = &tilem_disasm_view_row_activated;
+}
+
+GtkWidget * tilem_disasm_view_new(TilemDebugger *dbg)
+{
+	TilemDisasmView *dv;
+
+	g_return_val_if_fail(dbg != NULL, NULL);
+
+	dv = g_object_new(TILEM_TYPE_DISASM_VIEW, NULL);
+	dv->dbg = dbg;
+
+	return GTK_WIDGET(dv);
+}
+
+/* Select memory addressing mode. */
+void tilem_disasm_view_set_logical(TilemDisasmView *dv, gboolean logical)
+{
+	dword start, curpos;
+	TilemCalc *calc;
+
+	g_return_if_fail(TILEM_IS_DISASM_VIEW(dv));
+	g_return_if_fail(dv->dbg->emu->calc != NULL);
+
+	get_cursor_line(dv, &curpos, NULL);
+
+	if (logical && !dv->use_logical) {
+		tilem_calc_emulator_lock(dv->dbg->emu);
+		calc = dv->dbg->emu->calc;
+		curpos = pos_ptol(calc, curpos);
+		start = pos_ptol(calc, dv->startpos);
+		tilem_calc_emulator_unlock(dv->dbg->emu);
+
+		dv->use_logical = TRUE;
+		refresh_disassembly(dv, start, dv->nlines, curpos);
+	}
+	else if (!logical && dv->use_logical) {
+		tilem_calc_emulator_lock(dv->dbg->emu);
+		calc = dv->dbg->emu->calc;
+		curpos = pos_ltop(calc, curpos);
+		start = pos_ltop(calc, dv->startpos);
+		tilem_calc_emulator_unlock(dv->dbg->emu);
+
+		dv->use_logical = FALSE;
+		refresh_disassembly(dv, start, dv->nlines, curpos);
+	}
+}
+
+/* Refresh contents of view. */
+void tilem_disasm_view_refresh(TilemDisasmView *dv)
+{
+	dword curpos;
+	g_return_if_fail(TILEM_IS_DISASM_VIEW(dv));
+	get_cursor_line(dv, &curpos, NULL);
+	refresh_disassembly(dv, dv->startpos, dv->nlines, curpos);
+}
+
+/* Find tree path for the given position */
+static GtkTreePath *find_path_for_position(GtkTreeModel *model, int pos)
+{
+	gint p;
+	GtkTreeIter iter;
+
+	if (!gtk_tree_model_get_iter_first(model, &iter))
+		return NULL;
+
+	do {
+		gtk_tree_model_get(model, &iter, COL_POSITION, &p, -1);
+		if (p == pos) {
+			return gtk_tree_model_get_path(model, &iter);
+		}
+	} while (gtk_tree_model_iter_next(model, &iter));
+
+	return NULL;
+}
+
+/* Highlight the specified address. */
+void tilem_disasm_view_go_to_address(TilemDisasmView *dv, dword addr,
+                                     gboolean logical)
+{
+	dword pos;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	TilemCalc *calc;
+
+	g_return_if_fail(TILEM_IS_DISASM_VIEW(dv));
+
+	tilem_calc_emulator_lock(dv->dbg->emu);
+	calc = dv->dbg->emu->calc;
+
+	if (logical) {
+		addr &= 0xffff;
+		if (dv->use_logical)
+			pos = ADDR_TO_POS(addr);
+		else
+			pos = pos_ltop(calc, ADDR_TO_POS(addr));
+	}
+	else {
+		if (dv->use_logical) {
+			addr = (*calc->hw.mem_ptol)(calc, addr);
+			if (addr == (dword) -1) {
+				tilem_calc_emulator_unlock(dv->dbg->emu);
+				return;
+			}
+		}
+		pos = ADDR_TO_POS(addr);
+	}
+
+	tilem_calc_emulator_unlock(dv->dbg->emu);
+
+	if (pos >= dv->startpos && pos < dv->endpos) {
+		model = gtk_tree_view_get_model(GTK_TREE_VIEW(dv));
+		path = find_path_for_position(model, pos);
+		if (path) {
+			gtk_tree_view_set_cursor(GTK_TREE_VIEW(dv), path,
+			                         NULL, FALSE);
+			gtk_tree_path_free(path);
+			return;
+		}
+	}
+
+	refresh_disassembly(dv, pos, dv->nlines, pos);
+}
+
+/* Get currently selected address. */
+gboolean tilem_disasm_view_get_cursor(TilemDisasmView *dv, dword *addr,
+                                      gboolean *is_logical)
+{
+	dword pos;
+
+	g_return_val_if_fail(TILEM_IS_DISASM_VIEW(dv), FALSE);
+
+	if (is_logical) *is_logical = dv->use_logical;
+
+	if (!get_cursor_line(dv, &pos, NULL))
+		return FALSE;
+
+	if (addr) *addr = POS_TO_ADDR(pos);
+	return TRUE;
+}
+
diff --git a/tool/tilem-src/gui/disasmview.h b/tool/tilem-src/gui/disasmview.h
new file mode 100644
index 0000000..58463e4
--- /dev/null
+++ b/tool/tilem-src/gui/disasmview.h
@@ -0,0 +1,73 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+G_BEGIN_DECLS
+
+#define TILEM_TYPE_DISASM_VIEW           (tilem_disasm_view_get_type())
+#define TILEM_DISASM_VIEW(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), TILEM_TYPE_DISASM_VIEW, TilemDisasmView))
+#define TILEM_DISASM_VIEW_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST((cls), TILEM_TYPE_DISASM_VIEW, TilemDisasmViewClass))
+#define TILEM_IS_DISASM_VIEW(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), TILEM_TYPE_DISASM_VIEW))
+#define TILEM_IS_DISASM_VIEW_CLASS(cls)  (G_TYPE_CHECK_CLASS_TYPE((cls), TILEM_TYPE_DISASM_VIEW))
+#define TILEM_DISASM_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TILEM_TYPE_DISASM_VIEW, TilemDisasmViewClass))
+
+typedef struct _TilemDisasmView {
+	GtkTreeView parent;
+
+	TilemDebugger *dbg;
+
+	gboolean use_logical;
+
+	int base_height;  /* base height of tree view */
+	int line_height;  /* height of each row */
+
+	dword startpos;   /* position at start of window */
+	dword endpos;     /* position at end of window */
+	int nlines;       /* number of lines visible */
+
+	GtkTreeViewColumn *icon_column;
+	GtkWidget *popup_menu;
+} TilemDisasmView;
+
+typedef struct _TilemDisasmViewClass {
+	GtkTreeViewClass parent_class;
+} TilemDisasmViewClass;
+
+GType tilem_disasm_view_get_type(void) G_GNUC_CONST;
+
+/* Create a new TilemDisasmView. */
+GtkWidget * tilem_disasm_view_new(TilemDebugger *dbg);
+
+/* Select memory addressing mode. */
+void tilem_disasm_view_set_logical(TilemDisasmView *dv, gboolean logical);
+
+/* Refresh contents of view. */
+void tilem_disasm_view_refresh(TilemDisasmView *dv);
+
+/* Highlight the specified address. */
+void tilem_disasm_view_go_to_address(TilemDisasmView *dv, dword addr,
+                                     gboolean logical);
+
+/* Get currently selected address. */
+gboolean tilem_disasm_view_get_cursor(TilemDisasmView *dv, dword *addr,
+                                      gboolean *is_logical);
+
+/* Toggle breakpoint at selected address. */
+void tilem_disasm_view_toggle_breakpoint(TilemDisasmView *dv);
+
+G_END_DECLS
diff --git a/tool/tilem-src/gui/emucore.c b/tool/tilem-src/gui/emucore.c
new file mode 100644
index 0000000..a5840ab
--- /dev/null
+++ b/tool/tilem-src/gui/emucore.c
@@ -0,0 +1,436 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include <scancodes.h>
+
+#include "gui.h"
+#include "emucore.h"
+
+#define MICROSEC_PER_TICK 10000
+
+typedef struct {
+	TilemCalcEmulator *emu;
+	TilemTaskMainFunc mainf;
+	TilemTaskFinishedFunc finishedf;
+	gpointer userdata;
+	gboolean cancelled;
+} Task;
+
+/* Add a task to the queue. */
+void tilem_calc_emulator_begin(TilemCalcEmulator *emu,
+                               TilemTaskMainFunc mainf,
+                               TilemTaskFinishedFunc finishedf,
+                               gpointer data)
+{
+	Task *task;
+
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(mainf != NULL);
+
+	task = g_slice_new0(Task);
+	task->emu = emu;
+	task->mainf = mainf;
+	task->finishedf = finishedf;
+	task->userdata = data;
+
+	tilem_calc_emulator_lock(emu);
+	g_queue_push_tail(emu->task_queue, task);
+	tilem_calc_emulator_unlock(emu);
+}
+
+/* Cancel all pending tasks.  If a task is currently running, this
+   will wait for it to finish. */
+void tilem_calc_emulator_cancel_tasks(TilemCalcEmulator *emu)
+{
+	GQueue *oldqueue;
+	Task *task;
+
+	tilem_calc_emulator_lock(emu);
+	emu->task_abort = TRUE;
+	emu->link_update->cancel = TRUE;
+	oldqueue = emu->task_queue;
+	emu->task_queue = g_queue_new();
+	tilem_calc_emulator_unlock(emu);
+
+	while ((task = g_queue_pop_head(oldqueue))) {
+		if (task->finishedf)
+			(*task->finishedf)(emu, task->userdata, TRUE);
+		g_slice_free(Task, task);
+	}
+
+	g_queue_free(oldqueue);
+
+	g_mutex_lock(emu->calc_mutex);
+	while (emu->task_busy)
+		g_cond_wait(emu->task_finished_cond, emu->calc_mutex);
+	emu->task_abort = FALSE;
+	emu->link_update->cancel = FALSE;
+	g_cond_broadcast(emu->calc_wakeup_cond);
+	g_mutex_unlock(emu->calc_mutex);
+}
+
+/* Check if calculator is powered off */
+static gboolean calc_asleep(TilemCalcEmulator *emu)
+{
+	return (emu->calc->z80.halted
+	        && !emu->calc->z80.interrupts
+	        && !emu->calc->poweronhalt
+	        && !emu->key_queue_timer);
+}
+
+static gboolean refresh_lcd(gpointer data)
+{
+	TilemCalcEmulator* emu = data;
+
+	if (emu->ewin)
+		tilem_emulator_window_refresh_lcd(emu->ewin);
+
+	return FALSE;
+}
+
+/* Update screen for display while paused */
+static void update_screen_mono(TilemCalcEmulator *emu)
+{
+	g_mutex_lock(emu->lcd_mutex);
+
+	tilem_lcd_get_frame(emu->calc, emu->lcd_buffer);
+
+	if (!emu->lcd_update_pending) {
+		emu->lcd_update_pending = TRUE;
+		g_idle_add_full(G_PRIORITY_DEFAULT, &refresh_lcd, emu, NULL);
+	}
+
+	g_mutex_unlock(emu->lcd_mutex);
+}
+
+/* idle callback to update progress bar */
+static gboolean pbar_update(gpointer data)
+{
+	TilemCalcEmulator *emu = data;
+	progress_bar_update(emu);
+	return FALSE;
+}
+
+static void update_progress(TilemCalcEmulator *emu, gboolean force)
+{
+	if (force || emu->progress_changed)
+		g_idle_add(&pbar_update, emu);
+	emu->progress_changed = FALSE;
+}
+
+static gboolean show_debugger(gpointer data)
+{
+	TilemCalcEmulator* emu = data;
+
+	if (emu->dbg)
+		tilem_debugger_show(emu->dbg);
+
+	return FALSE;
+}
+
+#define BREAK_MASK (TILEM_STOP_BREAKPOINT \
+                    | TILEM_STOP_INVALID_INST \
+                    | TILEM_STOP_UNDOCUMENTED_INST)
+
+/* Run one iteration of the emulator. */
+dword tilem_em_run(TilemCalcEmulator *emu, int linkmode,
+                   dword events, dword ff_events, gboolean keep_awake,
+                   int timeout, int *elapsed)
+{
+	dword all_events, ev_auto, ev_user;
+	int rem;
+	gulong tcur, delaytime;
+
+	if (emu->exiting || emu->task_abort) {
+		if (elapsed) *elapsed = 0;
+		return 0;
+	}
+	else if (emu->paused) {
+		update_screen_mono(emu);
+		update_progress(emu, TRUE);
+		g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
+		update_progress(emu, TRUE);
+		g_timer_elapsed(emu->timer, &emu->timevalue);
+		if (elapsed) *elapsed = 0;
+		return 0;
+	}
+	else if (!keep_awake && calc_asleep(emu)) {
+		update_progress(emu, FALSE);
+		update_screen_mono(emu);
+		g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
+		g_timer_elapsed(emu->timer, &emu->timevalue);
+		if (elapsed) *elapsed = timeout;
+		return 0;
+	}
+
+	update_progress(emu, FALSE);
+
+	all_events = events | BREAK_MASK;
+
+	emu->calc->linkport.linkemu = linkmode;
+	emu->calc->z80.stop_mask = ~all_events;
+
+	tilem_z80_run_time(emu->calc, timeout, &rem);
+
+	ev_user = emu->calc->z80.stop_reason & events;
+	ev_auto = emu->calc->z80.stop_reason & ~events;
+
+	if (elapsed) *elapsed = timeout - rem;
+
+	if (ev_auto & BREAK_MASK) {
+		emu->paused = TRUE;
+		g_idle_add(&show_debugger, emu);
+	}
+
+	if (emu->limit_speed
+	    && !(ff_events & ev_user)
+	    && ff_events != TILEM_EM_ALWAYS_FF) {
+		emu->timevalue += timeout - rem;
+		g_timer_elapsed(emu->timer, &tcur);
+
+		/* emu->timevalue is the "ideal" time when the
+		   operation should be completed.  If emu->timevalue
+		   is greater than tcur, we're running faster than
+		   real time.  Try to sleep for (emu->timevalue -
+		   tcur) microseconds.
+
+		   If emu->timevalue is less than tcur, we're running
+		   slower than real time.  If the difference is small,
+		   just keep going and hope we'll catch up later.
+
+		   If the difference is substantial (more than 1/10
+		   second in either direction), re-synchronize. */
+
+		delaytime = emu->timevalue - tcur;
+
+		if (delaytime <= (gulong) 100000 + timeout) {
+			tilem_em_unlock(emu);
+			g_usleep(delaytime);
+			tilem_em_lock(emu);
+		}
+		else {
+			tilem_em_check_yield(emu);
+			if (delaytime < (gulong) -100000)
+				emu->timevalue = tcur;
+		}
+	}
+	else {
+		tilem_em_check_yield(emu);
+	}
+
+	return ev_user;
+}
+
+static gboolean taskfinished(gpointer data)
+{
+	Task *task = data;
+
+	if (task->finishedf)
+		(*task->finishedf)(task->emu, task->userdata, task->cancelled);
+
+	g_slice_free(Task, task);
+	return FALSE;
+}
+
+static void run_task(TilemCalcEmulator *emu, Task *task)
+{
+	gboolean status;
+
+	emu->task_busy = TRUE;
+	status = (*task->mainf)(emu, task->userdata);
+
+	g_idle_add(&taskfinished, task);
+
+	if (!status) {
+		while ((task = g_queue_pop_head(emu->task_queue))) {
+			task->cancelled = TRUE;
+			g_idle_add(&taskfinished, task);
+		}
+	}
+	emu->task_busy = FALSE;
+}
+
+/* Main loop */
+gpointer tilem_em_main(gpointer data)
+{
+	TilemCalcEmulator *emu = data;
+	Task *task;
+
+	tilem_em_lock(emu);
+
+	g_timer_start(emu->timer);
+	g_timer_elapsed(emu->timer, &emu->timevalue);
+
+	while (!emu->exiting) {
+		task = g_queue_pop_head(emu->task_queue);
+		if (task) {
+			run_task(emu, task);
+		}
+		else if (emu->task_abort) {
+			g_cond_broadcast(emu->task_finished_cond);
+			g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
+		}
+		else {
+			tilem_em_run(emu, 0, 0, 0, FALSE,
+			             MICROSEC_PER_TICK, NULL);
+		}
+	}
+
+	tilem_em_unlock(emu);
+	return NULL;
+}
+
+/* Run the calculator for a short time. */
+void tilem_em_delay(TilemCalcEmulator *emu, int timeout, gboolean ff)
+{
+	int t;
+	G_GNUC_UNUSED dword events;
+
+	while (!emu->task_abort && timeout > 0) {
+		t = MIN(MICROSEC_PER_TICK, timeout);
+		events = tilem_em_run(emu, 0, 0,
+		                      (ff ? TILEM_EM_ALWAYS_FF : 0), TRUE,
+		                      t, &t);
+		timeout -= t;
+	}
+}
+
+#define LINK_EVENTS (TILEM_STOP_LINK_READ_BYTE \
+                     | TILEM_STOP_LINK_WRITE_BYTE \
+                     | TILEM_STOP_LINK_ERROR)
+
+static int run_until_ready(TilemCalcEmulator *emu, int timeout, gboolean ff)
+{
+	int t;
+	dword events;
+
+	emu->calc->linkport.linkemu = TILEM_LINK_EMULATOR_GRAY;
+	while (!emu->task_abort && timeout > 0) {
+		if (tilem_linkport_graylink_ready(emu->calc))
+			return 0;
+
+		t = MIN(MICROSEC_PER_TICK, timeout);
+		events = tilem_em_run(emu, TILEM_LINK_EMULATOR_GRAY,
+		                      LINK_EVENTS, (ff ? LINK_EVENTS : 0), TRUE,
+		                      t, &t);
+
+		timeout -= t;
+		if (events & TILEM_STOP_LINK_ERROR)
+			break;
+	}
+	return -1;
+}
+
+/* Send a byte to the calculator. */
+int tilem_em_send_byte(TilemCalcEmulator *emu, unsigned value,
+                       int timeout, gboolean ff)
+{
+	if (run_until_ready(emu, timeout, ff))
+		return -1;
+	if (tilem_linkport_graylink_send_byte(emu->calc, value))
+		return -1;
+	if (run_until_ready(emu, timeout, ff))
+		return -1;
+	return 0;
+}
+
+/* Receive a byte from the calculator. */
+int tilem_em_get_byte(TilemCalcEmulator *emu, int timeout, gboolean ff)
+{
+	int t, v;
+	dword events;
+
+	while (!emu->task_abort && timeout > 0) {
+		v = tilem_linkport_graylink_get_byte(emu->calc);
+		if (v >= 0)
+			return v;
+
+		t = MIN(MICROSEC_PER_TICK, timeout);
+		events = tilem_em_run(emu, TILEM_LINK_EMULATOR_GRAY,
+		                      LINK_EVENTS, (ff ? LINK_EVENTS : 0), FALSE,
+		                      t, &t);
+		timeout -= t;
+		if (events & TILEM_STOP_LINK_ERROR)
+			break;
+	}
+	return -1;
+}
+
+/* Wake up calculator if currently turned off. */
+void tilem_em_wake_up(TilemCalcEmulator *emu, gboolean ff)
+{
+	tilem_em_delay(emu, 1000000, ff);
+
+	if (!calc_asleep(emu))
+		return;
+
+	tilem_keypad_press_key(emu->calc, TILEM_KEY_ON);
+	tilem_em_delay(emu, 500000, ff);
+	tilem_keypad_release_key(emu->calc, TILEM_KEY_ON);
+	tilem_em_delay(emu, 500000, ff);
+}
+
+/* Set progress window title.  Set TITLE to NULL to disable progress
+   window. */
+void tilem_em_set_progress_title(TilemCalcEmulator *emu, const char *title)
+{
+	g_mutex_lock(emu->pbar_mutex);
+	g_free(emu->pbar_title);
+	g_free(emu->pbar_status);
+	emu->pbar_title = title ? g_strdup(title) : NULL;
+	emu->pbar_status = NULL;
+	emu->pbar_progress = 0.0;
+	if (!emu->pbar_update_pending)
+		emu->progress_changed = TRUE;
+	emu->pbar_update_pending = TRUE;
+	g_mutex_unlock(emu->pbar_mutex);
+}
+
+/* Set current progress information.  FRAC is the estimated fraction
+   of the task completed; STATUS is a text description of the current
+   operation. */
+void tilem_em_set_progress(TilemCalcEmulator *emu, gdouble frac,
+                           const char *status)
+{
+	g_mutex_lock(emu->pbar_mutex);
+
+	if (!emu->pbar_status || !status
+	    || strcmp(status, emu->pbar_status)) {
+		g_free(emu->pbar_status);
+		emu->pbar_status = status ? g_strdup(status) : NULL;
+	}
+
+	emu->pbar_progress = frac;
+
+	if (!emu->pbar_update_pending)
+		emu->progress_changed = TRUE;
+	emu->pbar_update_pending = TRUE;
+
+	g_mutex_unlock(emu->pbar_mutex);
+}
diff --git a/tool/tilem-src/gui/emucore.h b/tool/tilem-src/gui/emucore.h
new file mode 100644
index 0000000..391b17f
--- /dev/null
+++ b/tool/tilem-src/gui/emucore.h
@@ -0,0 +1,95 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* IMPORTANT: The following functions may ONLY be used within
+   tilem_em_main() or a task function, and must be called while
+   holding the emulator lock. */
+
+#define TILEM_EM_ALWAYS_FF 0xffffffff
+
+/* Run one iteration of the emulator.  LINKMODE is the link port
+   emulation mode.  EVENTS is a mask of events we are interested in;
+   emulation will stop early if any of these events occur, or possibly
+   for other reasons (e.g., a breakpoint being hit.)
+
+   FF_EVENTS is a mask of events we want to fast-forward through
+   (i.e., not apply speed limiting even if enabled.)  If FF_EVENTS is
+   set to the constant ALWAYS_FF, speed limiting is completely
+   disabled.
+
+   If KEEP_AWAKE is FALSE and the calculator CPU is turned off, this
+   function may block until another thread wakes it up; in that case,
+   no timer events will be triggered, and the elapsed time cannot be
+   measured in any meaningful way.  If KEEP_AWAKE is TRUE, the
+   emulator continues running even when the CPU is turned off.
+
+   TIMEOUT is the length of time (microseconds) to run the emulator.
+   If ELAPSED is non-null, *ELAPSED will be set to the actual number
+   of microseconds elapsed.
+
+   The return value is a mask indicating which of the requested events
+   occurred. */
+dword tilem_em_run(TilemCalcEmulator *emu, int linkmode,
+                   dword events, dword ff_events, gboolean keep_awake,
+                   int timeout, int *elapsed);
+
+/* Main loop */
+gpointer tilem_em_main(gpointer data);
+
+/* Run the calculator for a short time. */
+void tilem_em_delay(TilemCalcEmulator *emu, int timeout, gboolean ff);
+
+/* Send a byte to the calculator. */
+int tilem_em_send_byte(TilemCalcEmulator *emu, unsigned value,
+                       int timeout, gboolean ff);
+
+/* Receive a byte from the calculator. */
+int tilem_em_get_byte(TilemCalcEmulator *emu, int timeout, gboolean ff);
+
+/* Wake up calculator if currently turned off. */
+void tilem_em_wake_up(TilemCalcEmulator *emu, gboolean ff);
+
+/* Set progress window title.  Set TITLE to NULL to disable progress
+   window. */
+void tilem_em_set_progress_title(TilemCalcEmulator *emu, const char *title);
+
+/* Set current progress information.  FRAC is the estimated fraction
+   of the task completed; STATUS is a text description of the current
+   operation. */
+void tilem_em_set_progress(TilemCalcEmulator *emu, gdouble frac,
+                           const char *status);
+
+/* Lock emulator. */
+#define tilem_em_lock(emu) \
+	g_mutex_lock(emu->calc_mutex)
+
+/* Unlock temporarily if another thread is waiting. */
+#define tilem_em_check_yield(emu) \
+	do { \
+		if (g_atomic_int_get(&emu->calc_lock_waiting)) \
+			g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex); \
+	} while (0)
+
+/* Unlock emulator. */
+#define tilem_em_unlock(emu) \
+	do { \
+		tilem_em_check_yield(emu); \
+		g_mutex_unlock(emu->calc_mutex); \
+	} while (0)
+
diff --git a/tool/tilem-src/gui/emulator.c b/tool/tilem-src/gui/emulator.c
new file mode 100644
index 0000000..1cd68a3
--- /dev/null
+++ b/tool/tilem-src/gui/emulator.c
@@ -0,0 +1,899 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "emucore.h"
+#include "msgbox.h"
+#include "filedlg.h"
+
+#define MILLISEC_PER_FRAME 30
+#define MICROSEC_PER_FRAME (MILLISEC_PER_FRAME * 1000)
+
+#define GRAY_WINDOW_SIZE 4
+#define GRAY_SAMPLE_INT 200
+
+
+/* Lock emulator.  Notify the core loop that we wish to do so - note
+   that if the core is running at full speed, it keeps the mutex
+   locked almost all the time, so we need to explicitly ask it to
+   yield.  (This may not be necessary with all mutex implementations,
+   but definitely is necessary with some.) */
+void tilem_calc_emulator_lock(TilemCalcEmulator *emu)
+{
+	g_atomic_int_inc(&emu->calc_lock_waiting);
+	g_mutex_lock(emu->calc_mutex);
+}
+
+/* Unlock emulator and (if no other threads are waiting to lock it)
+   wake up the core thread.  This also serves to resume emulation if
+   the calculator has been powered off. */
+void tilem_calc_emulator_unlock(TilemCalcEmulator *emu)
+{
+	if (g_atomic_int_dec_and_test(&emu->calc_lock_waiting))
+		g_cond_signal(emu->calc_wakeup_cond);
+	g_mutex_unlock(emu->calc_mutex);
+}
+
+static gboolean refresh_lcd(gpointer data)
+{
+	TilemCalcEmulator* emu = data;
+
+	if (emu->ewin)
+		tilem_emulator_window_refresh_lcd(emu->ewin);
+
+	return FALSE;
+}
+
+static void tmr_screen_update(TilemCalc *calc, void *data)
+{
+	TilemCalcEmulator *emu = data;
+
+	g_mutex_lock(emu->lcd_mutex);
+
+	if (emu->glcd)
+		tilem_gray_lcd_get_frame(emu->glcd, emu->lcd_buffer);
+	else
+		tilem_lcd_get_frame(calc, emu->lcd_buffer);
+
+	if (emu->anim) {
+		if (emu->anim_grayscale) {
+			tilem_animation_append_frame(emu->anim,
+			                             emu->lcd_buffer,
+			                             MILLISEC_PER_FRAME);
+		}
+		else {
+			tilem_lcd_get_frame(calc, emu->tmp_lcd_buffer);
+			tilem_animation_append_frame(emu->anim,
+			                             emu->tmp_lcd_buffer,
+			                             MILLISEC_PER_FRAME);
+		}
+	}
+
+	if (!emu->lcd_update_pending) {
+		emu->lcd_update_pending = TRUE;
+		g_idle_add_full(G_PRIORITY_DEFAULT, &refresh_lcd, emu, NULL);
+	}
+
+	g_mutex_unlock(emu->lcd_mutex);
+}
+
+static void cancel_animation(TilemCalcEmulator *emu)
+{
+	if (emu->anim)
+		g_object_unref(emu->anim);
+	emu->anim = NULL;
+}
+
+static GtkWidget *get_toplevel(TilemCalcEmulator *emu)
+{
+	if (emu->ewin)
+		return emu->ewin->window;
+	else
+		return NULL;
+}
+
+static void link_update_nop()
+{
+}
+
+TilemCalcEmulator *tilem_calc_emulator_new()
+{
+	TilemCalcEmulator *emu = g_new0(TilemCalcEmulator, 1);
+	CalcUpdate *update;
+
+	emu->calc_mutex = g_mutex_new();
+	emu->calc_wakeup_cond = g_cond_new();
+	emu->lcd_mutex = g_mutex_new();
+
+	tilem_config_get("emulation",
+	                 "grayscale/b=1", &emu->grayscale,
+	                 "limit_speed/b=1", &emu->limit_speed,
+	                 NULL);
+
+	emu->task_queue = g_queue_new();
+	emu->task_finished_cond = g_cond_new();
+
+	emu->timer = g_timer_new();
+
+	emu->pbar_mutex = g_mutex_new();
+
+	update = g_new0(CalcUpdate, 1);
+	update->start = &link_update_nop;
+	update->stop = &link_update_nop;
+	update->refresh = &link_update_nop;
+	update->pbar = &link_update_nop;
+	update->label = &link_update_nop;
+	emu->link_update = update;
+
+	return emu;
+}
+
+void tilem_calc_emulator_free(TilemCalcEmulator *emu)
+{
+	g_return_if_fail(emu != NULL);
+
+	tilem_calc_emulator_cancel_tasks(emu);
+
+	tilem_calc_emulator_lock(emu);
+	cancel_animation(emu);
+	emu->exiting = TRUE;
+	tilem_calc_emulator_unlock(emu);
+
+	if (emu->z80_thread)
+		g_thread_join(emu->z80_thread);
+
+	g_free(emu->key_queue);
+
+	g_free(emu->rom_file_name);
+	g_free(emu->state_file_name);
+
+	g_mutex_free(emu->calc_mutex);
+	g_mutex_free(emu->lcd_mutex);
+	g_cond_free(emu->calc_wakeup_cond);
+
+	g_cond_free(emu->task_finished_cond);
+	g_queue_free(emu->task_queue);
+
+	g_timer_destroy(emu->timer);
+
+	g_mutex_free(emu->pbar_mutex);
+	g_free(emu->link_update);
+
+	if (emu->lcd_buffer)
+		tilem_lcd_buffer_free(emu->lcd_buffer);
+	if (emu->tmp_lcd_buffer)
+		tilem_lcd_buffer_free(emu->tmp_lcd_buffer);
+	if (emu->glcd)
+		tilem_gray_lcd_free(emu->glcd);
+	if (emu->calc)
+		tilem_calc_free(emu->calc);
+
+	g_free(emu);
+}
+
+static char *get_sav_name(const char *romname)
+{
+	char *dname, *bname, *sname, *suff;
+
+	dname = g_path_get_dirname(romname);
+	bname = g_path_get_basename(romname);
+
+	if ((suff = strrchr(bname, '.')))
+		*suff = 0;
+	sname = g_strconcat(dname, G_DIR_SEPARATOR_S, bname, ".sav", NULL);
+
+	g_free(dname);
+	g_free(bname);
+	return sname;
+}
+
+gboolean tilem_calc_emulator_load_state(TilemCalcEmulator *emu,
+                                        const char *romfname,
+                                        const char *statefname,
+                                        int model, GError **err)
+{
+	const char *modelname;
+	FILE *romfile, *savfile;
+	char *rname = NULL, *sname = NULL;
+	TilemCalc *calc;
+	char *dname;
+	int errnum;
+
+	g_return_val_if_fail(emu != NULL, FALSE);
+	g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+	tilem_calc_emulator_cancel_tasks(emu);
+
+	if (romfname)
+		rname = g_strdup(romfname);
+	if (!sname && statefname)
+		sname = g_strdup(statefname);
+
+	/* Choose ROM file */
+
+	if (!rname && model) {
+		modelname = model_to_name(model);
+		g_return_val_if_fail(modelname != NULL, FALSE);
+		if (sname) g_free(sname);
+		tilem_config_get(modelname,
+		                 "rom_file/f", &rname,
+		                 "state_file/f", &sname,
+		                 NULL);
+	}
+
+	if (!rname) {
+		g_set_error(err, TILEM_EMULATOR_ERROR,
+		            TILEM_EMULATOR_ERROR_NO_ROM,
+		            "No ROM file specified");
+		g_free(rname);
+		g_free(sname);
+		return FALSE;
+	}
+
+	/* Open ROM file */
+
+	romfile = g_fopen(rname, "rb");
+	if (!romfile) {
+		errnum = errno;
+		dname = g_filename_display_basename(rname);
+		g_set_error(err, G_FILE_ERROR,
+		            g_file_error_from_errno(errnum),
+		            "Unable to open %s for reading: %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		g_free(rname);
+		g_free(sname);
+		return FALSE;
+	}
+
+	/* Open state file */
+
+	if (!sname)
+		sname = get_sav_name(rname);
+
+	savfile = g_fopen(sname, "rb");
+
+	if (!savfile && errno != ENOENT) {
+		errnum = errno;
+		dname = g_filename_display_basename(sname);
+		g_set_error(err, G_FILE_ERROR,
+		            g_file_error_from_errno(errnum),
+		            "Unable to open %s for reading: %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		g_free(rname);
+		g_free(sname);
+		fclose(romfile);
+		return FALSE;
+	}
+
+	/* Determine model from state file, if possible */
+
+	if (!model && savfile)
+		model = tilem_get_sav_type(savfile);
+
+	/* Otherwise, guess from ROM file; ask user if ambiguous */
+
+	if (!model) {
+		model = tilem_guess_rom_type(romfile);
+		if (model) {
+			model = choose_rom_popup(get_toplevel(emu),
+						 rname, model);
+		}
+		else {
+			dname = g_filename_display_basename(rname);
+			g_set_error(err, TILEM_EMULATOR_ERROR,
+			            TILEM_EMULATOR_ERROR_INVALID_ROM,
+			            "The file %s is not a recognized"
+			            " calculator ROM file.",
+			            dname);
+			g_free(dname);
+		}
+	}
+
+	if (!model) {
+		fclose(romfile);
+		if (savfile) fclose(savfile);
+		g_free(rname);
+		g_free(sname);
+		return FALSE;
+	}
+
+	/* Create new calc, and load state */
+
+	calc = tilem_calc_new(model);
+	if (tilem_calc_load_state(calc, romfile, savfile)) {
+		g_set_error(err, TILEM_EMULATOR_ERROR,
+		            TILEM_EMULATOR_ERROR_INVALID_STATE,
+		            "The specified ROM or state file is invalid.");
+		fclose(romfile);
+		if (savfile) fclose(savfile);
+		g_free(rname);
+		g_free(sname);
+		return FALSE;
+	}
+
+	if (!savfile) {
+		/* save model as default for the future */
+		savfile = g_fopen(sname, "wb");
+		if (savfile)
+			fprintf(savfile, "MODEL = %s\n", calc->hw.name);
+	}
+
+	fclose(romfile);
+	if (savfile) fclose(savfile);
+
+	/* Switch to new calc */
+
+	tilem_calc_emulator_lock(emu);
+
+	cancel_animation(emu);
+
+	if (emu->glcd)
+		tilem_gray_lcd_free(emu->glcd);
+	if (emu->calc)
+		tilem_calc_free(emu->calc);
+
+	emu->calc = calc;
+	emu->lcd_buffer = tilem_lcd_buffer_new();
+	emu->tmp_lcd_buffer = tilem_lcd_buffer_new();
+
+	if (emu->grayscale)
+		emu->glcd = tilem_gray_lcd_new(calc, GRAY_WINDOW_SIZE,
+		                               GRAY_SAMPLE_INT);
+	else
+		emu->glcd = NULL;
+
+	tilem_z80_add_timer(calc, MICROSEC_PER_FRAME,
+	                    MICROSEC_PER_FRAME, 1,
+	                    &tmr_screen_update, emu);
+
+	tilem_calc_emulator_unlock(emu);
+
+	if (emu->rom_file_name)
+		g_free(emu->rom_file_name);
+	emu->rom_file_name = rname;
+
+	if (emu->state_file_name)
+		g_free(emu->state_file_name);
+	emu->state_file_name = sname;
+
+	tilem_keybindings_init(emu, calc->hw.name);
+
+	if (emu->ewin)
+		tilem_emulator_window_calc_changed(emu->ewin);
+	if (emu->dbg)
+		tilem_debugger_calc_changed(emu->dbg);
+
+	if (emu->rcvdlg)
+		tilem_receive_dialog_free(emu->rcvdlg);
+	emu->rcvdlg = NULL;
+
+	return TRUE;
+}
+
+gboolean tilem_calc_emulator_revert_state(TilemCalcEmulator *emu, GError **err)
+{
+	FILE *romfile, *savfile;
+	char *dname;
+	int errnum = 0;
+	gboolean status = TRUE;
+
+	g_return_val_if_fail(emu != NULL, FALSE);
+	g_return_val_if_fail(emu->calc != NULL, FALSE);
+	g_return_val_if_fail(emu->rom_file_name != NULL, FALSE);
+	g_return_val_if_fail(emu->state_file_name != NULL, FALSE);
+	g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+	/* Open ROM file */
+
+	if (emu->calc->hw.flags & TILEM_CALC_HAS_FLASH) {
+		romfile = g_fopen(emu->rom_file_name, "rb");
+		if (!romfile) {
+			errnum = errno;
+			dname = g_filename_display_basename(emu->rom_file_name);
+			g_set_error(err, G_FILE_ERROR,
+			            g_file_error_from_errno(errnum),
+			            "Unable to open %s for reading: %s",
+			            dname, g_strerror(errnum));
+			g_free(dname);
+			return FALSE;
+		}
+	}
+	else {
+		romfile = NULL;
+	}
+
+	/* Open state file */
+
+	savfile = g_fopen(emu->state_file_name, "rb");
+	if (!savfile) {
+		errnum = errno;
+		dname = g_filename_display_basename(emu->state_file_name);
+		g_set_error(err, G_FILE_ERROR,
+		            g_file_error_from_errno(errnum),
+		            "Unable to open %s for reading: %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		if (romfile) fclose(romfile);
+		return FALSE;
+	}
+
+	/* Read state */
+
+	tilem_calc_emulator_lock(emu);
+
+	if (tilem_calc_load_state(emu->calc, romfile, savfile)) {
+		g_set_error(err, TILEM_EMULATOR_ERROR,
+		            TILEM_EMULATOR_ERROR_INVALID_STATE,
+		            "The specified ROM or state file is invalid.");
+		status = FALSE;
+	}
+
+	tilem_calc_emulator_unlock(emu);
+
+	if (emu->dbg)
+		tilem_debugger_refresh(emu->dbg, TRUE);
+
+	if (romfile) fclose(romfile);
+	fclose(savfile);
+	return status;
+}
+
+gboolean tilem_calc_emulator_save_state(TilemCalcEmulator *emu, GError **err)
+{
+	FILE *romfile, *savfile;
+	char *dname;
+	int errnum = 0;
+
+	g_return_val_if_fail(emu != NULL, FALSE);
+	g_return_val_if_fail(emu->calc != NULL, FALSE);
+	g_return_val_if_fail(emu->rom_file_name != NULL, FALSE);
+	g_return_val_if_fail(emu->state_file_name != NULL, FALSE);
+	g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
+
+	/* Open ROM file */
+
+	if (emu->calc->hw.flags & TILEM_CALC_HAS_FLASH) {
+		romfile = g_fopen(emu->rom_file_name, "r+b");
+		if (!romfile) {
+			errnum = errno;
+			dname = g_filename_display_basename(emu->rom_file_name);
+			g_set_error(err, G_FILE_ERROR,
+			            g_file_error_from_errno(errnum),
+			            "Unable to open %s for writing: %s",
+			            dname, g_strerror(errnum));
+			g_free(dname);
+			return FALSE;
+		}
+	}
+	else {
+		romfile = NULL;
+	}
+
+	/* Open state file */
+
+	savfile = g_fopen(emu->state_file_name, "wb");
+	if (!savfile) {
+		errnum = errno;
+		dname = g_filename_display_basename(emu->state_file_name);
+		g_set_error(err, G_FILE_ERROR,
+		            g_file_error_from_errno(errnum),
+		            "Unable to open %s for writing: %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		if (romfile) fclose(romfile);
+		return FALSE;
+	}
+
+	/* Write state */
+
+	tilem_calc_emulator_lock(emu);
+
+	if (romfile && tilem_calc_save_state(emu->calc, romfile, NULL))
+		errnum = errno;
+	if (romfile && fclose(romfile))
+		errnum = errno;
+
+	if (errnum) {
+		dname = g_filename_display_basename(emu->rom_file_name);
+		g_set_error(err, G_FILE_ERROR,
+		            g_file_error_from_errno(errnum),
+		            "Error writing %s: %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		fclose(savfile);
+		tilem_calc_emulator_unlock(emu);
+		return FALSE;
+	}
+
+	if (tilem_calc_save_state(emu->calc, NULL, savfile))
+		errnum = errno;
+	if (fclose(savfile))
+		errnum = errno;
+
+	tilem_calc_emulator_unlock(emu);
+
+	if (errnum) {
+		dname = g_filename_display_basename(emu->state_file_name);
+		g_set_error(err, G_FILE_ERROR,
+		            g_file_error_from_errno(errnum),
+		            "Error writing %s: %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+void tilem_calc_emulator_reset(TilemCalcEmulator *emu)
+{
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(emu->calc != NULL);
+
+	tilem_calc_emulator_lock(emu);
+	tilem_calc_reset(emu->calc);
+	tilem_calc_emulator_unlock(emu);
+
+	if (emu->dbg)
+		tilem_debugger_refresh(emu->dbg, TRUE);
+}
+
+void tilem_calc_emulator_pause(TilemCalcEmulator *emu)
+{
+	g_return_if_fail(emu != NULL);
+
+	tilem_calc_emulator_lock(emu);
+	emu->paused = TRUE;
+	tilem_calc_emulator_unlock(emu);
+}
+
+void tilem_calc_emulator_run(TilemCalcEmulator *emu)
+{
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(emu->calc != NULL);
+
+	tilem_calc_emulator_lock(emu);
+	emu->paused = FALSE;
+	tilem_calc_emulator_unlock(emu);
+
+	if (!emu->z80_thread)
+		emu->z80_thread = g_thread_create(&tilem_em_main, emu, TRUE, NULL);
+}
+
+void tilem_calc_emulator_set_limit_speed(TilemCalcEmulator *emu,
+                                         gboolean limit)
+{
+	emu->limit_speed = limit;
+}
+
+void tilem_calc_emulator_set_grayscale(TilemCalcEmulator *emu,
+                                       gboolean grayscale)
+{
+	emu->grayscale = grayscale;
+
+	if (grayscale && emu->calc && !emu->glcd) {
+		tilem_calc_emulator_lock(emu);
+		emu->glcd = tilem_gray_lcd_new(emu->calc, GRAY_WINDOW_SIZE,
+		                               GRAY_SAMPLE_INT);
+		tilem_calc_emulator_unlock(emu);
+	}
+	else if (!grayscale && emu->glcd) {
+		tilem_calc_emulator_lock(emu);
+		tilem_gray_lcd_free(emu->glcd);
+		emu->glcd = NULL;
+		tilem_calc_emulator_unlock(emu);
+	}
+}
+
+/* If currently recording a macro, record a keypress */
+static void record_key(TilemCalcEmulator* emu, int code)
+{
+	char* codechar;
+	int type = 0;
+
+	if (emu->isMacroRecording) {
+		codechar = g_strdup_printf("%04d", code);
+		tilem_macro_add_action(emu->macro, type, codechar);     
+		g_free(codechar);
+	}
+}
+
+void tilem_calc_emulator_press_key(TilemCalcEmulator *emu, int key)
+{
+	g_return_if_fail(emu != NULL);
+
+	if (key == 0)
+		return;
+
+	tilem_calc_emulator_lock(emu);
+	tilem_keypad_press_key(emu->calc, key);
+	tilem_calc_emulator_unlock(emu);
+
+	record_key(emu, key);
+
+	if (emu->dbg && emu->dbg->keypad_dialog)
+		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
+}
+
+void tilem_calc_emulator_release_key(TilemCalcEmulator *emu, int key)
+{
+	g_return_if_fail(emu != NULL);
+
+	if (key == 0)
+		return;
+
+	tilem_calc_emulator_lock(emu);
+	tilem_keypad_release_key(emu->calc, key);
+	tilem_calc_emulator_unlock(emu);
+
+	if (emu->dbg && emu->dbg->keypad_dialog)
+		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
+}
+
+static gboolean refresh_kpd(gpointer data)
+{
+	TilemCalcEmulator *emu = data;
+
+	if (emu->dbg && emu->dbg->keypad_dialog)
+		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
+
+	return FALSE;
+}
+
+/* Timer callback for key sequences */
+static void tmr_key_queue(TilemCalc* calc, void* data)
+{
+	TilemCalcEmulator *emu = data;
+	int nextkey;
+
+	if (emu->key_queue_pressed) {
+		if (emu->key_queue_len > 0 || !emu->key_queue_hold) {
+			tilem_keypad_release_key(calc, emu->key_queue_cur);
+			emu->key_queue_pressed = 0;
+			emu->key_queue_cur = 0;
+			tilem_z80_set_timer(calc, emu->key_queue_timer,
+			                    50000, 0, 1);
+		}
+		else {
+			tilem_z80_remove_timer(calc, emu->key_queue_timer);
+			emu->key_queue_timer = 0;
+		}
+	}
+	else {
+		if (emu->key_queue_len > 0) {
+			nextkey = emu->key_queue[--emu->key_queue_len];
+			tilem_keypad_press_key(calc, nextkey);
+			emu->key_queue_pressed = 1;
+			emu->key_queue_cur = nextkey;
+			tilem_z80_set_timer(calc, emu->key_queue_timer,
+			                    20000, 0, 1);
+		}
+		else {
+			tilem_z80_remove_timer(calc, emu->key_queue_timer);
+			emu->key_queue_timer = 0;
+		}
+	}
+
+	g_idle_add(&refresh_kpd, emu);
+}
+
+static void queue_keys(TilemCalcEmulator *emu, const byte *keys, int nkeys)
+{
+	byte *q;
+	int i;
+
+	q = g_new(byte, emu->key_queue_len + nkeys);
+
+	for (i = 0; i < nkeys; i++) {
+		q[nkeys - i - 1] = keys[i];
+		record_key(emu, keys[i]);
+	}
+
+	if (emu->key_queue_len)
+		memcpy(q + nkeys, emu->key_queue, emu->key_queue_len);
+
+	g_free(emu->key_queue);
+	emu->key_queue = q;
+	emu->key_queue_len += nkeys;
+	emu->key_queue_hold = 1;
+
+	if (!emu->key_queue_timer) {
+		emu->key_queue_timer
+			= tilem_z80_add_timer(emu->calc, 1, 0, 1,
+			                      &tmr_key_queue, emu);
+	}
+}
+
+void tilem_calc_emulator_queue_keys(TilemCalcEmulator *emu,
+                                    const byte *keys, int nkeys)
+{
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(keys != NULL);
+	g_return_if_fail(nkeys > 0);
+
+	tilem_calc_emulator_lock(emu);
+	queue_keys(emu, keys, nkeys);
+	tilem_calc_emulator_unlock(emu);
+}
+
+void tilem_calc_emulator_release_queued_key(TilemCalcEmulator *emu)
+{
+	g_return_if_fail(emu != NULL);
+
+	tilem_calc_emulator_lock(emu);
+	if (emu->key_queue_timer) {
+		emu->key_queue_hold = 0;
+	}
+	else if (emu->key_queue_pressed) {
+		tilem_keypad_release_key(emu->calc, emu->key_queue_cur);
+		emu->key_queue_pressed = 0;
+		emu->key_queue_cur = 0;
+	}
+	tilem_calc_emulator_unlock(emu);
+
+	if (emu->dbg && emu->dbg->keypad_dialog)
+		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
+}
+
+gboolean tilem_calc_emulator_press_or_queue(TilemCalcEmulator *emu,
+                                            int key)
+{
+	byte b;
+	gboolean status;
+
+	g_return_val_if_fail(emu != NULL, FALSE);
+
+	tilem_calc_emulator_lock(emu);
+
+	if (emu->key_queue_timer) {
+		b = key;
+		queue_keys(emu, &b, 1);
+		status = TRUE;
+	}
+	else {
+		tilem_keypad_press_key(emu->calc, key);
+		status = FALSE;
+	}
+	tilem_calc_emulator_unlock(emu);
+
+	if (emu->dbg && emu->dbg->keypad_dialog)
+		tilem_keypad_dialog_refresh(emu->dbg->keypad_dialog);
+
+	return status;
+}
+
+TilemAnimation * tilem_calc_emulator_get_screenshot(TilemCalcEmulator *emu,
+                                                    gboolean grayscale)
+{
+	TilemAnimation *anim;
+
+	g_return_val_if_fail(emu != NULL, NULL);
+	g_return_val_if_fail(emu->calc != NULL, NULL);
+
+	anim = tilem_animation_new(emu->calc->hw.lcdwidth,
+	                           emu->calc->hw.lcdheight);
+
+	tilem_calc_emulator_lock(emu);
+
+	if (grayscale && emu->glcd)
+		tilem_gray_lcd_get_frame(emu->glcd, emu->tmp_lcd_buffer);
+	else
+		tilem_lcd_get_frame(emu->calc, emu->tmp_lcd_buffer);
+
+	tilem_animation_append_frame(anim, emu->tmp_lcd_buffer, 1);
+
+	tilem_calc_emulator_unlock(emu);
+
+	return anim;
+}
+
+void tilem_calc_emulator_begin_animation(TilemCalcEmulator *emu,
+                                         gboolean grayscale)
+{
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(emu->calc != NULL);
+
+	tilem_calc_emulator_lock(emu);
+	cancel_animation(emu);
+	emu->anim = tilem_animation_new(emu->calc->hw.lcdwidth,
+	                                emu->calc->hw.lcdheight);
+	emu->anim_grayscale = grayscale;
+	tilem_calc_emulator_unlock(emu);
+}
+
+TilemAnimation * tilem_calc_emulator_end_animation(TilemCalcEmulator *emu)
+{
+	TilemAnimation *anim;
+
+	g_return_val_if_fail(emu != NULL, NULL);
+	g_return_val_if_fail(emu->anim != NULL, NULL);
+
+	tilem_calc_emulator_lock(emu);
+	anim = emu->anim;
+	emu->anim = NULL;
+	tilem_calc_emulator_unlock(emu);
+
+	return anim;
+}
+
+/* Prompt for a ROM file to open */
+int tilem_calc_emulator_prompt_open_rom(TilemCalcEmulator *emu)
+{
+	char *dir, *filename;
+	GError *err = NULL;
+	const char *modelname;
+
+	if (emu->rom_file_name)
+		dir = g_path_get_dirname(emu->rom_file_name);
+	else
+		dir = g_get_current_dir();
+
+	filename = prompt_open_file("Open Calculator", GTK_WINDOW(get_toplevel(emu)),
+	                            dir, "ROM files", "*.rom;*.clc;*.bin",
+	                            "All files", "*", NULL);
+	g_free(dir);
+	if (!filename)
+		return 0;
+
+	if (tilem_calc_emulator_load_state(emu, filename, NULL,
+	                                   0, &err)) {
+		modelname = emu->calc->hw.name;
+		tilem_config_set(modelname,
+		                 "rom_file/f", emu->rom_file_name,
+		                 "state_file/f", emu->state_file_name,
+		                 NULL);
+		tilem_config_set("recent", "last_model/s", modelname, NULL);
+	}
+	g_free(filename);
+
+	if (err) {
+		messagebox01(GTK_WINDOW(get_toplevel(emu)), GTK_MESSAGE_ERROR,
+		             "Unable to load calculator state",
+		             "%s", err->message);
+		g_error_free(err);
+		return -1;
+	}
+	else {
+		return 1;
+	}
+}
+
+/* Run slowly to play macro (used instead run_with_key() function) */
+void run_with_key_slowly(TilemCalc* calc, int key)
+{
+	tilem_z80_run_time(calc, 5000000, NULL); /* Wait */
+	tilem_keypad_press_key(calc, key); /* Press */
+	tilem_z80_run_time(calc, 10000, NULL); /* Wait (don't forget to wait) */
+	tilem_keypad_release_key(calc, key); /* Release */
+	tilem_z80_run_time(calc, 50, NULL); /* Wait */
+}
diff --git a/tool/tilem-src/gui/emulator.h b/tool/tilem-src/gui/emulator.h
new file mode 100644
index 0000000..640006f
--- /dev/null
+++ b/tool/tilem-src/gui/emulator.h
@@ -0,0 +1,263 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011-2012 Benjamin Moody
+ * Copyright (c) 2011 Duponchelle Thibault
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Key binding */
+typedef struct _TilemKeyBinding {
+	unsigned int keysym;     /* host keysym value */
+	unsigned int modifiers;  /* modifier mask */
+	int nscancodes;          /* number of calculator scancodes */
+	byte *scancodes;         /* calculator scancodes */
+} TilemKeyBinding;
+
+/* A single action */
+typedef struct _TilemMacroAtom {
+	char* value;
+	int type;
+} TilemMacroAtom;
+
+/* All the actions */
+typedef struct _TilemMacro {
+	TilemMacroAtom** actions;
+	int n;
+} TilemMacro;
+
+
+
+typedef struct _TilemCalcEmulator {
+	GThread *z80_thread;
+
+	/* Mutex controlling access to the calc.  Use
+	   tilem_calc_emulator_lock()/unlock() rather than
+	   g_mutex_lock()/unlock() directly. */
+	GMutex *calc_mutex;
+	int calc_lock_waiting;
+
+	GCond *calc_wakeup_cond;
+	TilemCalc *calc;
+	gboolean paused;
+	gboolean exiting;
+	gboolean limit_speed;   /* limit to actual speed */
+
+	/* Timer used for speed limiting */
+	GTimer *timer;
+	gulong timevalue;
+
+	/* Queue of tasks to be performed */
+	GQueue *task_queue;
+	gboolean task_busy;
+	gboolean task_abort;
+	GCond *task_finished_cond;
+
+	/* Sequence of keys to be pressed */
+	byte *key_queue;
+	int key_queue_len;
+	int key_queue_timer;
+	int key_queue_pressed;
+	int key_queue_cur;
+	int key_queue_hold;
+
+	GMutex *lcd_mutex;
+	TilemLCDBuffer *lcd_buffer;
+	TilemLCDBuffer *tmp_lcd_buffer;
+	TilemGrayLCD *glcd;
+	gboolean grayscale;
+	gboolean lcd_update_pending;
+
+	TilemAnimation *anim; /* animation being recorded */
+	gboolean anim_grayscale; /* use grayscale in animation */
+
+	char *rom_file_name;
+	char *state_file_name;
+
+	/* List of key bindings */
+	TilemKeyBinding *keybindings;
+	int nkeybindings;
+	
+	struct _TilemMacro *macro;
+
+	/* Link transfer state */
+	gboolean ilp_active;
+	CalcUpdate *link_update; /* CalcUpdate (status and callbacks for ticalcs) */
+	GMutex *pbar_mutex;
+	char *pbar_title;
+	char *pbar_status;
+	gdouble pbar_progress;
+	gboolean pbar_update_pending;
+	gboolean progress_changed;
+
+	/* GUI widgets */
+	struct _TilemDebugger *dbg;
+	struct _TilemEmulatorWindow *ewin;
+	struct _TilemScreenshotDialog *ssdlg;
+	struct _TilemReceiveDialog *rcvdlg;
+	struct _TilemLinkProgress *linkpb;
+	
+
+	FILE * macro_file;	/* The macro file */
+	gboolean isMacroRecording; /* A flag to know everywhere that macro is recording */
+
+} TilemCalcEmulator;
+
+/* Errors */
+#define TILEM_EMULATOR_ERROR g_quark_from_static_string("tilem-emulator-error")
+enum {
+	TILEM_EMULATOR_ERROR_NO_ROM,
+	TILEM_EMULATOR_ERROR_INVALID_ROM,
+	TILEM_EMULATOR_ERROR_INVALID_STATE
+};
+
+/* Create a new TilemCalcEmulator. */
+TilemCalcEmulator *tilem_calc_emulator_new(void);
+
+/* Free a TilemCalcEmulator. */
+void tilem_calc_emulator_free(TilemCalcEmulator *emu);
+
+/* Lock calculator so we can directly access it from outside the core
+   thread. */
+void tilem_calc_emulator_lock(TilemCalcEmulator *emu);
+
+/* Unlock calculator and allow emulation to continue. */
+void tilem_calc_emulator_unlock(TilemCalcEmulator *emu);
+
+/* Load the calculator state from the given ROM file (and accompanying
+   sav file, if any.) */
+gboolean tilem_calc_emulator_load_state(TilemCalcEmulator *emu,
+                                        const char *romfname,
+                                        const char *statefname,
+                                        int model, GError **err);
+
+/* Reload the calculator state from the most recently loaded file. */
+gboolean tilem_calc_emulator_revert_state(TilemCalcEmulator *emu,
+                                          GError **err);
+
+/* Save the calculator state. */
+gboolean tilem_calc_emulator_save_state(TilemCalcEmulator *emu,
+                                        GError **err);
+
+/* Reset the calculator. */
+void tilem_calc_emulator_reset(TilemCalcEmulator *emu);
+
+/* Pause emulation (if currently running.) */
+void tilem_calc_emulator_pause(TilemCalcEmulator *emu);
+
+/* Resume emulation (if currently paused.) */
+void tilem_calc_emulator_run(TilemCalcEmulator *emu);
+
+/* Enable/disable speed limiting (TRUE means attempt to run at the
+   actual CPU speed; FALSE means run as fast as we can.) */
+void tilem_calc_emulator_set_limit_speed(TilemCalcEmulator *emu,
+                                         gboolean limit);
+
+/* Enable/disable grayscale */
+void tilem_calc_emulator_set_grayscale(TilemCalcEmulator *emu,
+                                       gboolean grayscale);
+
+/* Press a single key. */
+void tilem_calc_emulator_press_key(TilemCalcEmulator *emu, int key);
+
+/* Release a single key. */
+void tilem_calc_emulator_release_key(TilemCalcEmulator *emu, int key);
+
+/* Add keys to the input queue. */
+void tilem_calc_emulator_queue_keys(TilemCalcEmulator *emu,
+                                    const byte *keys, int nkeys);
+
+/* Release final key in input queue. */
+void tilem_calc_emulator_release_queued_key(TilemCalcEmulator *emu);
+
+/* If input queue is empty, press key immediately; otherwise, add to
+   the input queue.  Return TRUE if key was added to the queue. */
+gboolean tilem_calc_emulator_press_or_queue(TilemCalcEmulator *emu, int key);
+
+/* Retrieve a static screenshot of current calculator screen.
+   Returned object has a reference count of 1 (free it with
+   g_object_unref().) */
+TilemAnimation * tilem_calc_emulator_get_screenshot(TilemCalcEmulator *emu,
+                                                    gboolean grayscale);
+
+/* Begin recording an animated screenshot. */
+void tilem_calc_emulator_begin_animation(TilemCalcEmulator *emu,
+                                         gboolean grayscale);
+
+/* Finish recording an animated screenshot.  Returned object has a
+   reference count of 1 (free it with g_object_unref().) */
+TilemAnimation * tilem_calc_emulator_end_animation(TilemCalcEmulator *emu);
+
+/* Prompt for a ROM file to open */
+int tilem_calc_emulator_prompt_open_rom(TilemCalcEmulator *emu);
+
+
+/* Run slowly to play macro */
+void run_with_key_slowly(TilemCalc* calc, int key);
+
+
+/* Task handling */
+ 
+typedef gboolean (*TilemTaskMainFunc)(TilemCalcEmulator *emu, gpointer data);
+typedef void (*TilemTaskFinishedFunc)(TilemCalcEmulator *emu, gpointer data,
+                                      gboolean cancelled);
+ 
+/* Add a task to the queue.  MAINF is a function to perform in the
+   core thread.  If it returns FALSE, all further tasks will be
+   cancelled.  Tasks can also be cancelled early by calling
+   tilem_calc_emulator_cancel_tasks().
+
+   After the task finishes or is cancelled, FINISHEDF will be called
+   in the GUI thread.  Task-finished functions might not be called in
+   the same order the tasks were originally added to the queue. */
+void tilem_calc_emulator_begin(TilemCalcEmulator *emu,
+                               TilemTaskMainFunc taskf,
+                               TilemTaskFinishedFunc finishedf,
+                               gpointer data);
+
+/* Cancel all pending tasks.  If a task is currently running, this
+   will attempt to cancel it and wait for it to exit. */
+void tilem_calc_emulator_cancel_tasks(TilemCalcEmulator *emu);
+
+
+/* Macros */
+
+/* Start to record a macro */
+void tilem_macro_start(TilemCalcEmulator *emu);
+
+
+/* Add an action to the macro */
+void tilem_macro_add_action(TilemMacro* macro, int type, char * value);
+
+
+/* Stop recording a macro */
+void tilem_macro_stop(TilemCalcEmulator *emu);
+
+
+/* Print the macro (debug) */
+void tilem_macro_print(TilemMacro *macro);
+
+
+/* Write a macro file */
+void tilem_macro_write_file(TilemCalcEmulator *emu);
+
+
+/* Play a macro (loaded or recorded before) */
+void tilem_macro_play(TilemCalcEmulator *emu);
+
+
+/* Load a macro from filename or if filename == NULL prompt user */
+void tilem_macro_load(TilemCalcEmulator *emu, char* filename);
+
diff --git a/tool/tilem-src/gui/emuwin.c b/tool/tilem-src/gui/emuwin.c
new file mode 100644
index 0000000..68c563e
--- /dev/null
+++ b/tool/tilem-src/gui/emuwin.c
@@ -0,0 +1,621 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "files.h"
+#include "msgbox.h"
+
+/* Set size hints for the toplevel window */
+static void set_size_hints(GtkWidget *widget, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+
+	/* Don't use gtk_window_set_geometry_hints() (which would
+	   appear to do what we want) because, in addition to setting
+	   the hints we want, that function causes GTK+ to argue with
+	   the window manager.
+
+	   Instead, we call this function after the check-resize
+	   signal (which is when GTK+ itself would normally set the
+	   hints.)
+
+	   FIXME: check that this works as desired on Win32/Quartz. */
+
+	if (gtk_widget_get_window(widget))
+		gdk_window_set_geometry_hints(gtk_widget_get_window(widget),
+		                              &ewin->geomhints,
+		                              ewin->geomhintmask);
+}
+
+static void window_state_changed(G_GNUC_UNUSED GtkWidget *w,
+                                 GdkEventWindowState *event, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	ewin->window_state = event->new_window_state;
+}
+
+static gboolean window_maximized(TilemEmulatorWindow *ewin)
+{
+	return (ewin->window_state & (GDK_WINDOW_STATE_MAXIMIZED
+	                              | GDK_WINDOW_STATE_FULLSCREEN));
+}
+
+static gboolean screen_repaint(GtkWidget *w, GdkEventExpose *ev G_GNUC_UNUSED,
+                               TilemEmulatorWindow *ewin)
+{
+	GtkAllocation alloc;
+	GdkWindow *win;
+	GtkStyle *style;
+
+	gtk_widget_get_allocation(w, &alloc);
+
+	/* If image buffer is not the correct size, allocate a new one */
+
+	if (!ewin->lcd_image_buf
+	    || alloc.width != ewin->lcd_image_width
+	    || alloc.height != ewin->lcd_image_height) {
+		ewin->lcd_image_width = alloc.width;
+		ewin->lcd_image_height = alloc.height;
+		g_free(ewin->lcd_image_buf);
+		ewin->lcd_image_buf = g_new(byte, alloc.width * alloc.height);
+	}
+
+	/* Draw LCD contents into the image buffer */
+
+	g_mutex_lock(ewin->emu->lcd_mutex);
+	ewin->emu->lcd_update_pending = FALSE;
+	tilem_draw_lcd_image_indexed(ewin->emu->lcd_buffer,
+	                             ewin->lcd_image_buf,
+	                             alloc.width, alloc.height, alloc.width,
+	                             (ewin->lcd_smooth_scale
+	                              ? TILEM_SCALE_SMOOTH
+	                              : TILEM_SCALE_FAST));
+	g_mutex_unlock(ewin->emu->lcd_mutex);
+
+	/* Render buffer to the screen */
+
+	win = gtk_widget_get_window(w);
+	style = gtk_widget_get_style(w);
+	gdk_draw_indexed_image(win, style->fg_gc[GTK_STATE_NORMAL], 0, 0,
+	                       alloc.width, alloc.height, GDK_RGB_DITHER_NONE,
+	                       ewin->lcd_image_buf, alloc.width,
+	                       ewin->lcd_cmap);
+	return TRUE;
+}
+
+/* Set the color palette for drawing the emulated LCD. */
+static void screen_restyle(GtkWidget* w, GtkStyle* oldstyle G_GNUC_UNUSED,
+                           TilemEmulatorWindow* ewin)
+{
+	dword* palette;
+	GtkStyle *style;
+	int r_dark, g_dark, b_dark;
+	int r_light, g_light, b_light;
+	double gamma = 2.2;
+
+	if (!ewin->skin) {
+		/* no skin -> use standard GTK colors */
+
+		style = gtk_widget_get_style(w);
+
+		r_dark = style->text[GTK_STATE_NORMAL].red / 257;
+		g_dark = style->text[GTK_STATE_NORMAL].green / 257;
+		b_dark = style->text[GTK_STATE_NORMAL].blue / 257;
+
+		r_light = style->base[GTK_STATE_NORMAL].red / 257;
+		g_light = style->base[GTK_STATE_NORMAL].green / 257;
+		b_light = style->base[GTK_STATE_NORMAL].blue / 257;
+	}
+	else {
+		/* use skin colors */
+
+		r_dark = ((ewin->skin->lcd_black >> 16) & 0xff);
+		g_dark = ((ewin->skin->lcd_black >> 8) & 0xff);
+		b_dark = (ewin->skin->lcd_black & 0xff);
+
+		r_light = ((ewin->skin->lcd_white >> 16) & 0xff);
+		g_light = ((ewin->skin->lcd_white >> 8) & 0xff);
+		b_light = (ewin->skin->lcd_white & 0xff);
+	}
+
+	/* Generate a new palette, and convert it into GDK format */
+
+	if (ewin->lcd_cmap)
+		gdk_rgb_cmap_free(ewin->lcd_cmap);
+
+	palette = tilem_color_palette_new(r_light, g_light, b_light,
+					  r_dark, g_dark, b_dark, gamma);
+	ewin->lcd_cmap = gdk_rgb_cmap_new(palette, 256);
+	tilem_free(palette);
+
+	gtk_widget_queue_draw(ewin->lcd);
+}
+
+static void skin_size_allocate(GtkWidget *widget, GtkAllocation *alloc,
+                               gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	int rawwidth, rawheight, scaledwidth, scaledheight;
+	int lcdleft, lcdright, lcdtop, lcdbottom;
+	double rx, ry, r;
+
+	g_return_if_fail(ewin->skin != NULL);
+
+	rawwidth = gdk_pixbuf_get_width(ewin->skin->raw);
+	rawheight = gdk_pixbuf_get_height(ewin->skin->raw);
+
+	rx = (double) alloc->width / rawwidth;
+	ry = (double) alloc->height / rawheight;
+	r = MIN(rx, ry);
+
+	scaledwidth = rawwidth * r;
+	scaledheight = rawheight * r;
+
+	if (ewin->skin->width == scaledwidth
+	    && ewin->skin->height == scaledheight)
+		return;
+
+	ewin->skin->width = scaledwidth;
+	ewin->skin->height = scaledheight;
+	ewin->skin->sx = ewin->skin->sy = 1.0 / r;
+
+	if (ewin->skin->image)
+		g_object_unref(ewin->skin->image);
+	ewin->skin->image = gdk_pixbuf_scale_simple(ewin->skin->raw,
+	                                            scaledwidth,
+	                                            scaledheight,
+	                                            GDK_INTERP_BILINEAR);
+
+	gtk_image_set_from_pixbuf(GTK_IMAGE(ewin->background),
+	                          ewin->skin->image);
+
+	lcdleft = ewin->skin->lcd_pos.left * r + 0.5;
+	lcdright = ewin->skin->lcd_pos.right * r + 0.5;
+	lcdtop = ewin->skin->lcd_pos.top * r + 0.5;
+	lcdbottom = ewin->skin->lcd_pos.bottom * r + 0.5;
+
+	gtk_widget_set_size_request(ewin->lcd,
+	                            MAX(lcdright - lcdleft, 1),
+	                            MAX(lcdbottom - lcdtop, 1));
+
+	gtk_layout_move(GTK_LAYOUT(widget), ewin->lcd,
+	                lcdleft, lcdtop);
+
+	ewin->zoom_factor = r / ewin->base_zoom;
+
+	if (ewin->zoom_factor <= 1.0)
+		ewin->zoom_factor = 1.0;
+}
+
+static void noskin_size_allocate(G_GNUC_UNUSED GtkWidget *widget,
+                                 GtkAllocation *alloc, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	int lcdwidth, lcdheight;
+
+	g_return_if_fail(ewin->emu->calc != NULL);
+
+	lcdwidth = ewin->emu->calc->hw.lcdwidth;
+	lcdheight = ewin->emu->calc->hw.lcdheight;
+
+	if (alloc->width > alloc->height)
+		ewin->zoom_factor = (gdouble) alloc->width / lcdwidth;
+	else
+		ewin->zoom_factor = (gdouble) alloc->height / lcdheight;
+
+	if (ewin->zoom_factor <= 1.0)
+		ewin->zoom_factor = 1.0;
+}
+
+/* Used when you load another skin */
+void redraw_screen(TilemEmulatorWindow *ewin)
+{
+	GtkWidget *pImage;
+	GtkWidget *emuwin;
+	int lcdwidth, lcdheight;
+	int screenwidth, screenheight;
+	int minwidth, minheight, defwidth, defheight,
+		curwidth, curheight;
+	double sx, sy, s, a1, a2;
+	GError *err = NULL;
+
+	if (ewin->skin) {
+		skin_unload(ewin->skin);
+		g_free(ewin->skin);
+	}
+
+	if (ewin->skin_disabled || !ewin->skin_file_name) {
+		ewin->skin = NULL;
+	}
+	else {
+		ewin->skin = g_new0(SKIN_INFOS, 1);
+		if (skin_load(ewin->skin, ewin->skin_file_name, &err)) {
+			skin_unload(ewin->skin);
+			ewin->skin = NULL;
+		}
+	}
+
+	if (ewin->emu->calc) {
+		lcdwidth = ewin->emu->calc->hw.lcdwidth;
+		lcdheight = ewin->emu->calc->hw.lcdheight;
+	}
+	else {
+		lcdwidth = lcdheight = 1;
+	}
+
+	if (ewin->lcd)
+		gtk_widget_destroy(ewin->lcd);
+	if (ewin->background)
+		gtk_widget_destroy(ewin->background);
+	if (ewin->layout)
+		gtk_widget_destroy(ewin->layout);
+
+	/* create LCD widget */
+	ewin->lcd = gtk_drawing_area_new();
+	gtk_widget_set_double_buffered(ewin->lcd, FALSE);
+
+	/* create background image and layout */
+	if (ewin->skin) {
+		ewin->layout = gtk_layout_new(NULL, NULL);
+
+		pImage = gtk_image_new();
+		gtk_layout_put(GTK_LAYOUT(ewin->layout), pImage, 0, 0);
+		ewin->background = pImage;
+
+		screenwidth = (ewin->skin->lcd_pos.right
+		               - ewin->skin->lcd_pos.left);
+		screenheight = (ewin->skin->lcd_pos.bottom
+		                - ewin->skin->lcd_pos.top);
+
+		gtk_widget_set_size_request(ewin->lcd,
+		                            screenwidth, screenheight);
+		gtk_layout_put(GTK_LAYOUT(ewin->layout), ewin->lcd,
+		               ewin->skin->lcd_pos.left,
+		               ewin->skin->lcd_pos.top);
+
+		g_signal_connect(ewin->layout, "size-allocate",
+		                 G_CALLBACK(skin_size_allocate), ewin);
+
+		emuwin = ewin->layout;
+
+		defwidth = ewin->skin->width;
+		defheight = ewin->skin->height;
+
+		sx = (double) lcdwidth / screenwidth;
+		sy = (double) lcdheight / screenheight;
+		s = MAX(sx, sy);
+		minwidth = defwidth * s + 0.5;
+		minheight = defheight * s + 0.5;
+		ewin->base_zoom = s;
+	}
+	else {
+		ewin->layout = NULL;
+		ewin->background = NULL;
+
+		emuwin = ewin->lcd;
+
+		g_signal_connect(ewin->lcd, "size-allocate",
+		                 G_CALLBACK(noskin_size_allocate), ewin);
+
+		defwidth = minwidth = lcdwidth;
+		defheight = minheight = lcdheight;
+		ewin->base_zoom = 1.0;
+	}
+
+	curwidth = defwidth * ewin->base_zoom * ewin->zoom_factor + 0.5;
+	curheight = defheight * ewin->base_zoom * ewin->zoom_factor + 0.5;
+
+	gtk_widget_set_can_focus(emuwin, TRUE);
+
+	gtk_widget_add_events(emuwin, (GDK_BUTTON_PRESS_MASK
+	                               | GDK_BUTTON_RELEASE_MASK
+	                               | GDK_BUTTON1_MOTION_MASK
+	                               | GDK_POINTER_MOTION_HINT_MASK 
+				       | GDK_DROP_FINISHED
+				       | GDK_DRAG_MOTION));
+
+	g_signal_connect(ewin->lcd, "expose-event",
+	                 G_CALLBACK(screen_repaint), ewin);
+	g_signal_connect(ewin->lcd, "style-set",
+	                 G_CALLBACK(screen_restyle), ewin);
+
+	g_signal_connect(emuwin, "button-press-event",
+	                 G_CALLBACK(mouse_press_event), ewin);
+	g_signal_connect(emuwin, "motion-notify-event",
+	                 G_CALLBACK(pointer_motion_event), ewin);
+	g_signal_connect(emuwin, "button-release-event",
+	                 G_CALLBACK(mouse_release_event), ewin);
+	g_signal_connect(emuwin, "popup-menu",
+	                 G_CALLBACK(popup_menu_event), ewin);
+
+	/* FIXME: this is rather broken; GTK_DEST_DEFAULT_ALL sends a
+	   successful "finished" message to any drop that matches the
+	   list of targets.  Files/URIs we can't accept should be
+	   rejected, and we shouldn't send the "finished" message
+	   until it's actually finished. */
+	gtk_drag_dest_set(emuwin, GTK_DEST_DEFAULT_ALL,
+	                  NULL, 0, GDK_ACTION_COPY);
+	gtk_drag_dest_add_uri_targets(emuwin);
+	g_signal_connect(emuwin, "drag-data-received",
+	                 G_CALLBACK(drag_data_received), ewin);
+
+
+	/* Hint calculation assumes the emulator is the only thing in
+	   the window; if other widgets are added, this will have to
+	   change accordingly
+	*/
+	ewin->geomhints.min_width = minwidth;
+	ewin->geomhints.min_height = minheight;
+	a1 = (double) minwidth / minheight;
+	a2 = (double) defwidth / defheight;
+	ewin->geomhints.min_aspect = MIN(a1, a2) - 0.0001;
+	ewin->geomhints.max_aspect = MAX(a1, a2) + 0.0001;
+	ewin->geomhintmask = (GDK_HINT_MIN_SIZE | GDK_HINT_ASPECT);
+
+	/* If the window is already realized, set the hints now, so
+	   that the WM will see the new hints before we try to resize
+	   the window */
+	set_size_hints(ewin->window, ewin);
+
+	gtk_widget_set_size_request(emuwin, minwidth, minheight);
+	gtk_container_add(GTK_CONTAINER(ewin->window), emuwin);
+
+	if (!window_maximized(ewin))
+		gtk_window_resize(GTK_WINDOW(ewin->window),
+		                  curwidth, curheight);
+
+	gtk_widget_show_all(emuwin);
+
+	if (err) {
+		messagebox01(GTK_WINDOW(ewin->window), GTK_MESSAGE_ERROR,
+		             "Unable to load skin", "%s", err->message);
+		g_error_free(err);
+	}
+}
+
+static void window_destroy(G_GNUC_UNUSED GtkWidget *w, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+
+	if (!window_maximized(ewin))
+		tilem_config_set("settings",
+		                 "zoom/r", ewin->zoom_factor,
+		                 NULL);
+
+	ewin->window = ewin->layout = ewin->lcd = ewin->background = NULL;
+}
+
+TilemEmulatorWindow *tilem_emulator_window_new(TilemCalcEmulator *emu)
+{
+	TilemEmulatorWindow *ewin;
+
+	g_return_val_if_fail(emu != NULL, NULL);
+
+	ewin = g_slice_new0(TilemEmulatorWindow);
+	ewin->emu = emu;
+
+	tilem_config_get("settings",
+	                 "skin_disabled/b", &ewin->skin_disabled,
+	                 "smooth_scaling/b=1", &ewin->lcd_smooth_scale,
+	                 "zoom/r=2.0", &ewin->zoom_factor,
+	                 NULL);
+
+	if (ewin->zoom_factor <= 1.0)
+		ewin->zoom_factor = 1.0;
+
+	/* Create the window */
+	ewin->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+
+	g_signal_connect(ewin->window, "window-state-event",
+	                 G_CALLBACK(window_state_changed), ewin);
+	g_signal_connect(ewin->window, "destroy",
+	                 G_CALLBACK(window_destroy), ewin);
+
+	g_signal_connect_after(ewin->window, "check-resize",
+	                       G_CALLBACK(set_size_hints), ewin);
+
+	gtk_widget_add_events(ewin->window, (GDK_KEY_PRESS_MASK
+	                                      | GDK_KEY_RELEASE_MASK));
+
+	g_signal_connect(ewin->window, "key-press-event",
+	                 G_CALLBACK(key_press_event), ewin);
+	g_signal_connect(ewin->window, "key-release-event",
+	                 G_CALLBACK(key_release_event), ewin);
+
+	build_menu(ewin);
+
+	tilem_emulator_window_calc_changed(ewin);
+
+	return ewin;
+}
+
+void tilem_emulator_window_free(TilemEmulatorWindow *ewin)
+{
+	g_return_if_fail(ewin != NULL);
+
+	if (ewin->lcd)
+		gtk_widget_destroy(ewin->lcd);
+	if (ewin->background)
+		gtk_widget_destroy(ewin->background);
+	if (ewin->layout)
+		gtk_widget_destroy(ewin->layout);
+	if (ewin->window)
+		gtk_widget_destroy(ewin->window);
+	if (ewin->popup_menu)
+		gtk_widget_destroy(ewin->popup_menu);
+	if (ewin->actions)
+		g_object_unref(ewin->actions);
+
+	g_free(ewin->lcd_image_buf);
+
+	g_free(ewin->skin_file_name);
+	if (ewin->skin) {
+		skin_unload(ewin->skin);
+		g_free(ewin->skin);
+	}
+
+	if (ewin->lcd_cmap)
+		gdk_rgb_cmap_free(ewin->lcd_cmap);
+
+	g_slice_free(TilemEmulatorWindow, ewin);
+}
+
+void tilem_emulator_window_set_skin(TilemEmulatorWindow *ewin,
+                                    const char *filename)
+{
+	g_return_if_fail(ewin != NULL);
+
+	g_free(ewin->skin_file_name);
+	if (filename)
+		ewin->skin_file_name = g_strdup(filename);
+	else
+		ewin->skin_file_name = NULL;
+	redraw_screen(ewin);
+}
+
+/* Switch between skin and LCD-only mode */
+void tilem_emulator_window_set_skin_disabled(TilemEmulatorWindow *ewin,
+                                             gboolean disabled)
+{
+	g_return_if_fail(ewin != NULL);
+
+	if (ewin->skin_disabled != !!disabled) {
+		ewin->skin_disabled = !!disabled;
+		redraw_screen(ewin);
+	}
+}
+
+void tilem_emulator_window_calc_changed(TilemEmulatorWindow *ewin)
+{
+	const char *model;
+	char *name = NULL, *path;
+
+	g_return_if_fail(ewin != NULL);
+	g_return_if_fail(ewin->emu != NULL);
+
+	g_free(ewin->skin_file_name);
+	ewin->skin_file_name = NULL;
+
+	if (!ewin->emu->calc)
+		return;
+
+	model = ewin->emu->calc->hw.name;
+
+	tilem_config_get(model,
+	                 "skin/f", &name,
+	                 NULL);
+
+	if (!name)
+		name = g_strdup_printf("%s.skn", model);
+
+	if (!g_path_is_absolute(name)) {
+		path = get_shared_file_path("skins", name, NULL);
+		tilem_emulator_window_set_skin(ewin, path);
+		g_free(path);
+	}
+	else {
+		tilem_emulator_window_set_skin(ewin, name);
+	}
+
+	g_free(name);
+}
+
+void tilem_emulator_window_refresh_lcd(TilemEmulatorWindow *ewin)
+{
+	g_return_if_fail(ewin != NULL);
+	if (ewin->lcd)
+		gtk_widget_queue_draw(ewin->lcd);
+}
+
+
+
+
+/* Display the lcd image into the terminal */
+void display_lcdimage_into_terminal(TilemEmulatorWindow *ewin)
+{
+	
+	int width, height;
+	guchar* lcddata;
+	int x, y;
+	char c;
+	width = ewin->emu->calc->hw.lcdwidth;
+	height = ewin->emu->calc->hw.lcdheight;
+	FILE* lcd_content_file;
+	/* Alloc mmem */
+	lcddata = g_new(guchar, (width / 8) * height);
+		
+	/* Get the lcd content using the function 's pointer from Benjamin's core */
+	(*ewin->emu->calc->hw.get_lcd)(ewin->emu->calc, lcddata);
+		
+	/* Print a little demo just for fun;) */
+	printf("\n\n\n");	
+	printf("	 r     rr    r  rrrrr  rrr  r     rrrrr r   r  rr    r    rr     r                      \n");
+	printf("  r     r     r     r     r     r   r     r     rr rr    r    r     r     r     r     r     r   \n");
+	printf("   r   r      r    r      r     r   r     r     r r r   r      r    r      r     r     r     r  \n");
+	printf("rrrrr r      r     r      r     r   r     rrrr  r r r  r       r     r      r rrrrr rrrrr rrrrr \n");
+	printf("   r   r      r    r      r     r   r     r     r   r  rrr     r    r      r     r     r     r  \n");
+	printf("  r     r     r     r     r     r   r     r     r   r         r     r     r     r     r     r   \n");
+	printf("	 r     rr    r    r    rrr  rrrrr rrrrr r   r        r    rr     r                      \n");
+	printf("\n(Here is just a sample...)\n\n");	
+	
+	/* Request user to know which char user wants */	
+	
+	printf("Which char to display FOR BLACK?\n");
+	scanf("%c", &c); /* Choose wich char for the black */	
+	
+	//printf("Which char to display FOR GRAY ?\n");
+	//scanf("%c", &b); /* Choose wich char for the black */	
+	
+	lcd_content_file = g_fopen("lcd_content.txt", "w");
+
+	printf("\n\n\n### LCD CONTENT ###\n\n\n\n");
+	for (y = 0; y < height; y++) {
+		for (x = 0; x < width; x++) {
+			/*printf("%d ", lcddata[y * width + x]); */	
+			if (lcddata[(y * width + x) / 8] & (0x80 >> (x % 8))) {
+				printf("%c", c);
+				if(lcd_content_file != NULL)	
+					fprintf(lcd_content_file,"%c", c);
+			} else {
+				printf("%c", ' ');
+				if(lcd_content_file != NULL)	
+					fprintf(lcd_content_file,"%c", ' ');
+			}
+		}
+		printf("\n");
+		if(lcd_content_file != NULL)	
+			fprintf(lcd_content_file,"%c", '\n');
+	}	
+	if(lcd_content_file != NULL) {	
+		fclose(lcd_content_file);
+		printf("\n### END ###\n\nSaved into lcd_content.txt\n");
+	}	
+
+}
diff --git a/tool/tilem-src/gui/emuwin.h b/tool/tilem-src/gui/emuwin.h
new file mode 100644
index 0000000..74fb3ca
--- /dev/null
+++ b/tool/tilem-src/gui/emuwin.h
@@ -0,0 +1,78 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Root window view (widgets and flags) */
+typedef struct _TilemEmulatorWindow {
+	TilemCalcEmulator *emu;
+
+	GtkWidget *window; /* The top level window */
+	GtkWidget *layout; /* Layout */
+	GtkWidget *lcd;
+	GtkWidget *background;
+	GtkWidget *popup_menu;
+
+	GtkActionGroup *actions;
+
+	GdkGeometry geomhints;
+	GdkWindowHints geomhintmask;
+
+	byte* lcd_image_buf;
+	int lcd_image_width;
+	int lcd_image_height;
+	GdkRgbCmap* lcd_cmap;
+	gboolean lcd_smooth_scale;
+
+	char *skin_file_name;
+	SKIN_INFOS *skin;
+	gboolean skin_disabled; /* A flag to know if skinless or not */
+	gdouble base_zoom;
+	gdouble zoom_factor;
+	GdkWindowState window_state;
+
+	int mouse_key;		/* Key currently pressed by mouse button */
+
+	/* Host keycode used to activate each key, if any */
+	int keypress_keycodes[64];
+	int sequence_keycode;
+
+} TilemEmulatorWindow;
+
+/* Create a new TilemEmulatorWindow. */
+TilemEmulatorWindow *tilem_emulator_window_new(TilemCalcEmulator *emu);
+
+/* Free a TilemEmulatorWindow. */
+void tilem_emulator_window_free(TilemEmulatorWindow *ewin);
+
+/* Load a skin file. */
+void tilem_emulator_window_set_skin(TilemEmulatorWindow *ewin,
+                                    const char *filename);
+
+/* Enable or disable skin. */
+void tilem_emulator_window_set_skin_disabled(TilemEmulatorWindow *ewin,
+                                             gboolean disabled);
+
+/* New calculator loaded. */
+void tilem_emulator_window_calc_changed(TilemEmulatorWindow *ewin);
+
+/* Redraw LCD contents. */
+void tilem_emulator_window_refresh_lcd(TilemEmulatorWindow *ewin);
+
+/* Prompt for a ROM file to open */
+gboolean tilem_emulator_window_prompt_open_rom(TilemEmulatorWindow *ewin);
diff --git a/tool/tilem-src/gui/event.c b/tool/tilem-src/gui/event.c
new file mode 100644
index 0000000..33fd478
--- /dev/null
+++ b/tool/tilem-src/gui/event.c
@@ -0,0 +1,436 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include <scancodes.h>
+
+#include "gui.h"
+#include "files.h"
+#include "filedlg.h"
+
+/* Table for translating skin-file key number (based on actual
+   position, and defined by the VTI/TiEmu file formats) into a
+   scancode.  Note that the TILEM_KEY_* constants are named according
+   to the TI-83 keypad layout; other models use different names for
+   the keys, but the same scancodes. */
+static const int keycode_map[] =
+	{ TILEM_KEY_YEQU,
+	  TILEM_KEY_WINDOW,
+	  TILEM_KEY_ZOOM,
+	  TILEM_KEY_TRACE,
+	  TILEM_KEY_GRAPH,
+
+	  TILEM_KEY_2ND,
+	  TILEM_KEY_MODE,
+	  TILEM_KEY_DEL,
+	  TILEM_KEY_LEFT,
+	  TILEM_KEY_RIGHT,
+	  TILEM_KEY_UP,
+	  TILEM_KEY_DOWN,
+	  TILEM_KEY_ALPHA,
+	  TILEM_KEY_GRAPHVAR,
+	  TILEM_KEY_STAT,
+
+	  TILEM_KEY_MATH,
+	  TILEM_KEY_MATRIX,
+	  TILEM_KEY_PRGM,
+	  TILEM_KEY_VARS,
+	  TILEM_KEY_CLEAR,
+
+	  TILEM_KEY_RECIP,
+	  TILEM_KEY_SIN,
+	  TILEM_KEY_COS,
+	  TILEM_KEY_TAN,
+	  TILEM_KEY_POWER,
+
+	  TILEM_KEY_SQUARE,
+	  TILEM_KEY_COMMA,
+	  TILEM_KEY_LPAREN,
+	  TILEM_KEY_RPAREN,
+	  TILEM_KEY_DIV,
+
+	  TILEM_KEY_LOG,
+	  TILEM_KEY_7,
+	  TILEM_KEY_8,
+	  TILEM_KEY_9,
+	  TILEM_KEY_MUL,
+
+	  TILEM_KEY_LN,
+	  TILEM_KEY_4,
+	  TILEM_KEY_5,
+	  TILEM_KEY_6,
+	  TILEM_KEY_SUB,
+
+	  TILEM_KEY_STORE,
+	  TILEM_KEY_1,
+	  TILEM_KEY_2,
+	  TILEM_KEY_3,
+	  TILEM_KEY_ADD,
+
+	  TILEM_KEY_ON,
+	  TILEM_KEY_0,
+	  TILEM_KEY_DECPNT,
+	  TILEM_KEY_CHS,
+	  TILEM_KEY_ENTER };
+
+/* Find the keycode for the key (if any) at the given position.  If
+   the keys overlap, choose the "nearest" (according to Manhattan
+   distance to the midpoint.) */
+static int scan_click(const SKIN_INFOS* skin, double x, double y)
+{
+	guint ix, iy, nearest = 0, i;
+	int dx, dy, d, best_d = G_MAXINT;
+
+	if (!skin)
+		return 0;
+
+	ix = (x * skin->sx + 0.5);
+	iy = (y * skin->sy + 0.5);
+
+	for (i = 0; i < G_N_ELEMENTS(keycode_map); i++) {
+		if (ix >= skin->keys_pos[i].left
+		    && ix < skin->keys_pos[i].right
+		    && iy >= skin->keys_pos[i].top
+		    && iy < skin->keys_pos[i].bottom) {
+			dx = (skin->keys_pos[i].left + skin->keys_pos[i].right
+			      - 2 * ix);
+			dy = (skin->keys_pos[i].top + skin->keys_pos[i].bottom
+			      - 2 * iy);
+			d = ABS(dx) + ABS(dy);
+
+			if (d < best_d) {
+				best_d = d;
+				nearest = keycode_map[i];
+			}
+		}
+	}
+
+	return nearest;
+}
+
+/* Retrieve pointer coordinates for an input device. */
+static void get_device_pointer(GdkWindow *win, GdkDevice *dev,
+                               gdouble *x, gdouble *y, GdkModifierType *mask)
+{
+	gdouble *axes;
+	int i;
+
+	axes = g_new(gdouble, dev->num_axes);
+	gdk_device_get_state(dev, win, axes, mask);
+
+	for (i = 0; i < dev->num_axes; i++) {
+		if (x && dev->axes[i].use == GDK_AXIS_X)
+			*x = axes[i];
+		else if (y && dev->axes[i].use == GDK_AXIS_Y)
+			*y = axes[i];
+	}
+
+	g_free(axes);
+}
+
+/* Show a nice GtkAboutDialog */
+void show_about()
+{
+	GtkWidget *dialog = gtk_about_dialog_new();
+	gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), "2.0"); 
+	gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dialog), "(c) Benjamin Moody\n(c) Thibault Duponchelle\n(c) Luc Bruant\n");
+	gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog), "TilEm is a TI Linux Emulator.\n It emulates all current z80 models.\n TI73, TI76, TI81, TI82, TI83(+)(SE), TI84+(SE), TI85 and TI86 ;D");
+	gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog), "http://lpg.ticalc.org/prj_tilem/");
+
+	/* Add the logo */	
+	char* tilem_ban = get_shared_file_path("pixs", "tilem_ban.png", NULL);
+	if(tilem_ban) {
+		GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(tilem_ban, NULL);
+		gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), pixbuf);
+		g_object_unref(pixbuf), pixbuf = NULL;
+	}
+
+	gtk_dialog_run(GTK_DIALOG (dialog));
+	gtk_widget_destroy(dialog);
+
+}
+
+
+void launch_debugger(TilemEmulatorWindow *ewin)
+{
+	if (!ewin->emu->dbg)
+		ewin->emu->dbg = tilem_debugger_new(ewin->emu);
+	tilem_debugger_show(ewin->emu->dbg);
+}
+
+/* Press a key, ensuring that at most one key is "pressed" at a time
+   due to this function (if pointer moves or is released, we don't
+   want the old key held down.)
+
+   FIXME: on multi-pointer displays, allow each input device to act
+   separately */
+static void press_mouse_key(TilemEmulatorWindow* ewin, int key)
+{
+	if (ewin->mouse_key == key)
+		return;
+
+	tilem_calc_emulator_release_key(ewin->emu, ewin->mouse_key);
+	tilem_calc_emulator_press_key(ewin->emu, key);
+	ewin->mouse_key = key;
+}
+
+/* Mouse button pressed */
+gboolean mouse_press_event(G_GNUC_UNUSED GtkWidget* w, GdkEventButton *event,
+                           gpointer data)
+{  	
+	TilemEmulatorWindow* ewin = data;
+	int key;
+
+	key = scan_click(ewin->skin, event->x, event->y);
+
+	if (event->button == 1) {
+		/* button 1: press key until button is released or
+		   pointer moves away */
+		press_mouse_key(ewin, key);
+		return TRUE;
+	}
+	else if (event->button == 2) {
+		/* button 2: hold key down permanently */
+		tilem_calc_emulator_press_key(ewin->emu, key);
+		return TRUE;
+	}
+	else if (event->button == 3) {
+		/* button 3: popup menu */
+		gtk_menu_popup(GTK_MENU(ewin->popup_menu),
+		               NULL, NULL, NULL, NULL,
+		               event->button, event->time);
+		return TRUE;
+	}
+	else
+		return FALSE;
+}
+
+/* Mouse pointer moved */
+gboolean pointer_motion_event(G_GNUC_UNUSED GtkWidget* w, GdkEventMotion *event,
+                              gpointer data)
+{
+	TilemEmulatorWindow* ewin = data;
+	int key;
+
+	if (event->is_hint)
+		get_device_pointer(event->window, event->device,
+		                   &event->x, &event->y, &event->state);
+
+	if (event->state & GDK_BUTTON1_MASK)
+		key = scan_click(ewin->skin, event->x, event->y);
+	else
+		key = 0;
+
+	press_mouse_key(ewin, key);
+
+	return FALSE;
+}
+
+/* Mouse button released */
+gboolean mouse_release_event(G_GNUC_UNUSED GtkWidget* w, GdkEventButton *event,
+                             gpointer data)
+{
+	TilemEmulatorWindow* ewin = data;
+
+	if (event->button == 1)
+		press_mouse_key(ewin, 0);
+
+	return FALSE;
+}
+
+/* Find key binding for the given keysym and modifiers */
+static TilemKeyBinding* find_key_binding_for_keysym(TilemCalcEmulator* emu,
+                                                    guint keyval,
+                                                    GdkModifierType mods)
+{
+	int i;
+
+	for (i = 0; i < emu->nkeybindings; i++)
+		if (keyval == emu->keybindings[i].keysym
+		    && mods == emu->keybindings[i].modifiers)
+			return &emu->keybindings[i];
+
+	return NULL;
+}
+
+/* Find key binding matching the given event */
+static TilemKeyBinding* find_key_binding(TilemCalcEmulator* emu,
+                                         GdkEventKey* event)
+{
+	GdkDisplay *dpy;
+	GdkKeymap *km;
+	guint keyval;
+	GdkModifierType consumed, mods;
+	TilemKeyBinding *kb;
+
+	dpy = gdk_drawable_get_display(event->window);
+	km = gdk_keymap_get_for_display(dpy);
+
+	/* determine the relevant set of modifiers */
+
+	gdk_keymap_translate_keyboard_state(km, event->hardware_keycode,
+	                                    event->state, event->group,
+	                                    &keyval, NULL, NULL, &consumed);
+
+	mods = (event->state & ~consumed
+	        & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK));
+
+	if (event->state & GDK_LOCK_MASK
+	    && (kb = find_key_binding_for_keysym(emu, keyval,
+	                                         mods | GDK_LOCK_MASK))) {
+		return kb;
+	}
+
+	return find_key_binding_for_keysym(emu, keyval, mods);
+}
+
+/* Key-press event */
+gboolean key_press_event(G_GNUC_UNUSED GtkWidget* w, GdkEventKey* event,
+                         gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	TilemKeyBinding *kb;
+	int i, key;
+	unsigned int hwkey;
+
+	/* Ignore repeating keys */
+	for (i = 0; i < 64; i++)
+		if (ewin->keypress_keycodes[i] == event->hardware_keycode)
+			return TRUE;
+	if (ewin->sequence_keycode == event->hardware_keycode)
+		return TRUE;
+
+	if (!(kb = find_key_binding(ewin->emu, event)))
+		return FALSE;
+
+	hwkey = event->hardware_keycode;
+
+	if (kb->nscancodes == 1) {
+		/* if queue is empty, just press the key; otherwise
+		   add it to the queue */
+		key = kb->scancodes[0];
+		if (tilem_calc_emulator_press_or_queue(ewin->emu, key))
+			ewin->sequence_keycode = hwkey;
+		else 
+			ewin->keypress_keycodes[key] = hwkey;
+	}
+	else {
+		tilem_calc_emulator_queue_keys(ewin->emu, kb->scancodes,
+		                               kb->nscancodes);
+		ewin->sequence_keycode = hwkey;
+	}
+
+	return TRUE;
+}
+
+/* Key-release event */
+gboolean key_release_event(G_GNUC_UNUSED GtkWidget* w, GdkEventKey* event,
+                           gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	int i;
+
+	/* Check if the key that was just released was one that
+	   activated a calculator keypress.  (Do not try to look up
+	   event->keyval; modifiers may have changed since the key was
+	   pressed.) */
+	for (i = 0; i < 64; i++) {
+		if (ewin->keypress_keycodes[i] == event->hardware_keycode) {
+			tilem_calc_emulator_release_key(ewin->emu, i);
+			ewin->keypress_keycodes[i] = 0;
+		}
+	}
+
+	if (ewin->sequence_keycode == event->hardware_keycode) {
+		tilem_calc_emulator_release_queued_key(ewin->emu);
+		ewin->sequence_keycode = 0;
+	}
+
+	return FALSE;
+}
+
+static void place_menu(GtkMenu *menu, gint *x, gint *y,
+                       gboolean *push_in, gpointer data)
+{
+	GtkWidget *w = data;
+	GdkWindow *win;
+	GdkScreen *screen;
+	int n;
+
+	win = gtk_widget_get_window(w);
+	gdk_window_get_origin(win, x, y);
+
+	screen = gdk_drawable_get_screen(win);
+	n = gdk_screen_get_monitor_at_point(screen, *x, *y);
+	gtk_menu_set_monitor(menu, n);
+
+	*push_in = FALSE;
+}
+
+/* Pop up menu on main window */
+gboolean popup_menu_event(GtkWidget* w, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+
+	gtk_menu_popup(GTK_MENU(ewin->popup_menu),
+	               NULL, NULL, &place_menu, w,
+	               0, gtk_get_current_event_time());
+
+	return TRUE;
+}
+
+/* Callback function for the drag and drop event */
+void drag_data_received(G_GNUC_UNUSED GtkWidget *win,
+                        G_GNUC_UNUSED GdkDragContext *dc,
+                        G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y,
+                        GtkSelectionData *seldata,
+                        G_GNUC_UNUSED guint info, G_GNUC_UNUSED guint t,
+                        gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	gchar **uris, **filenames;
+	gint i, j, n;
+
+	uris = gtk_selection_data_get_uris(seldata);
+	if (!uris)
+		return;
+
+	n = g_strv_length(uris);
+	filenames = g_new0(gchar *, n + 1);
+
+	for (i = j = 0; i < n; i++) {
+		filenames[j] = g_filename_from_uri(uris[i], NULL, NULL);
+		if (filenames[j])
+			j++;
+	}
+	filenames[j] = NULL;
+
+	load_files(ewin, filenames);
+	g_strfreev(filenames);
+}
diff --git a/tool/tilem-src/gui/filedlg.c b/tool/tilem-src/gui/filedlg.c
new file mode 100644
index 0000000..0aefa6d
--- /dev/null
+++ b/tool/tilem-src/gui/filedlg.c
@@ -0,0 +1,1039 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ * Copyright (c) 2011 Thibault Duponchelle // FIXME : My work is based on yours benjamin. Should I put "portions"?! Or something else ?
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#include "gtk-compat.h"
+#include "filedlg.h"
+
+#ifdef GDK_WINDOWING_WIN32
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include <commdlg.h>
+# include <shlobj.h>
+# include <gdk/gdkwin32.h>
+# include <wchar.h>
+
+# ifndef OPENFILENAME_SIZE_VERSION_400
+#  define OPENFILENAME_SIZE_VERSION_400 sizeof(OPENFILENAMEA)
+# endif
+
+struct fcinfo {
+	const char *title;
+	gboolean save;
+	HWND parent_window;
+	char *filename;
+	char *dirname;
+	char *extension;
+	const char *filters;
+	unsigned int flags;
+};
+
+#define BUFFER_SIZE 32768
+
+static char * file_chooser_main(const struct fcinfo *fci)
+{
+	if (G_WIN32_HAVE_WIDECHAR_API()) {
+		OPENFILENAMEW ofnw;
+		wchar_t *titlew, *filterw, *initdirw, *defextw;
+		wchar_t filenamew[BUFFER_SIZE + 1];
+		wchar_t *p;
+		int result;
+		int i;
+
+		titlew = g_utf8_to_utf16(fci->title, -1, 0, 0, 0);
+
+		filterw = g_utf8_to_utf16(fci->filters, -1, 0, 0, 0);
+		for (i = 0; filterw[i]; i++)
+			if (filterw[i] == '\n') filterw[i] = 0;
+
+		memset(&ofnw, 0, sizeof(ofnw));
+		ofnw.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+
+		ofnw.hwndOwner = fci->parent_window;
+		ofnw.lpstrTitle = titlew;
+		ofnw.lpstrFilter = filterw;
+		ofnw.nFilterIndex = 1;
+		ofnw.lpstrFile = filenamew;
+		ofnw.nMaxFile = BUFFER_SIZE;
+
+		memset(filenamew, 0, sizeof(filenamew));
+
+		if (fci->filename) {
+			p = g_utf8_to_utf16(fci->filename, -1, 0, 0, 0);
+			if (p) {
+				wcsncpy(filenamew, p, BUFFER_SIZE);
+				g_free(p);
+			}
+		}
+
+		if (fci->dirname)
+			initdirw = g_utf8_to_utf16(fci->dirname, -1, 0, 0, 0);
+		else
+			initdirw = NULL;
+
+		if (fci->extension)
+			defextw = g_utf8_to_utf16(fci->extension, -1, 0, 0, 0);
+		else
+			defextw = NULL;
+
+		ofnw.lpstrInitialDir = initdirw;
+		ofnw.lpstrDefExt = defextw;
+
+		ofnw.Flags = fci->flags;
+
+		result = (fci->save
+		          ? GetSaveFileNameW(&ofnw)
+		          : GetOpenFileNameW(&ofnw));
+
+		g_free(titlew);
+		g_free(filterw);
+		g_free(initdirw);
+		g_free(defextw);
+
+		if (!result)
+			return NULL;
+
+		if ((fci->flags & OFN_ALLOWMULTISELECT)) {
+			for (i = 0; i < BUFFER_SIZE; i++) {
+				if (filenamew[i] == 0 && filenamew[i + 1] == 0)
+					break;
+				else if (filenamew[i] == '/')
+					filenamew[i] = '\\';
+				else if (filenamew[i] == 0)
+					filenamew[i] = '/';
+			}
+		}
+
+		return g_utf16_to_utf8(filenamew, -1, 0, 0, 0);
+	}
+	else {
+		OPENFILENAMEA ofna;
+		char *titlel, *filterl, *initdirl, *defextl;
+		char filenamel[BUFFER_SIZE + 1];
+		char *p;
+		int result;
+		int i;
+
+		titlel = g_locale_from_utf8(fci->title, -1, 0, 0, 0);
+
+		filterl = g_locale_from_utf8(fci->filters, -1, 0, 0, 0);
+		for (i = 0; filterl[i]; i++)
+			if (filterl[i] == '\n') filterl[i] = 0;
+
+		memset(&ofna, 0, sizeof(ofna));
+		ofna.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+
+		ofna.hwndOwner = fci->parent_window;
+		ofna.lpstrTitle = titlel;
+		ofna.lpstrFilter = filterl;
+		ofna.nFilterIndex = 1;
+		ofna.lpstrFile = filenamel;
+		ofna.nMaxFile = BUFFER_SIZE;
+
+		memset(filenamel, 0, sizeof(filenamel));
+
+		if (fci->filename) {
+			p = g_locale_from_utf8(fci->filename, -1, 0, 0, 0);
+			if (p) {
+				strncpy(filenamel, p, BUFFER_SIZE);
+				g_free(p);
+			}
+		}
+
+		if (fci->dirname)
+			initdirl = g_locale_from_utf8(fci->dirname, -1, 0, 0, 0);
+		else
+			initdirl = NULL;
+
+		if (fci->extension)
+			defextl = g_locale_from_utf8(fci->extension, -1, 0, 0, 0);
+		else
+			defextl = NULL;
+
+		ofna.lpstrInitialDir = initdirl;
+		ofna.lpstrDefExt = defextl;
+
+		ofna.Flags = fci->flags;
+
+		result = (fci->save
+		          ? GetSaveFileNameA(&ofna)
+		          : GetOpenFileNameA(&ofna));
+
+		g_free(titlel);
+		g_free(filterl);
+		g_free(initdirl);
+		g_free(defextl);
+
+		if (!result)
+			return NULL;
+
+		if ((fci->flags & OFN_ALLOWMULTISELECT)) {
+			for (i = 0; i < BUFFER_SIZE; i++) {
+				if (filenamel[i] == 0 && filenamel[i + 1] == 0)
+					break;
+				else if (filenamel[i] == '/')
+					filenamel[i] = '\\';
+				else if (filenamel[i] == 0)
+					filenamel[i] = '/';
+			}
+		}
+
+		return g_locale_to_utf8(filenamel, -1, 0, 0, 0);
+	}
+}
+
+static gboolean wakeup(G_GNUC_UNUSED gpointer data)
+{
+	gtk_main_quit();
+	return FALSE;
+}
+
+static gpointer file_chooser_thread(gpointer data)
+{
+	struct fcinfo *fci = data;
+	gpointer res = file_chooser_main(fci);
+	g_idle_add(wakeup, NULL);
+	return res;
+}
+
+static char * build_filter_string(const char *desc1,
+                                  const char *pattern1,
+                                  va_list ap)
+{
+	GString *str = g_string_new(NULL);
+
+	while (desc1 && pattern1) {
+		if (pattern1[0]) {
+			g_string_append(str, desc1);
+			g_string_append_c(str, '\n');
+			g_string_append(str, pattern1);
+			g_string_append_c(str, '\n');
+		}
+
+		desc1 = va_arg(ap, char *);
+		if (!desc1) break;
+		pattern1 = va_arg(ap, char *);
+	}
+
+	return g_string_free(str, FALSE);
+}
+
+static char ** run_file_chooser1(const char *title,
+                                 GtkWindow *parent,
+                                 gboolean save,
+                                 gboolean multiple,
+                                 const char *suggest_name,
+                                 const char *suggest_dir,
+                                 const char *filters)
+{
+	struct fcinfo fci;
+	GThread *thread;
+	GtkWidget *dummy;
+	GdkWindow *pwin;
+	char *fname, *p, *dir;
+	char **result;
+	int i;
+
+	if (!g_thread_supported())
+		g_thread_init(NULL);
+
+	fci.title = title;
+	fci.save = save;
+
+	if (parent && (pwin = gtk_widget_get_window(GTK_WIDGET(parent))))
+		fci.parent_window = GDK_WINDOW_HWND(pwin);
+	else
+		fci.parent_window = NULL;
+
+	if (suggest_name && suggest_dir) {
+		fci.filename = g_build_filename(suggest_dir,
+		                                suggest_name, NULL);
+		fci.dirname = NULL;
+	}
+	else if (suggest_name) {
+		fci.filename = g_strdup(suggest_name);
+		fci.dirname = NULL;
+	}
+	else if (suggest_dir) {
+		fci.filename = NULL;
+		fci.dirname = g_strdup(suggest_dir);
+	}
+	else {
+		fci.filename = fci.dirname = NULL;
+	}
+
+	if (suggest_name && (p = strrchr(suggest_name, '.')))
+		fci.extension = g_strdup(p + 1);
+	else
+		fci.extension = NULL;
+
+	fci.filters = filters;
+
+	fci.flags = (OFN_HIDEREADONLY | OFN_EXPLORER);
+
+	if (save)
+		fci.flags |= OFN_OVERWRITEPROMPT;
+	else {
+		fci.flags |= OFN_FILEMUSTEXIST;
+		if (multiple)
+			fci.flags |= OFN_ALLOWMULTISELECT;
+	}
+
+	if ((thread = g_thread_create(file_chooser_thread, &fci, TRUE, NULL))) {
+		dummy = gtk_invisible_new();
+		gtk_grab_add(dummy);
+		gtk_main();
+		fname = g_thread_join(thread);
+		gtk_widget_destroy(dummy);
+	}
+	else {
+		fname = file_chooser_main(&fci);
+	}
+
+	g_free(fci.filename);
+	g_free(fci.dirname);
+	g_free(fci.extension);
+
+	if (!fname) {
+		return NULL;
+	}
+	else if (multiple && (p = strchr(fname, '/'))) {
+		dir = g_strndup(fname, p - fname);
+		result = g_strsplit(p + 1, "/", -1);
+
+		for (i = 0; result[i]; i++) {
+			p = result[i];
+			result[i] = g_build_filename(dir, p, NULL);
+			g_free(p);
+		}
+
+		g_free(fname);
+		return result;
+	}
+	else {
+		result = g_new(char *, 2);
+		result[0] = fname;
+		result[1] = NULL;
+		return result;
+	}
+}
+
+static char ** run_file_chooser(const char *title,
+                                GtkWindow *parent,
+                                gboolean save,
+                                gboolean multiple,
+                                const char *suggest_name,
+                                const char *suggest_dir,
+                                const char *desc1,
+                                const char *pattern1,
+                                va_list ap)
+{
+	char *filters;
+	char **result;
+	filters = build_filter_string(desc1, pattern1, ap);
+	result = run_file_chooser1(title, parent, save, multiple,
+	                           suggest_name, suggest_dir, filters);
+	g_free(filters);
+	return result;
+}
+
+struct dcinfo {
+	const char *title;
+	HWND parent_window;
+	wchar_t *suggest_dir_w;
+	char *suggest_dir_l;
+};
+
+static int CALLBACK dir_chooser_callback(HWND hwnd, UINT uMsg,
+                                         G_GNUC_UNUSED LPARAM lParam,
+                                         LPARAM lpData)
+{
+	const struct dcinfo *dci = (struct dcinfo*) lpData;
+
+	if (uMsg != BFFM_INITIALIZED)
+		return 0;
+
+	if (G_WIN32_HAVE_WIDECHAR_API())
+		SendMessageW(hwnd, BFFM_SETSELECTIONW,
+		             TRUE, (LPARAM) dci->suggest_dir_w);
+	else
+		SendMessageA(hwnd, BFFM_SETSELECTIONA,
+		             TRUE, (LPARAM) dci->suggest_dir_l);
+	return 0;
+}
+
+static char * dir_chooser_main(const struct dcinfo *dci)
+{
+	LPITEMIDLIST idl;
+	char *result = NULL;
+
+	CoInitialize(NULL);
+
+	if (G_WIN32_HAVE_WIDECHAR_API()) {
+		BROWSEINFOW bifw;
+		wchar_t dirnamew[MAX_PATH + 1];
+
+		memset(&bifw, 0, sizeof(bifw));
+		bifw.hwndOwner = dci->parent_window;
+		bifw.lpszTitle = g_utf8_to_utf16(dci->title, -1, 0, 0, 0);
+		bifw.ulFlags = (BIF_RETURNONLYFSDIRS | BIF_USENEWUI);
+		bifw.lpfn = &dir_chooser_callback;
+		bifw.lParam = (LPARAM) dci;
+
+		idl = SHBrowseForFolderW(&bifw);
+		if (idl && SHGetPathFromIDListW(idl, dirnamew))
+			result = g_utf16_to_utf8(dirnamew, -1, 0, 0, 0);
+	}
+	else {
+		BROWSEINFOA bifa;
+		char dirnamel[MAX_PATH + 1];
+
+		memset(&bifa, 0, sizeof(bifa));
+		bifa.hwndOwner = dci->parent_window;
+		bifa.lpszTitle = g_locale_from_utf8(dci->title, -1, 0, 0, 0);
+		bifa.ulFlags = (BIF_RETURNONLYFSDIRS | BIF_USENEWUI);
+		bifa.lpfn = &dir_chooser_callback;
+		bifa.lParam = (LPARAM) dci;
+
+		idl = SHBrowseForFolderA(&bifa);
+		if (idl && SHGetPathFromIDListA(idl, dirnamel))
+			result = g_locale_to_utf8(dirnamel, -1, 0, 0, 0);
+	}
+
+	if (idl)
+		CoTaskMemFree(idl);
+
+	CoUninitialize();
+
+	return result;
+}
+
+static gpointer dir_chooser_thread(gpointer data)
+{
+	struct dcinfo *dci = data;
+	gpointer res = dir_chooser_main(dci);
+	g_idle_add(wakeup, NULL);
+	return res;
+}
+
+static char* run_dir_chooser(G_GNUC_UNUSED const char *title,
+                             GtkWindow *parent,
+                             G_GNUC_UNUSED gboolean save,
+                             const char *suggest_dir)
+{
+	struct dcinfo dci;
+	GdkWindow *pwin;
+	GThread *thread;
+	GtkWidget *dummy;
+	char *dname;
+
+	if (!g_thread_supported())
+		g_thread_init(NULL);
+
+	dci.title = "Select a folder to save received files.";
+
+	if (parent && (pwin = gtk_widget_get_window(GTK_WIDGET(parent))))
+		dci.parent_window = GDK_WINDOW_HWND(pwin);
+	else
+		dci.parent_window = NULL;
+
+	if (suggest_dir) {
+		dci.suggest_dir_w = g_utf8_to_utf16(suggest_dir, -1, 0, 0, 0);
+		dci.suggest_dir_l = g_locale_from_utf8(suggest_dir, -1, 0, 0, 0);
+	}
+	else {
+		dci.suggest_dir_w = NULL;
+		dci.suggest_dir_l = NULL;
+	}
+
+	if ((thread = g_thread_create(dir_chooser_thread, &dci, TRUE, NULL))) {
+		dummy = gtk_invisible_new();
+		gtk_grab_add(dummy);
+		gtk_main();
+		dname = g_thread_join(thread);
+		gtk_widget_destroy(dummy);
+	}
+	else {
+		dname = dir_chooser_main(&dci);
+	}
+
+	g_free(dci.suggest_dir_w);
+	g_free(dci.suggest_dir_l);
+
+	return dname;
+}
+
+#else  /* ! GDK_WINDOWING_WIN32 */
+
+/* Case insensitive filter function */
+static gboolean filter_lowercase(const GtkFileFilterInfo *info,
+                                 gpointer data)
+{
+	GSList *list = data;
+	const char *base;
+	char *lowercase, *reversed;
+	int length;
+	gboolean matched = FALSE;
+
+	if ((base = strrchr(info->filename, G_DIR_SEPARATOR)))
+		base++;
+	else
+		base = info->filename;
+
+	lowercase = g_ascii_strdown(base, -1);
+	length = strlen(lowercase);
+	reversed = g_memdup(lowercase, length + 1);
+	g_strreverse(reversed);
+
+	while (list) {
+		if (g_pattern_match(list->data, length,
+		                    lowercase, reversed)) {
+			matched = TRUE;
+			break;
+		}
+		list = list->next;
+	}
+
+	g_free(lowercase);
+	g_free(reversed);
+	return matched;
+}
+
+static void free_filter_info(gpointer data)
+{
+	GSList *list = data, *l;
+	for (l = list; l; l = l->next)
+		g_pattern_spec_free(l->data);
+	g_slist_free(list);
+}
+
+static void setup_file_filters(GtkFileChooser *chooser,
+                               const char *desc1,
+                               const char *pattern1,
+                               va_list ap)
+{
+	GtkFileFilter *ffilt;
+	char **pats;
+	GPatternSpec *pspec;
+	GSList *pspeclist;
+	int i;
+
+	while (desc1 && pattern1) {
+		if (pattern1[0]) {
+			ffilt = gtk_file_filter_new();
+			gtk_file_filter_set_name(ffilt, desc1);
+
+			pats = g_strsplit(pattern1, ";", -1);
+			pspeclist = NULL;
+			for (i = 0; pats && pats[i]; i++) {
+				pspec = g_pattern_spec_new(pats[i]);
+				pspeclist = g_slist_prepend(pspeclist, pspec);
+			}
+			g_strfreev(pats);
+
+			gtk_file_filter_add_custom(ffilt, GTK_FILE_FILTER_FILENAME,
+			                           &filter_lowercase,
+			                           pspeclist,
+			                           &free_filter_info);
+
+			gtk_file_chooser_add_filter(chooser, ffilt);
+		}
+
+		desc1 = va_arg(ap, char *);
+		if (!desc1) break;
+		pattern1 = va_arg(ap, char *);
+	}
+}
+
+static gboolean prompt_overwrite(const char *fname,
+                                 GtkWindow *parent)
+{
+	GtkWidget *dlg;
+	GtkWidget *button;
+	char *p, *q;
+
+	if (!g_file_test(fname, G_FILE_TEST_EXISTS))
+		return TRUE;
+
+	if (!g_file_test(fname, G_FILE_TEST_IS_REGULAR))
+		return FALSE;
+
+	p = g_filename_display_basename(fname);
+	dlg = gtk_message_dialog_new(parent,
+	                             GTK_DIALOG_MODAL,
+	                             GTK_MESSAGE_QUESTION,
+	                             GTK_BUTTONS_NONE,
+	                             "A file named \"%s\" already exists.  "
+	                             "Do you want to replace it?",
+	                             p);
+	g_free(p);
+
+	p = g_path_get_dirname(fname);
+	q = g_filename_display_basename(p);
+	gtk_message_dialog_format_secondary_markup
+		(GTK_MESSAGE_DIALOG(dlg),
+		 "The file already exists in \"%s\".  Replacing it will "
+		 "overwrite its contents.", q);
+	g_free(p);
+	g_free(q);
+
+	gtk_dialog_add_button(GTK_DIALOG(dlg),
+	                      GTK_STOCK_CANCEL,
+	                      GTK_RESPONSE_CANCEL);
+
+	button = gtk_button_new_with_mnemonic("_Replace");
+	gtk_widget_set_can_default(button, TRUE);
+	gtk_button_set_image(GTK_BUTTON(button),
+	                     gtk_image_new_from_stock(GTK_STOCK_SAVE,
+	                                              GTK_ICON_SIZE_BUTTON));
+	gtk_widget_show(button);
+	gtk_dialog_add_action_widget(GTK_DIALOG(dlg), button,
+	                             GTK_RESPONSE_ACCEPT);
+
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
+	                                        GTK_RESPONSE_ACCEPT,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+
+	if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT) {
+		gtk_widget_destroy(dlg);
+		return TRUE;
+	}
+
+	gtk_widget_destroy(dlg);
+	return FALSE;
+}
+
+static char ** run_file_chooser(const char *title,
+                                GtkWindow *parent,
+                                gboolean save,
+                                gboolean multiple,
+                                const char *suggest_name,
+                                const char *suggest_dir,
+                                const char *desc1,
+                                const char *pattern1,
+                                va_list ap)
+{
+	GtkWidget *filesel;
+	GSList *filelist, *l;
+	char *fname;
+	char **fnames;
+	int i, n;
+
+	filesel = gtk_file_chooser_dialog_new(title, parent,
+	                                      (save
+	                                       ? GTK_FILE_CHOOSER_ACTION_SAVE
+	                                       : GTK_FILE_CHOOSER_ACTION_OPEN),
+	                                      GTK_STOCK_CANCEL,
+	                                      GTK_RESPONSE_CANCEL,
+	                                      (save
+	                                       ? GTK_STOCK_SAVE
+	                                       : GTK_STOCK_OPEN),
+	                                      GTK_RESPONSE_ACCEPT,
+	                                      NULL);
+
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(filesel),
+	                                        GTK_RESPONSE_ACCEPT,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+
+	gtk_dialog_set_default_response(GTK_DIALOG(filesel),
+	                                GTK_RESPONSE_ACCEPT);
+
+	if (suggest_dir)
+		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filesel),
+		                                    suggest_dir);
+
+	if (suggest_name)
+		gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filesel),
+		                                  suggest_name);
+
+	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filesel),
+	                                     multiple);
+
+	setup_file_filters(GTK_FILE_CHOOSER(filesel), desc1, pattern1, ap);
+
+	while (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
+		if (save) {
+			fname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
+			if (!fname || !prompt_overwrite(fname, GTK_WINDOW(filesel))) {
+				g_free(fname);
+				continue;
+			}
+
+			fnames = g_new(char *, 2);
+			fnames[0] = fname;
+			fnames[1] = NULL;
+
+			gtk_widget_destroy(filesel);
+			return fnames;
+		}
+		else {
+			filelist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(filesel));
+			if (!filelist)
+				continue;
+
+			n = g_slist_length(filelist);
+			fnames = g_new(char *, n + 1);
+			i = 0;
+			for (l = filelist; l; l = l->next)
+				fnames[i++] = l->data;
+			g_slist_free(filelist);
+			fnames[n] = NULL;
+
+			for (i = 0; i < n; i++)
+				if (!g_file_test(fnames[i],
+				                 G_FILE_TEST_IS_REGULAR))
+					break;
+			if (i < n) {
+				g_strfreev(fnames);
+				continue;
+			}
+
+			gtk_widget_destroy(filesel);
+			return fnames;
+		}
+	}
+
+	gtk_widget_destroy(filesel);
+	return NULL;
+}
+
+static char* run_dir_chooser(const char *title,
+                                GtkWindow *parent,
+                                gboolean save,
+                                const char *suggest_dir)
+{
+	GtkWidget *filesel;
+	char *fname;
+
+	filesel = gtk_file_chooser_dialog_new(title, parent,
+					      GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+	                                      GTK_STOCK_CANCEL,
+	                                      GTK_RESPONSE_CANCEL,
+	                                      (save
+	                                       ? GTK_STOCK_SAVE
+	                                       : GTK_STOCK_OPEN),
+	                                      GTK_RESPONSE_ACCEPT,
+	                                      NULL);
+
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(filesel),
+	                                        GTK_RESPONSE_ACCEPT,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+
+	gtk_dialog_set_default_response(GTK_DIALOG(filesel),
+	                                GTK_RESPONSE_ACCEPT);
+
+	if (suggest_dir)
+		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filesel),
+		                                    suggest_dir);
+
+	while (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
+		fname = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(filesel));
+		if (!fname) {
+			g_free(fname);
+			continue;
+		}
+
+		gtk_widget_destroy(filesel);
+		return fname;
+	}
+
+	gtk_widget_destroy(filesel);
+	return NULL;
+}
+
+#endif  /* ! GDK_WINDOWING_WIN32 */
+
+char * prompt_open_file(const char *title,
+                        GtkWindow *parent,
+                        const char *suggest_dir,
+                        const char *desc1,
+                        const char *pattern1,
+                        ...)
+{
+	char **result, *fname;
+	va_list ap;
+
+	va_start(ap, pattern1);
+	result = run_file_chooser(title, parent, FALSE, FALSE,
+	                          NULL, suggest_dir,
+	                          desc1, pattern1, ap);
+	va_end(ap);
+
+	if (!result || !result[0] || result[1]) {
+		g_strfreev(result);
+		return NULL;
+	}
+	else {
+		fname = result[0];
+		g_free(result);
+		return fname;
+	}
+}
+
+char ** prompt_open_files(const char *title,
+                          GtkWindow *parent,
+                          const char *suggest_dir,
+                          const char *desc1,
+                          const char *pattern1,
+                          ...)
+{
+	char **result;
+	va_list ap;
+
+	va_start(ap, pattern1);
+	result = run_file_chooser(title, parent, FALSE, TRUE,
+	                          NULL, suggest_dir,
+	                          desc1, pattern1, ap);
+	va_end(ap);
+	return result;
+}
+
+char * prompt_save_file(const char *title,
+                        GtkWindow *parent,
+                        const char *suggest_name,
+                        const char *suggest_dir,
+                        const char *desc1,
+                        const char *pattern1,
+                        ...)
+{
+	char **result, *fname;
+	va_list ap;
+
+	va_start(ap, pattern1);
+	result = run_file_chooser(title, parent, TRUE, FALSE,
+	                          suggest_name, suggest_dir,
+	                          desc1, pattern1, ap);
+	va_end(ap);
+
+	if (!result || !result[0] || result[1]) {
+		g_strfreev(result);
+		return NULL;
+	}
+	else {
+		fname = result[0];
+		g_free(result);
+		return fname;
+	}
+}
+
+char * prompt_select_dir(const char *title, GtkWindow *parent, const char *suggest_dir)
+{
+	char *dirname;
+
+	dirname = run_dir_chooser(title, parent, TRUE, suggest_dir);
+
+	if (!dirname) {
+		return NULL;
+	} else {
+		return dirname;
+	}
+}
+
+
+
+/**************** File entry ****************/
+
+#ifdef GDK_WINDOWING_WIN32
+
+typedef struct _FileEntry {
+	GtkHBox parent;
+	GtkWidget *entry;
+	GtkWidget *button;
+	char *title;
+	char *filters;
+	char *filename;
+} FileEntry;
+
+typedef struct _FileEntryClass {
+	GtkHBoxClass parent;
+} FileEntryClass;
+
+static guint selection_changed_signal = 0;
+
+G_DEFINE_TYPE(FileEntry, file_entry, GTK_TYPE_HBOX);
+
+static void file_entry_finalize(GObject *obj)
+{
+	FileEntry *fe = (FileEntry*) obj;
+	g_free(fe->title);
+	g_free(fe->filters);
+	g_free(fe->filename);
+}
+
+void file_entry_set_filename(GtkWidget *entry,
+                             const char *filename)
+{
+	FileEntry *fe = (FileEntry*) entry;
+
+	if (filename && filename[0]) {
+		if (!fe->filename || strcmp(filename, fe->filename)) {
+			g_free(fe->filename);
+			fe->filename = g_strdup(filename);
+			gtk_entry_set_text(GTK_ENTRY(fe->entry), filename);
+			g_signal_emit(fe, selection_changed_signal, 0, NULL);
+		}
+	}
+	else if (fe->filename) {
+		g_free(fe->filename);
+		fe->filename = NULL;
+		g_signal_emit(fe, selection_changed_signal, 0, NULL);		
+	}
+}
+
+char * file_entry_get_filename(GtkWidget *entry)
+{
+	FileEntry *fe = (FileEntry*) entry;
+	if (fe->filename)
+		return g_strdup(fe->filename);
+	else
+		return NULL;
+}
+
+static void focus_changed(G_GNUC_UNUSED GObject *obj,
+                          G_GNUC_UNUSED GParamSpec *pspec,
+                          gpointer data)
+{
+	FileEntry *fe = data;
+	const char *text;
+	text = gtk_entry_get_text(GTK_ENTRY(fe->entry));
+	file_entry_set_filename(GTK_WIDGET(fe), text);
+}
+
+static void browse_for_files(G_GNUC_UNUSED GtkButton *btn, gpointer data)
+{
+	FileEntry *fe = data;
+	GtkWidget *parent;
+	char **result;
+	char *bname, *dname;
+
+	parent = gtk_widget_get_toplevel(GTK_WIDGET(fe));
+
+	if (fe->filename) {
+		bname = g_path_get_basename(fe->filename);
+		dname = g_path_get_dirname(fe->filename);
+	}
+	else {
+		bname = dname = NULL;
+	}
+
+	result = run_file_chooser1(fe->title, GTK_WINDOW(parent), FALSE, FALSE,
+	                           bname, dname, fe->filters);
+	g_free(bname);
+	g_free(dname);
+
+	if (result && result[0])
+		file_entry_set_filename(GTK_WIDGET(fe), result[0]);
+
+	g_strfreev(result);
+}
+
+static void file_entry_init(FileEntry *fe)
+{
+	gtk_box_set_spacing(GTK_BOX(fe), 6);
+
+	fe->entry = gtk_entry_new();
+	fe->button = gtk_button_new_with_label("Browse...");
+	gtk_box_pack_start(GTK_BOX(fe), fe->entry, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(fe), fe->button, FALSE, FALSE, 0);
+	gtk_widget_show(fe->entry);
+	gtk_widget_show(fe->button);
+
+	g_signal_connect(fe->entry, "notify::is-focus",
+	                 G_CALLBACK(focus_changed), fe);
+	g_signal_connect(fe->button, "clicked",
+	                 G_CALLBACK(browse_for_files), fe);
+}
+
+static void file_entry_class_init(FileEntryClass *class)
+{
+	GObjectClass *obj_class;
+
+	obj_class = G_OBJECT_CLASS(class);
+	obj_class->finalize = file_entry_finalize;
+
+	selection_changed_signal =
+		g_signal_new("selection-changed",
+		             G_OBJECT_CLASS_TYPE(obj_class),
+		             G_SIGNAL_RUN_LAST,
+		             0, NULL, NULL,
+		             g_cclosure_marshal_VOID__VOID,
+		             G_TYPE_NONE, 0);
+}
+
+GtkWidget * file_entry_new(const char *title,
+                           const char *desc1,
+                           const char *pattern1,
+                           ...)
+{
+	FileEntry *fe = g_object_new(file_entry_get_type(), NULL);
+	va_list ap;
+
+	fe->title = g_strdup(title);
+
+	va_start(ap, pattern1);
+	fe->filters = build_filter_string(desc1, pattern1, ap);
+	va_end(ap);
+
+	return GTK_WIDGET(fe);
+}
+
+
+#else /* ! GDK_WINDOWING_WIN32 */
+
+GtkWidget * file_entry_new(const char *title,
+                           const char *desc1,
+                           const char *pattern1,
+                           ...)
+{
+	GtkWidget *btn;
+	va_list ap;
+
+	btn = gtk_file_chooser_button_new(title, GTK_FILE_CHOOSER_ACTION_OPEN);
+
+	va_start(ap, pattern1);
+	setup_file_filters(GTK_FILE_CHOOSER(btn), desc1, pattern1, ap);
+	va_end(ap);
+
+	return btn;
+}
+
+void file_entry_set_filename(GtkWidget *fe,
+                             const char *filename)
+{
+	gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(fe), filename);
+}
+
+char * file_entry_get_filename(GtkWidget *fe)
+{
+	return gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fe));
+}
+
+#endif /* ! GDK_WINDOWING_WIN32 */
diff --git a/tool/tilem-src/gui/filedlg.h b/tool/tilem-src/gui/filedlg.h
new file mode 100644
index 0000000..10e672f
--- /dev/null
+++ b/tool/tilem-src/gui/filedlg.h
@@ -0,0 +1,91 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Run a file chooser dialog, allowing user to select a single
+   existing file to open.
+
+   TITLE is the title of the dialog (UTF-8.)
+
+   PARENT is the "parent" (transient-for) window, if any.
+
+   SUGGEST_DIR is the directory to start in (GLib filename encoding.)
+
+   Remaining arguments are a series of pairs of strings describing the
+   permitted file types.  First string in each pair is the
+   description; second is a pattern set (consisting of one or more
+   glob-style patterns, separated by semicolons.)  Patterns must be
+   lowercase; they will be checked case-insensitively.  The list is
+   terminated by NULL.
+
+   A pattern may be the empty string (""); if so, that file type is
+   disabled.
+
+   Result is NULL if dialog was cancelled; otherwise, a string in
+   filename encoding, which must be freed with g_free().
+ */
+char * prompt_open_file(const char *title,        /* UTF-8 */
+                        GtkWindow *parent,
+                        const char *suggest_dir,  /* filename encoding */
+                        const char *desc1,        /* UTF-8 */
+                        const char *pattern1,     /* ASCII */
+                        ...)
+	G_GNUC_NULL_TERMINATED;
+
+/* Run a file chooser dialog, allowing user to select one or more
+   files to open.  Result is either NULL or an array of strings, which
+   must be freed with g_strfreev().  */
+char ** prompt_open_files(const char *title,        /* UTF-8 */
+                          GtkWindow *parent,
+                          const char *suggest_dir,  /* filename encoding */
+                          const char *desc1,        /* UTF-8 */
+                          const char *pattern1,     /* ASCII */
+                          ...)
+	G_GNUC_NULL_TERMINATED;
+
+/* Run a file chooser dialog, allowing user to enter a new filename to
+   be created.  SUGGEST_NAME is a suggested name for the new file;
+   note that this is UTF-8. */
+char * prompt_save_file(const char *title,         /* UTF-8 */
+                        GtkWindow *parent,
+                        const char *suggest_name,  /* UTF-8 (!) */
+                        const char *suggest_dir,   /* filename encoding */
+                        const char *desc1,        /* UTF-8 */
+                        const char *pattern1,     /* ASCII */
+                        ...)
+	G_GNUC_NULL_TERMINATED;
+
+/* Create a file entry or file-chooser button widget, allowing user to
+   select a single existing file to open. */
+GtkWidget * file_entry_new(const char *title, /* UTF-8 */
+                           const char *desc1, /* UTF-8 */
+                           const char *pattern1, /* ASCII */
+                           ...)
+	G_GNUC_NULL_TERMINATED;
+
+/* Set filename in a file entry. */
+void file_entry_set_filename(GtkWidget *fe,
+                             const char *filename); /* filename encoding */
+
+/* Get filename in a file entry.  Result is NULL if no file is
+   selected; otherwise, a string in filename encoding, which must be
+   freed with g_free(). */
+char * file_entry_get_filename(GtkWidget *fe);
+
+/* Run a directory chooser dialog, allowing user to select a directory. */
+char * prompt_select_dir(const char *title, GtkWindow *parent, const char *suggest_dir);
diff --git a/tool/tilem-src/gui/files.c b/tool/tilem-src/gui/files.c
new file mode 100644
index 0000000..ec06d72
--- /dev/null
+++ b/tool/tilem-src/gui/files.c
@@ -0,0 +1,270 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#ifdef G_OS_WIN32
+# include <shlobj.h>
+#endif
+
+#include "files.h"
+
+static char *program_dir;
+
+/* Set the name used to invoke this program */
+void set_program_path(const char *path)
+{
+	if (path && strchr(path, G_DIR_SEPARATOR))
+		program_dir = g_path_get_dirname(path);
+}
+
+/* Build a filename out of varargs */
+static char *build_filenamev(const char *start, va_list rest)
+{
+	char *args[10];
+	int i;
+
+	args[0] = (char*) start;
+	for (i = 1; i < 10; i++) {
+		args[i] = (char*) va_arg(rest, const char *);
+		if (!args[i])
+			break;
+	}
+	g_assert(i < 10);
+
+	return g_build_filenamev(args);
+}
+
+#ifdef G_OS_WIN32
+static char * get_special_folder(int csidl)
+{
+	char lpath[MAX_PATH+1];
+	wchar_t wpath[MAX_PATH+1];
+	LPITEMIDLIST pidl = NULL;
+	gchar *s = NULL;
+
+	if (SHGetSpecialFolderLocation(NULL, csidl, &pidl))
+		return NULL;
+
+	if (G_WIN32_HAVE_WIDECHAR_API()) {
+		if (SHGetPathFromIDListW(pidl, wpath))
+			s = g_utf16_to_utf8(wpath, -1, NULL, NULL, NULL);
+	}
+	else {
+		if (SHGetPathFromIDListA(pidl, lpath))
+			s = g_locale_to_utf8(lpath, -1, NULL, NULL, NULL);
+	}
+
+	CoTaskMemFree(pidl);
+	return s;
+}
+#endif
+
+/* Get the default configuration directory.
+
+   On Unix, this is $XDG_CONFIG_HOME/tilem2 (where $XDG_CONFIG_HOME
+   defaults to $HOME/.config/ if not set.)
+
+   On Windows, this is $CSIDL_LOCAL_APPDATA\tilem2 (where
+   $CSIDL_LOCAL_APPDATA is typically "Local Settings\Application Data"
+   in the user's profile.)
+
+   Result is cached and should not be freed. */
+static char * get_default_config_dir()
+{
+	static char *result;
+
+	if (!result) {
+#ifdef G_OS_WIN32
+		/* Do not use g_get_user_config_dir() on Windows,
+		   because the behavior of that function is not
+		   consistent across versions of GLib. */
+		char *s = get_special_folder(CSIDL_LOCAL_APPDATA);
+		if (s)
+			result = g_build_filename(s, "tilem2", NULL);
+		g_free(s);
+#else
+		result = g_build_filename(g_get_user_config_dir(),
+		                          "tilem2", NULL);
+#endif
+	}
+
+	return result;
+}
+
+/* Search for an existing file.
+
+   The default package configuration directory (defined above) is
+   searched first; if the file is not found there, try to find the
+   file that was installed along with the package, or (in case the
+   package hasn't yet been installed) the copy included in the source
+   package. */
+static char * find_filev(GFileTest test, const char *name, va_list rest)
+{
+	char *fullname, *dname, *path;
+	const char *userdir;
+	const char * const *sysdirs;
+
+	fullname = build_filenamev(name, rest);
+
+	dname = get_default_config_dir();
+	path = g_build_filename(dname, fullname, NULL);
+	if (g_file_test(path, test)) {
+		g_free(fullname);
+		return path;
+	}
+	g_free(path);
+
+#ifdef G_OS_WIN32
+	if ((dname = g_win32_get_package_installation_directory(NULL, NULL))) {
+		path = g_build_filename(dname, "share", "tilem2", fullname, NULL);
+		g_free(dname);
+		if (g_file_test(path, test)) {
+			g_free(fullname);
+			return path;
+		}
+		g_free(path);
+	}
+#endif
+
+#ifdef UNINSTALLED_SHARE_DIR
+	if (program_dir) {
+		path = g_build_filename(program_dir, UNINSTALLED_SHARE_DIR,
+		                        fullname, NULL);
+		if (g_file_test(path, test)) {
+			g_free(fullname);
+			return path;
+		}
+		g_free(path);
+	}
+#endif
+
+#ifdef SHARE_DIR
+	path = g_build_filename(SHARE_DIR, fullname, NULL);
+	if (g_file_test(path, test)) {
+		g_free(fullname);
+		return path;
+	}
+	g_free(path);
+#endif
+
+	userdir = g_get_user_data_dir();
+	if (userdir) {
+		path = g_build_filename(userdir, "tilem2", fullname, NULL);
+		if (g_file_test(path, test)) {
+			g_free(fullname);
+			return path;
+		}
+	}
+
+	sysdirs = g_get_system_data_dirs();
+	while (sysdirs && sysdirs[0]) {
+		path = g_build_filename(sysdirs[0], "tilem2", fullname, NULL);
+		if (g_file_test(path, test)) {
+			g_free(fullname);
+			return path;
+		}
+		sysdirs++;
+	}
+
+	g_free(fullname);
+	return NULL;
+}
+
+/* Locate an existing configuration or data file */
+char * get_shared_file_path(const char *name, ...)
+{
+	va_list ap;
+	char *path;
+	va_start(ap, name);
+	path = find_filev(G_FILE_TEST_IS_REGULAR, name, ap);
+	va_end(ap);
+	return path;
+}
+
+/* Locate an existing configuration or data directory */
+char * get_shared_dir_path(const char *name, ...)
+{
+	va_list ap;
+	char *path;
+	va_start(ap, name);
+	path = find_filev(G_FILE_TEST_IS_DIR, name, ap);
+	va_end(ap);
+	return path;
+}
+
+/* Return the path to the user's configuration directory, where any
+   new or modified config files should be written.  Result is cached
+   and should not be freed. */
+static char * get_config_dir()
+{
+	static char *result;
+	char *fname;
+	FILE *f;
+
+	if (result)
+		return result;
+
+	/* If config.ini already exists, in any of the standard
+	   locations, and is writable, use the directory containing
+	   it.  This will allow building the package as a relocatable
+	   bundle. */
+	fname = get_shared_file_path("config.ini", NULL);
+	if (fname) {
+		f = g_fopen(fname, "r+");
+		if (f) {
+			result = g_path_get_dirname(fname);
+			fclose(f);
+		}
+		g_free(fname);
+	}
+
+	/* Otherwise use default config directory */
+	if (!result)
+		result = g_strdup(get_default_config_dir());
+
+	return result;
+}
+
+/* Get path for writing a new or modified configuration file */
+char * get_config_file_path(const char *name, ...)
+{
+	va_list ap;
+	const char *cfgdir;
+	char *fullname, *path;
+
+	cfgdir = get_config_dir();
+	g_mkdir_with_parents(cfgdir, 0775);
+
+	va_start(ap, name);
+	fullname = build_filenamev(name, ap);
+	va_end(ap);
+	path = g_build_filename(cfgdir, fullname, NULL);
+	g_free(fullname);
+	return path;
+}
+
diff --git a/tool/tilem-src/gui/files.h b/tool/tilem-src/gui/files.h
new file mode 100644
index 0000000..9772d36
--- /dev/null
+++ b/tool/tilem-src/gui/files.h
@@ -0,0 +1,42 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Set the name used to invoke this program.  Data files can be
+   located relative to this path if the package is not installed */
+void set_program_path(const char *path);
+
+/* Locate an existing configuration or data file.  Arguments will be
+   concatenated, separated by / or \, as with g_build_filename().
+   NULL is returned if the file isn't found.  Free result with
+   g_free(). */
+char * get_shared_file_path(const char *name, ...)
+	G_GNUC_NULL_TERMINATED;
+
+/* Locate an existing configuration or data directory.  NULL is
+   returned if the file isn't found.  Free result with g_free(). */
+char * get_shared_dir_path(const char *name, ...)
+	G_GNUC_NULL_TERMINATED;
+
+/* Get the full path where a configuration file should be written;
+   attempt to create the directory if it doesn't exist.  This function
+   will always return a valid filename (although it may not actually
+   be writable.)  Free result with g_free(). */
+char * get_config_file_path(const char *name, ...)
+	G_GNUC_NULL_TERMINATED;
+
diff --git a/tool/tilem-src/gui/fixedtreeview.c b/tool/tilem-src/gui/fixedtreeview.c
new file mode 100644
index 0000000..74c384c
--- /dev/null
+++ b/tool/tilem-src/gui/fixedtreeview.c
@@ -0,0 +1,165 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gobject/gvaluecollector.h>
+
+#include "fixedtreeview.h"
+
+/* Style set on tree view; update column sizes */
+static void ftv_style_set(GtkWidget *treeview,
+                          G_GNUC_UNUSED GtkStyle *oldstyle,
+                          G_GNUC_UNUSED gpointer data)
+{
+	GtkTreeModel *template;
+	GtkTreeIter iter;
+	GList *cols, *cp;
+	GtkTreeViewColumn *col;
+	int width;
+
+	template = g_object_get_data(G_OBJECT(treeview), "ftv-template");
+	if (!template)
+		return;
+
+	if (!gtk_tree_model_get_iter_first(template, &iter))
+		return;
+
+	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(treeview));
+	for (cp = cols; cp; cp = cp->next) {
+		col = cp->data;
+		gtk_tree_view_column_cell_set_cell_data(col, template, &iter,
+		                                        FALSE, FALSE);
+		gtk_tree_view_column_cell_get_size(col, NULL, NULL, NULL,
+		                                   &width, NULL);
+		gtk_tree_view_column_set_fixed_width(col, width + 2);
+	}
+	g_list_free(cols);
+}
+
+/* Widget destroyed */
+static void ftv_destroy(GtkWidget *treeview, G_GNUC_UNUSED gpointer data)
+{
+	GtkTreeModel *template;
+
+	template = g_object_get_data(G_OBJECT(treeview), "ftv-template");
+	if (template)
+		g_object_unref(template);
+	g_object_set_data(G_OBJECT(treeview), "ftv-template", NULL);
+}
+
+void fixed_tree_view_init_with_template(GtkWidget *treeview,
+                                        GtkTreeModel *template)
+{
+	GtkTreeModel *oldtemplate;
+
+	if (template)
+		g_object_ref_sink(template);
+
+	oldtemplate = g_object_get_data(G_OBJECT(treeview), "ftv-template");
+	if (oldtemplate) {
+		g_object_unref(oldtemplate);
+	}
+	else {
+		g_signal_connect(treeview, "style-set",
+		                 G_CALLBACK(ftv_style_set), NULL);
+		g_signal_connect(treeview, "destroy",
+		                 G_CALLBACK(ftv_destroy), NULL);
+	}
+	g_object_set_data(G_OBJECT(treeview), "ftv-template", template);
+
+	if (template && GTK_WIDGET_REALIZED(treeview))
+		ftv_style_set(treeview, NULL, NULL);
+}
+
+void fixed_tree_view_init(GtkWidget *treeview, int colgroupsize, ...)
+{
+	GtkTreeModel *real_model;
+	int ncols, i, col;
+	GType *types;
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GValue value;
+	gchar *error = NULL;
+	va_list ap;
+
+	g_return_if_fail(GTK_IS_TREE_VIEW(treeview));
+	g_return_if_fail(colgroupsize >= 0);
+
+	real_model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
+	g_return_if_fail(real_model != NULL);
+
+	ncols = gtk_tree_model_get_n_columns(real_model);
+	g_return_if_fail(ncols > 0);
+
+	if (colgroupsize == 0)
+		colgroupsize = ncols;
+
+	g_return_if_fail(ncols % colgroupsize == 0);
+
+	types = g_new(GType, ncols);
+	for (i = 0; i < ncols; i++) {
+		types[i] = gtk_tree_model_get_column_type(real_model, i);
+		if (i > colgroupsize)
+			g_return_if_fail(types[i] == types[i - colgroupsize]);
+	}
+	store = gtk_list_store_newv(ncols, types);
+
+	va_start(ap, colgroupsize);
+	gtk_list_store_append(store, &iter);
+
+	memset(&value, 0, sizeof(value));
+
+	col = va_arg(ap, int);
+	while (col != -1) {
+		if (col < 0 || col >= colgroupsize) {
+			g_critical("missing sentinel");
+			break;
+		}
+
+		g_value_init(&value, types[col]);
+
+		G_VALUE_COLLECT(&value, ap, 0, &error);
+
+		if (error) {
+			g_critical("%s", error);
+			g_free(error);
+			break;
+		}
+
+		for (i = col; i < ncols; i += colgroupsize)
+			gtk_list_store_set_value(store, &iter, i, &value);
+
+		g_value_unset(&value);
+
+		col = va_arg(ap, int);
+	}
+
+	va_end(ap);
+
+	g_free(types);
+
+	fixed_tree_view_init_with_template(treeview, GTK_TREE_MODEL(store));
+}
diff --git a/tool/tilem-src/gui/fixedtreeview.h b/tool/tilem-src/gui/fixedtreeview.h
new file mode 100644
index 0000000..f2292bf
--- /dev/null
+++ b/tool/tilem-src/gui/fixedtreeview.h
@@ -0,0 +1,40 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Set up a tree view with fixed-size columns, where the column sizes
+   are determined automatically based on a template.  The template
+   model must contain the same number and types of data columns as the
+   tree view's data model.  The first row of the template model will
+   be used to compute column sizes; any additional rows are
+   ignored. */
+void fixed_tree_view_init_with_template(GtkWidget *treeview,
+                                        GtkTreeModel *template);
+
+/* As above, but the template model is constructed automatically.  The
+   tree view must have a data model attached already.
+
+   Arguments following COLGROUPSIZE are a sequence of (column, data)
+   pairs, as you would pass to gtk_list_store_set().  The list must be
+   terminated with -1.
+
+   If COLGROUPSIZE is a positive integer N, then the template will be
+   constructed by repeating the first N columns as many times as
+   necessary.  In this case, columns K and K+N must always have the
+   same type. */
+void fixed_tree_view_init(GtkWidget *treeview, int colgroupsize, ...);
diff --git a/tool/tilem-src/gui/gifencod.c b/tool/tilem-src/gui/gifencod.c
new file mode 100644
index 0000000..e622c8a
--- /dev/null
+++ b/tool/tilem-src/gui/gifencod.c
@@ -0,0 +1,326 @@
+/* Fast GIF encoder 
+ * taken from http://www.msg.net/utility/whirlgif/gifencod.html - go there for the algorithm explanation
+ *
+ * gifencode.c
+ *
+ * Copyright (c) 1997,1998,1999 by Hans Dinsen-Hansen
+ * The algorithms are inspired by those of gifcode.c
+ * Copyright (c) 1995,1996 Michael A. Mayer
+ * All rights reserved.
+ *
+ * This software may be freely copied, modified and redistributed
+ * without fee provided that above copyright notices are preserved
+ * intact on all copies and modified copies.
+ *
+ * There is no warranty or other guarantee of fitness of this software.
+ * It is provided solely "as is". The author(s) disclaim(s) all
+ * responsibility and liability with respect to this software's usage
+ * or its effect upon hardware or computer systems.
+ *
+ * The Graphics Interchange format (c) is the Copyright property of
+ * Compuserve Incorporated.  Gif(sm) is a Service Mark property of
+ * Compuserve Incorporated.
+ *
+ *
+ *           Implements GIF encoding by means of a tree search.
+ *           --------------------------------------------------
+ *
+ *  - The string table may be thought of being stored in a "b-tree of
+ * steroids," or more specifically, a {256,128,...,4}-tree, depending on
+ * the size of the color map.
+ *  - Each (non-NULL) node contains the string table index (or code) and
+ * {256,128,...,4} pointers to other nodes.
+ *  - For example, the index associated with the string 0-3-173-25 would be
+ * stored in:
+ *       first->node[0]->node[3]->node[173]->node[25]->code
+ *
+ *  - Speed and effectivity considerations, however, have made this
+ * implementation somewhat obscure, because it is costly to initialize
+ * a node-array where most elements will never be used.
+ *  - Initially, a new node will be marked as terminating, TERMIN.
+ * If this node is used at a later stage, its mark will be changed.
+ *  - Only nodes with several used nodes will be associated with a
+ * node-array.  Such nodes are marked LOOKUP.
+ *  - The remaining nodes are marked SEARCH.  They are linked together
+ * in a search-list, where a field, NODE->alt, points at an alternative
+ * following color.
+ *  - It is hardly feasible exactly to predict which nodes will have most
+ * used node pointers.  The theory here is that the very first node as
+ * well as the first couple of nodes which need at least one alternative
+ * color, will be among the ones with many nodes ("... whatever that
+ * means", as my tutor in Num. Analysis and programming used to say).
+ *  - The number of possible LOOKUP nodes depends on the size of the color 
+ * map.  Large color maps will have many SEARCH nodes; small color maps
+ * will probably have many LOOKUP nodes.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef MEMDBG
+#include <mnemosyne.h>
+#endif
+
+#define BLOKLEN 255
+#define BUFLEN 1000
+#define TERMIN 'T'
+#define LOOKUP 'L'
+#define SEARCH 'S'
+#define noOfArrays 20
+/* defines the amount of memory set aside in the encoding for the
+ * LOOKUP type nodes; for a 256 color GIF, the number of LOOKUP
+ * nodes will be <= noOfArrays, for a 128 color GIF the number of
+ * LOOKUP nodes will be <= 2 * noOfArrays, etc.  */
+
+typedef struct GifTree {
+  char typ;             /* terminating, lookup, or search */
+  int code;             /* the code to be output */
+  unsigned char ix;     /* the color map index */
+  struct GifTree **node, *nxt, *alt;
+} GifTree;
+
+char *AddCodeToBuffer(int, short, char *);
+void ClearTree(int, GifTree *);
+
+extern unsigned int debugFlag;
+extern int count;
+
+int chainlen = 0, maxchainlen = 0, nodecount = 0, lookuptypes = 0, nbits;
+short need = 8;
+GifTree *empty[256], GifRoot = {LOOKUP, 0, 0, empty, NULL, NULL},
+        *topNode, *baseNode, **nodeArray, **lastArray;
+
+
+void GifEncode(FILE *fout, unsigned char *pixels, int depth, int siz)
+
+{
+  GifTree *first = &GifRoot, *newNode, *curNode;
+  unsigned char   *end;
+  int     cc, eoi, next, tel=0; //, dbw=0;
+  short   cLength;
+
+  char    *pos, *buffer;
+
+  empty[0] = NULL;
+  need = 8;
+  nodeArray = empty;
+  memmove(++nodeArray, empty, 255*sizeof(GifTree **));
+  if (( buffer = (char *)malloc((BUFLEN+1)*sizeof(char))) == NULL )
+         printf("No memory for writing");
+  buffer++;
+
+  pos = buffer;
+  buffer[0] = 0x0;
+
+  cc = (depth == 1) ? 0x4 : 1<<depth;
+  fputc((depth == 1) ? 2 : depth, fout);
+  eoi = cc+1;
+  next = cc+2;
+
+  cLength = (short) ((depth == 1) ? 3 : depth+1);
+  /*doubled due to 2 color gifs*/
+  if (( topNode = baseNode = (GifTree *)malloc(sizeof(GifTree)*4094*2)) == NULL )
+         printf("No memory for GIF-code tree");
+  if (( nodeArray = first->node = (GifTree **)malloc(256*sizeof(GifTree *)*noOfArrays)) == NULL )
+         printf("No memory for search nodes");
+  lastArray = nodeArray + ( 256*noOfArrays - cc);
+  ClearTree(cc, first);
+
+  pos = AddCodeToBuffer(cc, cLength, pos);
+
+  end = pixels+siz;
+  curNode = first;
+  while(pixels < end) {
+
+    if ( curNode->node[*pixels] != NULL ) {
+      curNode = curNode->node[*pixels];
+      tel++;
+      pixels++;
+      chainlen++;
+      continue;
+    } else if ( curNode->typ == SEARCH ) {
+      newNode = curNode->nxt;
+      while ( newNode->alt != NULL ) {
+        if ( newNode->ix == *pixels ) break;
+        newNode = newNode->alt;
+      }
+      if (newNode->ix == *pixels ) {
+        tel++;
+        pixels++;
+        chainlen++;
+        curNode = newNode;
+        continue;
+      }
+    }
+
+/* ******************************************************
+ * If there is no more thread to follow, we create a new node.  If the
+ * current node is terminating, it will become a SEARCH node.  If it is
+ * a SEARCH node, and if we still have room, it will be converted to a
+ * LOOKUP node.
+*/
+  newNode = ++topNode;
+  switch (curNode->typ ) {
+   case LOOKUP:
+     newNode->nxt = NULL;
+     newNode->alt = NULL,
+     curNode->node[*pixels] = newNode;
+   break;
+   case SEARCH:
+     if ( nodeArray != lastArray ) {
+       nodeArray += cc;
+       curNode->node = nodeArray;
+       curNode->typ = LOOKUP;
+       curNode->node[*pixels] = newNode;
+       curNode->node[(curNode->nxt)->ix] = curNode->nxt;
+       lookuptypes++;
+       newNode->nxt = NULL;
+       newNode->alt = NULL,
+       curNode->nxt = NULL;
+       break;
+     }
+/*   otherwise do as we do with a TERMIN node  */
+   case TERMIN:
+     newNode->alt = curNode->nxt;
+     newNode->nxt = NULL,
+     curNode->nxt = newNode;
+     curNode->typ = SEARCH;
+     break;
+   default:
+     fprintf(stderr, "Silly node type: %d\n", curNode->typ);
+  }
+  newNode->code = next;
+  newNode->ix = *pixels;
+  newNode->typ = TERMIN;
+  newNode->node = empty;
+  nodecount++;
+/*
+* End of node creation
+* ******************************************************
+*/
+#ifdef _WHGDBG
+  if (debugFlag) {
+    if (curNode == newNode) fprintf(stderr, "Wrong choice of node\n");
+    if ( curNode->typ == LOOKUP && curNode->node[*pixels] != newNode ) fprintf(stderr, "Wrong pixel coding\n");
+    if ( curNode->typ == TERMIN ) fprintf(stderr, "Wrong Type coding; frame no = %d; pixel# = %d; nodecount = %d\n", count, tel, nodecount);
+  }
+#endif
+    pos = AddCodeToBuffer(curNode->code, cLength, pos);
+    if ( chainlen > maxchainlen ) maxchainlen = chainlen;
+    chainlen = 0;
+    if(pos-buffer>BLOKLEN) {
+      buffer[-1] = BLOKLEN;
+      fwrite(buffer-1, 1, BLOKLEN+1, fout);
+      buffer[0] = buffer[BLOKLEN];
+      buffer[1] = buffer[BLOKLEN+1];
+      buffer[2] = buffer[BLOKLEN+2];
+      buffer[3] = buffer[BLOKLEN+3];
+      pos -= BLOKLEN;
+    }
+    curNode = first;
+
+    if(next == (1<<cLength)) cLength++;
+    next++;
+
+    if(next == 0x1000) {
+      ClearTree(cc,first);
+      pos = AddCodeToBuffer(cc, cLength, pos);
+      if(pos-buffer>BLOKLEN) {
+        buffer[-1] = BLOKLEN;
+        fwrite(buffer-1, 1, BLOKLEN+1, fout);
+        buffer[0] = buffer[BLOKLEN];
+        buffer[1] = buffer[BLOKLEN+1];
+        buffer[2] = buffer[BLOKLEN+2];
+        buffer[3] = buffer[BLOKLEN+3];
+        pos -= BLOKLEN;
+      }
+      next = cc+2;
+      cLength = (short) ((depth == 1)?3:depth+1);
+    }
+  }
+
+  pos = AddCodeToBuffer(curNode->code, cLength, pos);
+  if(pos-buffer>BLOKLEN-3) {
+    buffer[-1] = BLOKLEN-3;
+    fwrite(buffer-1, 1, BLOKLEN-2, fout);
+    buffer[0] = buffer[BLOKLEN-3];
+    buffer[1] = buffer[BLOKLEN-2];
+    buffer[2] = buffer[BLOKLEN-1];
+    buffer[3] = buffer[BLOKLEN];
+    buffer[4] = buffer[BLOKLEN+1];
+    pos -= BLOKLEN-3;
+  }
+  pos = AddCodeToBuffer(eoi, cLength, pos);
+  pos = AddCodeToBuffer(0x0, -1, pos);
+  buffer[-1] = (char) (pos-buffer);
+
+  fwrite(buffer-1, pos-buffer+1, 1, fout);
+  free(buffer-1);  free(first->node); free(baseNode);
+  buffer=NULL;first->node=NULL;baseNode=NULL;
+#ifdef _WHGDBG
+  if (debugFlag) fprintf(stderr, "pixel count = %d; nodeCount = %d lookup nodes = %d\n", tel, nodecount, lookuptypes);
+#endif
+  return;
+
+}
+
+void ClearTree(int cc, GifTree *root)
+{
+  int i;
+  GifTree *newNode, **xx;
+
+#ifdef _WHGDBG
+  if (debugFlag>1) fprintf(stderr, "Clear Tree  cc= %d\n", cc);
+  if (debugFlag>1) fprintf(stderr, "nodeCount = %d lookup nodes = %d\n", nodecount, lookuptypes);
+#endif
+  maxchainlen=0; lookuptypes = 1;
+  nodecount = 0;
+  nodeArray = root->node;
+  xx= nodeArray;
+  for (i = 0; i < noOfArrays; i++ ) {
+    memmove (xx, empty, 256*sizeof(GifTree **));
+    xx += 256;
+  }
+  topNode = baseNode;
+  for(i=0; i<cc; i++) {
+    root->node[i] = newNode = ++topNode;
+    newNode->nxt = NULL;
+    newNode->alt = NULL;
+    newNode->code = i;
+    newNode->ix = (unsigned char) i;
+    newNode->typ = TERMIN;
+    newNode->node = empty;
+    nodecount++;
+  }
+}
+
+char *AddCodeToBuffer(int code, short n, char *buf)
+{
+  int    mask;
+
+  if(n<0) {
+    if(need<8) {
+      buf++;
+      *buf = 0x0;
+    }
+    need = 8;
+    return buf;
+  }
+
+  while(n>=need) {
+    mask = (1<<need)-1;
+    *buf += (char) ((mask&code)<<(8-need));
+    buf++;
+    *buf = 0x0;
+    code = code>>need;
+    n -= need;
+    need = 8;
+  }
+  if(n) {
+    mask = (1<<n)-1;
+    *buf += (char) ((mask&code)<<(8-need));
+    need -= n;
+  }
+  return buf;
+}
+
diff --git a/tool/tilem-src/gui/gifencod.h b/tool/tilem-src/gui/gifencod.h
new file mode 100644
index 0000000..ebba47e
--- /dev/null
+++ b/tool/tilem-src/gui/gifencod.h
@@ -0,0 +1,200 @@
+/*
+ * whirlgif.h
+ *
+ * Copyright (c) 1997,1998 by Hans Dinsen-Hansen
+ * Copyright (c) 1995,1996 by Kevin Kadow
+ * Copyright (c) 1990,1991,1992 by Mark Podlipec.
+ * All rights reserved.
+ *
+ * This software may be freely copied, modified and redistributed
+ * without fee provided that this copyright notice is preserved
+ * intact on all copies and modified copies.
+ *
+ * There is no warranty or other guarantee of fitness of this software.
+ * It is provided solely "as is". The author(s) disclaim(s) all
+ * responsibility and liability with respect to this software's usage
+ * or its effect upon hardware or computer systems.
+ *
+ * The Graphics Interchange format (c) is the Copyright property of
+ * Compuserve Incorporated.  Gif(sm) is a Service Mark property of
+ * Compuserve Incorporated.
+ *
+ */
+
+#define gif_encod_header
+
+#define DA_REV  3.02
+
+/* common includes */
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _USE_STRINGS_H
+#include <strings.h>
+#else
+#include <string.h>
+#endif
+
+#ifdef _FOPEN_TXT_OR_BIN
+#define WRIBIN	"wb"
+#define REATXT	"rt"
+#define REABIN	"rb"
+#else
+/* Usually there is no need to distinguish between binary and txt */
+#define WRIBIN	"w"
+#define REATXT	"r"
+#define REABIN	"r"
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/* define constants and defaults */
+    /* Default amount of inter-frame time */
+#define DEFAULT_TIME 10
+    /* If set to 1, Netscape 'loop' code will be added by default */
+#define DEFAULT_LOOP 0
+    /* If set to 1, use the colormaps from all images, not just the first */
+#define DEFAULT_USE_COLORMAP 0
+
+    /* Used in calculating the transparent color */
+#define TRANS_NONE 1
+#define TRANS_RGB 2
+#define TRANS_MAP 3
+
+#define DISP_NONE 0
+#define DISP_NOT  1
+#define DISP_BACK 2
+#define DISP_PREV 3
+#define DEFAULT_DISPOSAL DISP_NONE
+    /* set default disposal method here to any of the DISP_XXXX values */
+
+#define BIGSTRING 256
+#define MAXVAL  4100        /* maxval of lzw coding size */
+#define MAXVALP 4200
+#define TERMIN 'T'
+#define LOOKUP 'L'
+#define SEARCH 'S'
+#define noOfArrays 20
+/* defines the amount of memory set aside in the encoding for the
+ * LOOKUP type nodes; for a 256 color GIF, the number of LOOKUP
+ * nodes will be <= noOfArrays, for a 128 color GIF the number of
+ * LOOKUP nodes will be <= 2 * noOfArrays, etc.  */
+
+/* define shorthand for various types */
+#define LONG int
+#define ULONG unsigned int
+#define BYTE char
+#define UBYTE unsigned char
+#define SHORT short
+#define USHORT unsigned short
+#define WORD short int
+#define UWORD unsigned short int
+
+int chainlen = 0, maxchainlen = 0, nodecount = 0, lookuptypes = 0, nbits;
+
+short need = 8;
+
+
+
+
+unsigned int debugFlag, verbose;
+int count;
+
+/* definition of various structures */
+typedef struct Transparency {
+  int type;
+  UBYTE valid;
+  UBYTE map;
+  UBYTE red;
+  UBYTE green;
+  UBYTE blue;
+  } Transparency;
+
+typedef struct Global {
+  Transparency trans;
+  int left;
+  int top;
+  unsigned int time;
+  unsigned short disposal;
+  } Global;
+
+typedef struct GifScreenHdr {
+  int width;
+  int height;
+  UBYTE m;
+  UBYTE cres;
+  UBYTE pixbits;
+  UBYTE bc;
+  UBYTE aspect;
+ } GifScreenHdr;
+
+typedef union GifColor {
+  struct cmap {
+    UBYTE red;
+    UBYTE green;
+    UBYTE blue;
+    UBYTE pad;
+   } cmap;
+  ULONG pixel;
+ } GifColor;
+
+typedef struct GifImageHdr {
+  int left;
+  int top;
+  int width;
+  int height;
+  UBYTE m;
+  UBYTE i;
+  UBYTE pixbits;
+  UBYTE reserved;
+ } GifImageHdr;
+
+typedef struct GifTable {
+  UBYTE valid;
+  UBYTE data;
+  UBYTE first;
+  UBYTE res;
+  int last;
+ } GifTable;
+
+typedef struct GifTree {
+  char typ;             /* terminating, lookup, or search */
+  int code;             /* the code to be output */
+  UBYTE ix;             /* the color map index */
+  struct GifTree **node, *nxt, *alt;
+} GifTree;
+
+GifTree *empty[256], GifRoot = {LOOKUP, 0, 0, empty, NULL, NULL},
+*topNode, *baseNode, **nodeArray, **lastArray;
+
+/* define inline functions */
+#define GifPutShort(i, fout)    {fputc(i&0xff, fout); fputc(i>>8, fout);}
+#define GifGetShort(fin)        (Xgetc(fin) | Xgetc(fin)<<8)
+
+/* forward declaration of the functions  */
+void  CalcTrans();
+void  GifAddToTable();
+void  GifClearTable();
+void  GifComment();
+void  GifDecode();
+void  GifEncode();
+ULONG GifGetCode();
+void  GifGetNextEntry();
+void  GifLoop();
+void  GifReadFile();
+void  GifScreenHeader();
+UBYTE *GifSendData();
+void  ReadImageHeader();
+void  SetOffset();
+void  TheEnd();
+void  TheEnd1();
+void  Usage();
+void  WriteImageHeader();
+UBYTE Xgetc();
+void ClearTree(int cc, GifTree *root);
+char *AddCodeToBuffer(int code, short n, char *buf);
diff --git a/tool/tilem-src/gui/gtk-compat.h b/tool/tilem-src/gui/gtk-compat.h
new file mode 100644
index 0000000..f817781
--- /dev/null
+++ b/tool/tilem-src/gui/gtk-compat.h
@@ -0,0 +1,11 @@
+#if !GTK_CHECK_VERSION(2, 14, 0)
+# define gtk_dialog_get_content_area(d) ((d)->vbox)
+# define gtk_widget_get_window(w) ((w)->window)
+# define gtk_selection_data_get_data(s) ((s)->data)
+#endif
+#if !GTK_CHECK_VERSION(2, 18, 0)
+# define gtk_widget_get_allocation(w, a) (*(a) = (w)->allocation)
+# define gtk_widget_get_visible(w) GTK_WIDGET_VISIBLE(w)
+# define gtk_widget_set_can_default(w, v) g_object_set((w), "can-default", v, NULL)
+# define gtk_widget_set_can_focus(w, v) g_object_set((w), "can-focus", v, NULL)
+#endif
diff --git a/tool/tilem-src/gui/gui.h b/tool/tilem-src/gui/gui.h
new file mode 100644
index 0000000..0e48c2d
--- /dev/null
+++ b/tool/tilem-src/gui/gui.h
@@ -0,0 +1,386 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "animation.h"
+#include "emulator.h"
+#include "skinops.h"
+#include "emuwin.h"
+#include "debugger.h"
+
+#include "gtk-compat.h"
+
+/* This struture is a wrapper for VarEntry with additionnal informations used by tilem */
+typedef struct {
+	int model;
+
+	VarEntry *ve;   	/* Original variable info retrieved
+	                	   from calculator */
+	int slot;       	/* Slot number */
+
+	/* Strings for display (UTF-8) */
+	char *name_str; 	/* Variable name */
+	char *type_str; 	/* Variable type */
+	char *slot_str; 	/* Program slot */
+	char *file_ext; 	/* Default file extension */
+	char *filetype_desc; 	/* File format description */
+
+	int size;            	/* Variable size */
+	gboolean archived;   	/* Is archived */
+	gboolean can_group;  	/* Can be stored in group file */
+
+} TilemVarEntry;
+
+/* Screenshot view (widgets and flags) */
+typedef struct _TilemScreenshotDialog {
+	TilemCalcEmulator *emu;
+	
+	GtkWidget* window;		/* The window itself */
+
+	/* Buttons */
+	GtkWidget* screenshot;		/* Grab button */
+	GtkWidget* record;		/* Record button */
+	GtkWidget* stop;		/* Stop button */
+
+	/* Screenshot menu */
+	GtkWidget* screenshot_preview_image; /* Review pixbuf */
+	GtkWidget* ss_ext_combo; 	/* Combo box for file format */
+	GtkWidget* ss_size_combo; 	/* Combo box for size */
+	GtkWidget* width_spin;		/* The width of the gif */
+	GtkWidget* height_spin;		/* The height of the gif */
+	GtkWidget* grayscale_tb;	/* Toggle Button for enabling/disabling grayscale */	
+	GtkWidget* animation_speed;	/* A scale for the speed of the animation */
+	GtkWidget* background_color;	/* Color chooser : Color used for pixel-off */
+	GtkWidget* foreground_color;	/* Color chooser : Color used for pixel-on */
+
+	TilemAnimation *current_anim;
+	gboolean current_anim_grayscale;
+} TilemScreenshotDialog;
+
+/* This struture is used by receive menu */
+typedef struct _TilemReceiveDialog {
+	TilemCalcEmulator *emu;
+
+	GSList *vars;			/* The list of vars */
+
+	GtkWidget* window;		/* The window itself */
+
+	GtkWidget* treeview;		/* The treeview to print the list of vars */
+	GtkTreeModel* model;		/* The model used by the treeview */
+
+	/* Radio buttons */
+	GtkWidget* mode_box;
+	GtkWidget* multiple_rb;		/* Write multiple files */
+	GtkWidget* group_rb;		/* Write a single group file */
+
+	gboolean refresh_pending;
+} TilemReceiveDialog;
+
+/* Handle the ilp progress stuff */
+typedef struct _TilemLinkProgress {
+	TilemCalcEmulator *emu;
+
+	GtkProgressBar* progress_bar; /* progress bar (total) */
+	GtkLabel* title_lbl;
+	GtkLabel* status_lbl;
+	GtkWidget* window;
+} TilemLinkProgress;
+
+#define LABEL_X_ALIGN 0.0
+
+
+/* ###### event.c ##### */
+
+/* Dialog mesg */
+void show_about();
+
+/* Launch the debugger */
+void launch_debugger(TilemEmulatorWindow *ewin);
+
+/* Button-press event */
+gboolean mouse_press_event(GtkWidget* w, GdkEventButton *event, gpointer data);
+
+/* Pointer-motion event */
+gboolean pointer_motion_event(GtkWidget* w, GdkEventMotion *event, gpointer data);
+
+/* Button-release event */
+gboolean mouse_release_event(GtkWidget* w, GdkEventButton *event, gpointer data);
+
+/* Key-press event */
+gboolean key_press_event(GtkWidget* w, GdkEventKey *event, gpointer data);
+
+/* Key-release event */
+gboolean key_release_event(GtkWidget* w, GdkEventKey *event, gpointer data);
+
+/* Pop up menu on main window */
+gboolean popup_menu_event(GtkWidget* w, gpointer data);
+
+/* Handle drag and drop */
+void drag_data_received(GtkWidget *win, GdkDragContext *dc, gint x, gint y,
+                        GtkSelectionData *seldata, guint info, guint t,
+                        gpointer data);
+
+
+/* ###### emuwin.c ##### */
+
+/* Display the lcd image into the terminal */
+void display_lcdimage_into_terminal(TilemEmulatorWindow *ewin);
+
+/* Redraw the screen with or without skin */
+void redraw_screen(TilemEmulatorWindow *ewin);
+
+
+/* ##### preferences.c ##### */
+
+/* Run preferences dialog. */
+void tilem_preferences_dialog(TilemEmulatorWindow *ewin);
+
+
+/* ##### address.c ##### */
+
+/* Convert address to a displayable string. */
+char * tilem_format_addr(TilemDebugger *dbg, dword addr, gboolean physical);
+
+/* Parse physical address expressed as page and offset. */
+gboolean tilem_parse_paged_addr(TilemDebugger *dbg, const char *pagestr,
+                                const char *offsstr, dword *value);
+
+/* Parse an address or hex constant.  If PHYSICAL is null, only a
+   logical address (simple hex value or symbol) is allowed.  If
+   PHYSICAL is non-null, physical addresses in the form "PAGE:OFFSET"
+   are also allowed.  *PHYSICAL will be set to true if the user
+   entered a physical address. */
+gboolean tilem_parse_addr(TilemDebugger *dbg, const char *string,
+                          dword *value, gboolean *physical);
+
+/* Open a dialog box prompting the user to enter an address.  PARENT
+   is the transient-for window; TITLE is the dialog's title; PROMPT is
+   a label for the input. */
+gboolean tilem_prompt_address(TilemDebugger *dbg, GtkWindow *parent,
+                              const char *title, const char *prompt,
+                              dword *value, gboolean physical,
+                              gboolean usedefault);
+
+
+/* ##### tool.c ##### */
+
+/* Get model name (abbreviation) for a TilEm model ID. */
+const char * model_to_name(int model);
+
+/* Convert model name to a model ID. */
+int name_to_model(const char *name);
+
+/* Convert TilEm model ID to tifiles2 model ID. */
+CalcModel model_to_calcmodel(int model);
+
+/* Convert tifiles2 model ID to TilEm model ID. */
+int calcmodel_to_model(CalcModel model);
+
+/* Get model ID for a given file. */
+int file_to_model(const char *name);
+
+/* Get "base" model for file type support. */
+int model_to_base_model(int calc_model);
+
+/* Check if calc is compatible with given file type. */
+gboolean model_supports_file(int calc_model, int file_model);
+
+/* Create a frame around the given widget */
+GtkWidget* new_frame(const gchar* label, GtkWidget* contents);
+
+/* The popup to choose what kind of rom you are trying to load  (at startup)*/
+char choose_rom_popup(GtkWidget *parent_window, const char *filename, char default_model);
+
+/* Convert UTF-8 to filename encoding.  Use ASCII digits in place of
+   subscripts if necessary.  If conversion fails utterly, fall back to
+   the UTF-8 name, which is broken but better than nothing. */
+char * utf8_to_filename(const char *utf8str);
+
+/* Convert UTF-8 to a subset of UTF-8 that is compatible with the
+   locale */
+char * utf8_to_restricted_utf8(const char *utf8str);
+
+/* Generate default filename (UTF-8) for a variable */
+char * get_default_filename(const TilemVarEntry *tve);
+
+
+/* ##### config.c ##### */
+
+/* Retrieve settings from configuration file.  GROUP is the
+   configuration group; following arguments are a series of OPTION
+   strings, each followed by a pointer to a variable that will receive
+   the value.  The list of options is terminated by NULL.
+
+   Each OPTION is a string of the form "KEY/TYPE" or "KEY/TYPE=VALUE",
+   where KEY is the name of the configuration property, and TYPE is
+   either 'f' for a filename (char*), 's' for a UTF-8 string (char*),
+   'i' for an integer (int), 'r' for a real number (double), or 'b'
+   for a boolean (int).
+
+   VALUE, if specified, is the default value for the option if it has
+   not been defined by the user.  If no VALUE is specified, the option
+   defaults to zero or NULL.
+
+   Strings returned by this function must be freed by the caller
+   (using g_free().) */
+void tilem_config_get(const char *group, const char *option, ...)
+	G_GNUC_NULL_TERMINATED;
+
+/* Save settings to the configuration file.  Arguments are a series of
+   option names, as above, each followed by the new value of the
+   option.  The list is terminated by NULL. */
+void tilem_config_set(const char *group, const char *option, ...)
+	G_GNUC_NULL_TERMINATED;
+
+
+/* ##### link.c ##### */
+
+/* This structure is used to send a file (usually slot=-1, first=TRUE, last=TRUE)*/
+struct TilemSendFileInfo {
+	char *filename;
+	char *display_name;
+	int slot;
+	int first;
+	int last;
+	char *error_message;
+};
+
+/* This structure is used to receive a file */
+struct TilemReceiveFileInfo {
+	GSList *entries;
+	char* destination;
+	char *error_message;
+	gboolean output_tig;
+};
+
+/* Copy a TilemVarEntry structure */
+TilemVarEntry *tilem_var_entry_copy(const TilemVarEntry *tve);
+
+/* Free a previous allocated TilemVarEntry */
+void tilem_var_entry_free(TilemVarEntry *tve);
+
+/* Send a file to the calculator through the GUI.  SLOT is the
+   destination program slot (for TI-81.)  FIRST must be true if this
+   is the first variable in a series; LAST must be true if this is the
+   last in a series. */
+void tilem_link_send_file(TilemCalcEmulator *emu, const char *filename,
+                          int slot, gboolean first, gboolean last);
+
+/* The effective send file function. If there's no good reason, use tilem_link_send_file instead. */
+gboolean send_file_main(TilemCalcEmulator *emu, gpointer data);
+
+/* Request directory listing. */
+void tilem_link_get_dirlist(TilemCalcEmulator *emu);
+
+/* Get the calc model as needed by ticalcs functions */
+int get_calc_model(TilemCalc *calc);
+
+/* Show error */
+void show_error(TilemCalcEmulator *emu, const char *title, const char *message);
+
+/* Receive a variable and write it to a file. */
+void tilem_link_receive_file(TilemCalcEmulator *emu,
+                             const TilemVarEntry* varentry,
+                             const char* destination);
+
+/* Receive a list of variables (GSList of TilemVarEntries) and save
+   them to a group file. */
+void tilem_link_receive_group(TilemCalcEmulator *emu,
+                              GSList *entries,
+                              const char *destination);
+
+/* Receive variables with names matching a pattern.  PATTERN is a
+   glob-like pattern in UTF-8.  Files will be written out to
+   DESTDIR. */
+void tilem_link_receive_matching(TilemCalcEmulator *emu,
+                                 const char *pattern,
+                                 const char *destdir);
+
+
+/* ##### pbar.c ##### */
+
+/* Create or update the progress bar */
+void progress_bar_update(TilemCalcEmulator* emu);
+
+
+
+/* ##### animatedgif.c ##### */
+
+/* Save a TilemAnimation to a GIF file. */
+void tilem_animation_write_gif(TilemAnimation *anim, byte* palette, int palette_size, FILE *fp);
+
+
+/* ##### gifencod.c ##### */
+
+/* Encode gif data */
+void GifEncode(FILE *fout, unsigned char *pixels, int depth, int siz);
+
+
+/* ##### screenshot.c ##### */
+
+/* create the screenshot popup */
+void popup_screenshot_window(TilemEmulatorWindow* ewin);
+
+/* Take a single screenshot */
+void quick_screenshot(TilemEmulatorWindow *ewin);
+
+
+/* ##### keybindings.c ##### */
+
+/* Load the keybindings */
+void tilem_keybindings_init(TilemCalcEmulator* emu, const char* model);
+
+
+/* ##### menu.c ##### */
+
+/* Build the menu (do not print it) */
+void build_menu(TilemEmulatorWindow* ewin);
+
+
+/* ##### sendfile.c ##### */
+
+/* Load a list of files through the GUI.  The list of filenames must
+   end with NULL. */
+void load_files(TilemEmulatorWindow *ewin, char **filenames);
+
+/* Load a list of files from the command line.  Filenames may begin
+   with an optional slot designation. */
+void load_files_cmdline(TilemEmulatorWindow *ewin, char **filenames);
+
+/* Prompt user to load a file from PC to TI */
+void load_file_dialog(TilemEmulatorWindow *ewin);
+
+
+/* ##### rcvmenu.c ##### */
+
+/* Createe the popup dialog */
+void popup_receive_menu(TilemEmulatorWindow *ewin);
+
+/* Create a TilemReceiveDialog */
+TilemReceiveDialog* tilem_receive_dialog_new(TilemCalcEmulator *emu);
+
+/* Destroy a TilemReceiveDialog */
+void tilem_receive_dialog_free(TilemReceiveDialog *rcvdlg);
+
+/* Update TilemReceiveDialog with directory listing.  VARLIST is a
+   GSList of TilemVarEntries; the dialog assumes ownership of this
+   list.  Display the dialog if it's currently hidden. */
+void tilem_receive_dialog_update(TilemReceiveDialog *rcvdlg,
+                                 GSList *varlist);
+
diff --git a/tool/tilem-src/gui/icons.c b/tool/tilem-src/gui/icons.c
new file mode 100644
index 0000000..b1818a4
--- /dev/null
+++ b/tool/tilem-src/gui/icons.c
@@ -0,0 +1,71 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+
+#include "icons.h"
+#include "files.h"
+
+static const char * const custom_icons[] = {
+	/* Disassembly icons */
+	"tilem-disasm-pc",
+	"tilem-disasm-break",
+	"tilem-disasm-break-pc",
+
+	/* Debugger actions */
+	"tilem-db-step",
+	"tilem-db-step-over",
+	"tilem-db-finish"
+};
+
+/* Set up custom icons. */
+void init_custom_icons()
+{
+	GtkIconTheme *theme;
+	GtkIconFactory *factory;
+	GtkIconSet *set;
+	GtkIconSource *source;
+	char *path;
+	gsize i;
+
+	path = get_shared_dir_path("icons", NULL);
+	if (path) {
+		theme = gtk_icon_theme_get_default();
+		gtk_icon_theme_append_search_path(theme, path);
+		g_free(path);
+	}
+
+	factory = gtk_icon_factory_new();
+	for (i = 0; i < G_N_ELEMENTS(custom_icons); i++) {
+		set = gtk_icon_set_new();
+		source = gtk_icon_source_new();
+		gtk_icon_source_set_icon_name(source, custom_icons[i]);
+		gtk_icon_set_add_source(set, source);
+		gtk_icon_source_free(source);
+		gtk_icon_factory_add(factory, custom_icons[i], set);
+		gtk_icon_set_unref(set);
+	}
+	gtk_icon_factory_add_default(factory);
+	g_object_unref(factory);
+}
diff --git a/tool/tilem-src/gui/icons.h b/tool/tilem-src/gui/icons.h
new file mode 100644
index 0000000..3096f4c
--- /dev/null
+++ b/tool/tilem-src/gui/icons.h
@@ -0,0 +1,20 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+void init_custom_icons(void);
diff --git a/tool/tilem-src/gui/keybindings.c b/tool/tilem-src/gui/keybindings.c
new file mode 100644
index 0000000..cbf4afd
--- /dev/null
+++ b/tool/tilem-src/gui/keybindings.c
@@ -0,0 +1,255 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle 
+ * Copyright (c) 2010-2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "msgbox.h"
+#include "files.h"
+
+/* Get the associated calculator key name */
+static int calc_key_from_name(const TilemCalc *calc, const char *name)
+{
+	int i;
+
+	for (i = 0; i < 64; i++)
+		if (calc->hw.keynames[i]
+		    && !strcmp(calc->hw.keynames[i], name))
+			return i + 1;
+
+	/* kludge: accept aliases for a few keys */
+	for (i = 0; i < 64; i++) {
+		if (!calc->hw.keynames[i])
+			continue;
+
+		if (!strcmp(name, "Matrix")
+		    && !strcmp(calc->hw.keynames[i], "Apps"))
+			return i + 1;
+		if (!strcmp(name, "Apps")
+		    && !strcmp(calc->hw.keynames[i], "AppsMenu"))
+			return i + 1;
+		if (!strcmp(name, "List")
+		    && !strcmp(calc->hw.keynames[i], "StatEd"))
+			return i + 1;
+		if (!strcmp(name, "Power")
+		    && !strcmp(calc->hw.keynames[i], "Expon"))
+			return i + 1;
+		if (!strcmp(name, "Stat")
+		    && !strcmp(calc->hw.keynames[i], "Table"))
+			return i + 1;
+	}
+
+	return 0;
+}
+
+/* Parse a line of the group (model) in the keybindings file */
+static gboolean parse_binding(TilemKeyBinding *kb,
+                              const char *pckeys, const char *tikeys,
+                              const TilemCalc *calc)
+{
+	const char *p;
+	char *s;
+	int n, k;
+
+	kb->modifiers = 0;
+	kb->keysym = 0;
+	kb->nscancodes = 0;
+	kb->scancodes = NULL;
+
+	/* Parse modifiers */
+
+	while ((p = strchr(pckeys, '+'))) {
+		s = g_strndup(pckeys, p - pckeys);
+		g_strstrip(s);
+		if (!g_ascii_strcasecmp(s, "ctrl")
+		    || !g_ascii_strcasecmp(s, "control"))
+			kb->modifiers |= GDK_CONTROL_MASK;
+		else if (!g_ascii_strcasecmp(s, "shift"))
+			kb->modifiers |= GDK_SHIFT_MASK;
+		else if (!g_ascii_strcasecmp(s, "alt")
+		         || !g_ascii_strcasecmp(s, "mod1"))
+			kb->modifiers |= GDK_MOD1_MASK;
+		else if (!g_ascii_strcasecmp(s, "mod2"))
+			kb->modifiers |= GDK_MOD2_MASK;
+		else if (!g_ascii_strcasecmp(s, "mod3"))
+			kb->modifiers |= GDK_MOD3_MASK;
+		else if (!g_ascii_strcasecmp(s, "mod4"))
+			kb->modifiers |= GDK_MOD4_MASK;
+		else if (!g_ascii_strcasecmp(s, "mod5"))
+			kb->modifiers |= GDK_MOD5_MASK;
+		else if (!g_ascii_strcasecmp(s, "lock")
+		         || !g_ascii_strcasecmp(s, "capslock"))
+			kb->modifiers |= GDK_LOCK_MASK;
+		else {
+			g_free(s);
+			return FALSE;
+		}
+		g_free(s);
+		pckeys = p + 1;
+	}
+
+	/* Parse keysym */
+
+	s = g_strstrip(g_strdup(pckeys));
+	kb->keysym = gdk_keyval_from_name(s);
+	g_free(s);
+	if (!kb->keysym)
+		return FALSE;
+
+	/* Parse calculator keys */
+
+	/* FIXME: allow combinations of simultaneous keys (separated
+	   by '+'); current TilemKeyBinding struct doesn't provide for
+	   this */
+
+	n = 0;
+	do {
+		if ((p = strchr(tikeys, ',')))
+			s = g_strndup(tikeys, p - tikeys);
+		else
+			s = g_strdup(tikeys);
+		g_strstrip(s);
+
+		k = calc_key_from_name(calc, s);
+		g_free(s);
+
+		if (!k) {
+			g_free(kb->scancodes);
+			kb->scancodes = NULL;
+			return FALSE;
+		}
+
+		kb->nscancodes++;
+		if (kb->nscancodes >= n) {
+			n = kb->nscancodes * 2;
+			kb->scancodes = g_renew(byte, kb->scancodes, n);
+		}
+		kb->scancodes[kb->nscancodes - 1] = k;
+
+		tikeys = (p ? p + 1 : NULL);
+	} while (tikeys);
+
+	return TRUE;
+}
+
+/* Parse a group (model) in the keybindings file */
+static void parse_binding_group(TilemCalcEmulator *emu, GKeyFile *gkf,
+                                const char *group, int maxdepth)
+{
+	gchar **keys, **groups;
+	char *k, *v;
+	int i, n;
+
+	keys = g_key_file_get_keys(gkf, group, NULL, NULL);
+	if (!keys) {
+		printf("no bindings for %s\n", group);
+		return;
+	}
+
+	for (i = 0; keys[i]; i++)
+		;
+
+	n = emu->nkeybindings;
+	emu->keybindings = g_renew(TilemKeyBinding, emu->keybindings, n + i);
+
+	for(i = 0; keys[i]; i++) {
+		k = keys[i];
+		if (!strcmp(k, "INHERIT"))
+			continue;
+
+		v = g_key_file_get_value(gkf, group, k, NULL);
+		if (!v)
+			continue;
+
+		if (parse_binding(&emu->keybindings[n], k, v, emu->calc))
+			n++;
+		else
+			g_printerr("syntax error in key bindings: '%s=%s'\n",
+			           k, v);
+		g_free(v);
+	}
+
+	emu->nkeybindings = n;
+
+	g_strfreev(keys);
+
+	/* Include all bindings from groups marked as INHERIT */
+
+	if (maxdepth == 0)
+		return;
+
+	groups = g_key_file_get_string_list(gkf, group, "INHERIT",
+	                                    NULL, NULL);
+	for (i = 0; groups && groups[i]; i++)
+		parse_binding_group(emu, gkf, groups[i], maxdepth - 1);
+	g_strfreev(groups);
+
+}
+
+/* Init the keybindings struct and open the keybindings file */
+void tilem_keybindings_init(TilemCalcEmulator *emu, const char *model)
+{
+	char *kfname = get_shared_file_path("keybindings.ini", NULL);
+	char *dname;
+	GKeyFile *gkf;
+	GError *err = NULL;
+
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(emu->calc != NULL);
+
+	if (kfname == NULL) {
+		messagebox00(NULL, GTK_MESSAGE_ERROR,
+		             "Unable to load key bindings",
+		             "The file keybindings.ini could not be found."
+		             "  TilEm may not have been installed correctly.");
+		return;
+	}
+
+	gkf = g_key_file_new();
+	if (!g_key_file_load_from_file(gkf, kfname, 0, &err)) {
+		dname = g_filename_display_name(kfname);
+		messagebox02(NULL, GTK_MESSAGE_ERROR,
+		             "Unable to load key bindings",
+		             "An error occurred while reading %s: %s",
+		             dname, err->message);
+		g_error_free(err);
+		g_free(dname);
+		g_free(kfname);
+		return;
+	}
+
+	g_free(emu->keybindings);
+	emu->keybindings = NULL;
+	emu->nkeybindings = 0;
+
+	parse_binding_group(emu, gkf, model, 5);
+
+	g_key_file_free(gkf);
+	g_free(kfname);
+}
diff --git a/tool/tilem-src/gui/keypaddlg.c b/tool/tilem-src/gui/keypaddlg.c
new file mode 100644
index 0000000..e099d20
--- /dev/null
+++ b/tool/tilem-src/gui/keypaddlg.c
@@ -0,0 +1,305 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include <scancodes.h>
+
+#include "gui.h"
+
+#define NGROUPS 7
+#define NKEYS 8
+
+/* Check-button toggled */
+static void group_toggled(GtkToggleButton *btn, gpointer data)
+{
+	TilemKeypadDialog *kpdlg = data;
+	TilemCalcEmulator *emu;
+	int i;
+	gboolean state;
+
+	if (kpdlg->refreshing)
+		return;
+
+	g_return_if_fail(kpdlg->dbg != NULL);
+	g_return_if_fail(kpdlg->dbg->emu != NULL);
+	emu = kpdlg->dbg->emu;
+
+	state = gtk_toggle_button_get_active(btn);
+
+	for (i = 0; i < NGROUPS; i++) {
+		if (GTK_WIDGET(btn) == kpdlg->output[i]) {
+			tilem_calc_emulator_lock(emu);
+			if (state)
+				emu->calc->keypad.group &= ~(1 << i);
+			else
+				emu->calc->keypad.group |= (1 << i);
+			tilem_calc_emulator_unlock(emu);
+
+			tilem_keypad_dialog_refresh(kpdlg);
+			return;
+		}
+	}
+
+	g_return_if_reached();
+}
+
+/* Key toggled */
+static void key_toggled(GtkToggleButton *btn, gpointer data)
+{
+	TilemKeypadDialog *kpdlg = data;
+	TilemCalcEmulator *emu;
+	int i, j, k;
+	gboolean state;
+
+	if (kpdlg->refreshing)
+		return;
+
+	g_return_if_fail(kpdlg->dbg != NULL);
+	g_return_if_fail(kpdlg->dbg->emu != NULL);
+	emu = kpdlg->dbg->emu;
+
+	state = gtk_toggle_button_get_active(btn);
+
+	for (i = 0; i < NGROUPS; i++) {
+		for (j = 0; j < NKEYS; j++) {
+			if (GTK_WIDGET(btn) == kpdlg->keys[i][j]) {
+				k = i * 8 + j + 1;
+				if (state)
+					tilem_calc_emulator_press_key(emu, k);
+				else
+					tilem_calc_emulator_release_key(emu, k);
+				return;
+			}
+		}
+	}
+
+	g_return_if_reached();
+}
+
+/* Create a new TilemKeypadDialog. */
+TilemKeypadDialog *tilem_keypad_dialog_new(TilemDebugger *dbg)
+{
+	TilemKeypadDialog *kpdlg;
+	GtkWidget *tbl1, *tbl2, *hbox, *vbox, *btn, *lbl;
+	int i, j;
+	char buf[20];
+
+	g_return_val_if_fail(dbg != NULL, NULL);
+
+	kpdlg = g_slice_new0(TilemKeypadDialog);
+	kpdlg->dbg = dbg;
+
+	kpdlg->window = gtk_dialog_new_with_buttons
+		("Keypad", NULL, 0,
+		 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+		 NULL);
+
+	g_signal_connect(kpdlg->window, "delete-event",
+	                 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
+
+	tbl1 = gtk_table_new(NGROUPS, NKEYS, TRUE);
+	hbox = gtk_hbox_new(TRUE, 0);
+	vbox = gtk_vbox_new(TRUE, 0);
+
+	/* Keypad buttons (labels will be filled in, and buttons
+	   shown/hidden, by tilem_keypad_dialog_calc_changed())
+
+	   Buttons are displayed right to left, top to bottom; this
+	   way, the layout of groups 1-5 roughly corresponds to the
+	   physical layout of the keys, and the "input" value can be
+	   read across the bottom as a binary number. */
+
+	for (i = 0; i < NGROUPS; i++) {
+		for (j = 0; j < NKEYS; j++) {
+			btn = gtk_toggle_button_new_with_label("");
+			kpdlg->keys[i][j] = btn;
+			gtk_table_attach(GTK_TABLE(tbl1), btn,
+			                 NKEYS - j - 1, NKEYS - j,
+			                 i, i + 1,
+			                 GTK_FILL, GTK_FILL, 2, 2);
+
+			g_signal_connect(btn, "toggled",
+			                 G_CALLBACK(key_toggled), kpdlg);
+
+			gtk_widget_set_no_show_all(btn, TRUE);
+		}
+	}
+
+	/* Check buttons for key groups (output bits) */
+
+	for (i = 0; i < NGROUPS; i++) {
+		g_snprintf(buf, sizeof(buf), "Group %d", i);
+		btn = gtk_check_button_new_with_label(buf);
+		kpdlg->output[i] = btn;
+		gtk_box_pack_start(GTK_BOX(vbox), btn, FALSE, TRUE, 2);
+
+		g_signal_connect(btn, "toggled",
+		                 G_CALLBACK(group_toggled), kpdlg);
+	}
+
+	/* Labels for input bits */
+
+	for (j = NKEYS - 1; j >= 0; j--) {
+		kpdlg->input[j] = lbl = gtk_label_new("");
+		gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, TRUE, 2);
+	}
+
+	tbl2 = gtk_table_new(3, 2, FALSE);
+	gtk_container_set_border_width(GTK_CONTAINER(tbl2), 6);
+	gtk_table_set_row_spacings(GTK_TABLE(tbl2), 12);
+	gtk_table_set_col_spacings(GTK_TABLE(tbl2), 12);
+
+	lbl = gtk_label_new(NULL);
+	gtk_label_set_markup(GTK_LABEL(lbl), "<b>Scan Groups</b>");
+	gtk_table_attach(GTK_TABLE(tbl2), lbl, 0, 1, 0, 1,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	lbl = gtk_label_new(NULL);
+	gtk_label_set_markup(GTK_LABEL(lbl), "<b>Keys</b>");
+	gtk_table_attach(GTK_TABLE(tbl2), lbl, 1, 2, 0, 1,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	lbl = gtk_label_new("Input Value:");
+	gtk_table_attach(GTK_TABLE(tbl2), lbl, 0, 1, 2, 3,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	gtk_table_attach(GTK_TABLE(tbl2), vbox, 0, 1, 1, 2,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+	gtk_table_attach(GTK_TABLE(tbl2), tbl1, 1, 2, 1, 2,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+	gtk_table_attach(GTK_TABLE(tbl2), hbox, 1, 2, 2, 3,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	gtk_widget_show_all(tbl2);
+
+	vbox = gtk_dialog_get_content_area(GTK_DIALOG(kpdlg->window));
+	gtk_box_pack_start(GTK_BOX(vbox), tbl2, FALSE, FALSE, 0);
+
+	tilem_keypad_dialog_calc_changed(kpdlg);
+
+	return kpdlg;
+}
+
+/* Free a TilemKeypadDialog. */
+void tilem_keypad_dialog_free(TilemKeypadDialog *kpdlg)
+{
+	g_return_if_fail(kpdlg != NULL);
+	if (kpdlg->window)
+		gtk_widget_destroy(kpdlg->window);
+	g_slice_free(TilemKeypadDialog, kpdlg);
+}
+
+/* New calculator loaded. */
+void tilem_keypad_dialog_calc_changed(TilemKeypadDialog *kpdlg)
+{
+	TilemCalc *calc;
+	int i, j, k;
+	GtkWidget *btn, *lbl;
+
+	g_return_if_fail(kpdlg != NULL);
+	g_return_if_fail(kpdlg->dbg != NULL);
+	g_return_if_fail(kpdlg->dbg->emu != NULL);
+	g_return_if_fail(kpdlg->dbg->emu->calc != NULL);
+	calc = kpdlg->dbg->emu->calc;
+
+	for (i = 0; i < NGROUPS; i++) {
+		for (j = 0; j < NKEYS; j++) {
+			btn = kpdlg->keys[i][j];
+			k = i * 8 + j + 1;
+			if (k != TILEM_KEY_ON
+			    && calc->hw.keynames[k - 1] != NULL) {
+				lbl = gtk_bin_get_child(GTK_BIN(btn));
+				gtk_label_set_text(GTK_LABEL(lbl),
+				                   calc->hw.keynames[k - 1]);
+				gtk_widget_show(btn);
+			}
+			else {
+				gtk_widget_hide(btn);
+			}
+		}
+	}
+
+	tilem_keypad_dialog_refresh(kpdlg);
+}
+
+/* Refresh key states. */
+void tilem_keypad_dialog_refresh(TilemKeypadDialog *kpdlg)
+{
+	int i, j;
+	byte keys[NGROUPS], inval, outval;
+	TilemCalcEmulator *emu;
+	GtkWidget *btn, *lbl;
+
+	g_return_if_fail(kpdlg != NULL);
+	g_return_if_fail(kpdlg->dbg != NULL);
+	g_return_if_fail(kpdlg->dbg->emu != NULL);
+	emu = kpdlg->dbg->emu;
+
+	if (kpdlg->refreshing)
+		return;
+
+	kpdlg->refreshing = TRUE;
+
+	tilem_calc_emulator_lock(emu);
+	for (i = 0; i < NGROUPS; i++)
+		keys[i] = emu->calc->keypad.keysdown[i];
+	outval = emu->calc->keypad.group;
+	inval = tilem_keypad_read_keys(emu->calc);
+	tilem_calc_emulator_unlock(emu);
+
+	for (i = 0; i < NGROUPS; i++) {
+		for (j = 0; j < NKEYS; j++) {
+			btn = kpdlg->keys[i][j];
+			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(btn),
+			                             (keys[i] & (1 << j)));
+		}
+	}
+
+
+	for (i = 0; i < NGROUPS; i++) {
+		btn = kpdlg->output[i];
+		if (emu->paused) {
+			gtk_widget_set_sensitive(btn, TRUE);
+			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(btn),
+			                             !(outval & (1 << i)));
+		}
+		else {
+			gtk_widget_set_sensitive(btn, FALSE);
+		}
+	}
+
+	for (j = 0; j < NKEYS; j++) {
+		lbl = kpdlg->input[j];
+		gtk_label_set_text(GTK_LABEL(lbl), 
+		                   (emu->paused
+		                    ? (inval & (1 << j) ? "1" : "0")
+		                    : ""));
+
+	}
+
+	kpdlg->refreshing = FALSE;
+}
diff --git a/tool/tilem-src/gui/link.c b/tool/tilem-src/gui/link.c
new file mode 100644
index 0000000..6a3ecc6
--- /dev/null
+++ b/tool/tilem-src/gui/link.c
@@ -0,0 +1,1301 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <ticonv.h>
+#include <string.h>
+#include <tilem.h>
+#include <scancodes.h>
+
+#include "gui.h"
+#include "emucore.h"
+#include "ti81prg.h"
+#include "msgbox.h"
+
+/**************** Internal link emulation ****************/
+
+/* Open cable */
+static int ilp_open(CableHandle* cbl)
+{
+	TilemCalcEmulator* emu = cbl->priv;
+
+	tilem_em_lock(emu);
+
+	if (emu->ilp_active) {
+		fprintf(stderr, "INTERNAL ERROR: cable already opened\n");
+		tilem_em_unlock(emu);
+		return 1;
+	}
+
+	emu->ilp_active = TRUE;
+	tilem_linkport_graylink_reset(emu->calc);
+	tilem_em_unlock(emu);
+	return 0;
+}
+
+/* Close cable */
+static int ilp_close(CableHandle* cbl)
+{
+	TilemCalcEmulator* emu = cbl->priv;
+
+	tilem_em_lock(emu);
+
+	if (!emu->ilp_active) {
+		fprintf(stderr, "INTERNAL ERROR: cable already closed\n");
+		tilem_em_unlock(emu);
+		return 1;
+	}
+
+	emu->ilp_active = FALSE;
+	emu->calc->linkport.linkemu = TILEM_LINK_EMULATOR_NONE;
+	tilem_linkport_graylink_reset(emu->calc);
+	tilem_em_unlock(emu);
+	return 0;
+}
+
+/* Reset cable */
+static int ilp_reset(CableHandle* cbl)
+{
+	TilemCalcEmulator* emu = cbl->priv;
+
+	tilem_em_lock(emu);
+	tilem_linkport_graylink_reset(emu->calc);
+	tilem_em_unlock(emu);
+	return 0;
+}
+
+/* Send data to calc */
+static int ilp_send(CableHandle* cbl, uint8_t* data, uint32_t count)
+{
+	TilemCalcEmulator* emu = cbl->priv;
+	int timeout = cbl->timeout * 100000;
+
+	tilem_em_lock(emu);
+	while (count > 0) {
+		if (tilem_em_send_byte(emu, data[0], timeout, TRUE)) {
+			tilem_em_unlock(emu);
+			return ERROR_WRITE_TIMEOUT;
+		}
+		data++;
+		count--;
+	}
+	tilem_em_unlock(emu);
+	return 0;
+}
+
+/* cool-down period required after receiving and before sending */
+#define COOLDOWN 10000
+
+/* Receive data from calc */
+static int ilp_recv(CableHandle* cbl, uint8_t* data, uint32_t count)
+{
+	TilemCalcEmulator* emu = cbl->priv;
+	int timeout = cbl->timeout * 100000;
+	int value;
+
+	tilem_em_lock(emu);
+	while (count > 0) {
+		value = tilem_em_get_byte(emu, timeout, TRUE);
+		if (value < 0) {
+			tilem_em_unlock(emu);
+			return ERROR_READ_TIMEOUT;
+		}
+		data[0] = value;
+		data++;
+		count--;
+	}
+	tilem_em_delay(emu, COOLDOWN, TRUE);
+	tilem_em_unlock(emu);
+	return 0;
+}
+
+/* Check if ready */
+static int ilp_check(CableHandle* cbl, int* status)
+{
+	TilemCalcEmulator* emu = cbl->priv;
+
+	tilem_em_lock(emu);
+
+	*status = STATUS_NONE;
+	if (emu->calc->linkport.lines)
+		*status |= STATUS_RX;
+	if (emu->calc->linkport.extlines)
+		*status |= STATUS_TX;
+
+	tilem_em_unlock(emu);
+	return 0;
+}
+
+/* Open a cable */
+static CableHandle* internal_link_handle_new(TilemCalcEmulator* emu)
+{
+	CableHandle* cbl;
+
+	cbl = ticables_handle_new(CABLE_ILP, PORT_0);
+	if (!cbl)
+		return NULL;
+
+	cbl->priv = emu;
+	cbl->cable->open = ilp_open;
+	cbl->cable->close = ilp_close;
+	cbl->cable->reset = ilp_reset;
+	cbl->cable->send = ilp_send;
+	cbl->cable->recv = ilp_recv;
+	cbl->cable->check = ilp_check;
+
+	return cbl;
+}
+
+/**************** Automatic link menu ****************/
+
+/* Run a key (wait, press, wait; release; wait) */
+static void run_with_key(TilemCalcEmulator* emu, int key)
+{
+	tilem_em_delay(emu, 50000, TRUE);
+	tilem_keypad_press_key(emu->calc, key);
+	tilem_em_delay(emu, 50000, TRUE);
+	tilem_keypad_release_key(emu->calc, key);
+	tilem_em_delay(emu, 50000, TRUE);
+}
+
+/* Automatically press key to be in the receive mode (ti82 and ti85) */
+static void prepare_for_link_send(TilemCalcEmulator* emu)
+{
+	tilem_em_lock(emu);
+	tilem_em_wake_up(emu, TRUE);
+	if (emu->calc->hw.model_id == TILEM_CALC_TI82) {
+		run_with_key(emu, TILEM_KEY_2ND);
+		run_with_key(emu, TILEM_KEY_MODE);
+		run_with_key(emu, TILEM_KEY_2ND);
+		run_with_key(emu, TILEM_KEY_GRAPHVAR);
+		run_with_key(emu, TILEM_KEY_RIGHT);
+		run_with_key(emu, TILEM_KEY_ENTER);
+	}
+	else if (emu->calc->hw.model_id == TILEM_CALC_TI85) {
+		run_with_key(emu, TILEM_KEY_MODE);
+		run_with_key(emu, TILEM_KEY_MODE);
+		run_with_key(emu, TILEM_KEY_MODE);
+		run_with_key(emu, TILEM_KEY_2ND);
+		run_with_key(emu, TILEM_KEY_GRAPHVAR);
+		run_with_key(emu, TILEM_KEY_WINDOW);
+	}
+	tilem_em_unlock(emu);
+}
+
+/* Automatically press key to be in the send mode (ti82 and ti85) */
+static void prepare_for_link_receive(TilemCalcEmulator *emu)
+{
+	tilem_em_lock(emu);
+	tilem_em_wake_up(emu, TRUE);
+	if (emu->calc->hw.model_id == TILEM_CALC_TI82) {
+		run_with_key(emu, TILEM_KEY_2ND);
+		run_with_key(emu, TILEM_KEY_MODE);
+		run_with_key(emu, TILEM_KEY_2ND);
+		run_with_key(emu, TILEM_KEY_GRAPHVAR);
+		run_with_key(emu, TILEM_KEY_ENTER);
+		tilem_em_delay(emu, 10000000, TRUE);
+		run_with_key(emu, TILEM_KEY_RIGHT);
+		run_with_key(emu, TILEM_KEY_ENTER);
+	}
+	else if (emu->calc->hw.model_id == TILEM_CALC_TI85) {
+		run_with_key(emu, TILEM_KEY_MODE);
+		run_with_key(emu, TILEM_KEY_MODE);
+		run_with_key(emu, TILEM_KEY_MODE);
+		run_with_key(emu, TILEM_KEY_2ND);
+		run_with_key(emu, TILEM_KEY_GRAPHVAR);
+		run_with_key(emu, TILEM_KEY_YEQU);
+		run_with_key(emu, TILEM_KEY_GRAPH);
+		tilem_em_delay(emu, 10000000, TRUE);
+		run_with_key(emu, TILEM_KEY_ZOOM);
+		tilem_em_delay(emu, 10000000, TRUE);
+		run_with_key(emu, TILEM_KEY_YEQU);
+	}
+	tilem_em_unlock(emu);
+}
+
+/**************** Calc handle ****************/
+
+static GStaticPrivate current_emu_key = G_STATIC_PRIVATE_INIT;
+
+/* ticalcs progress bar callback */
+static void pbar_do_update()
+{
+	TilemCalcEmulator *emu = g_static_private_get(&current_emu_key);
+	CalcUpdate *upd = emu->link_update;
+	gdouble frac;
+
+	if (upd->max1 > 0 && upd->max2 > 0)
+		frac = ((gdouble) upd->cnt1 / upd->max1 + upd->cnt2) / upd->max2;
+	else if (upd->max1 > 0)
+		frac = ((gdouble) upd->cnt1 / upd->max1);
+	else if (upd->max2 > 0)
+		frac = ((gdouble) upd->cnt2 / upd->max2);
+	else
+		frac = -1.0;
+
+	tilem_em_set_progress(emu, frac, upd->text);
+}
+
+/* Get the calc model (compatible for ticalcs) */
+int get_calc_model(TilemCalc *calc)
+{
+	return model_to_calcmodel(calc->hw.model_id);
+}
+
+/* Create a calc handle */
+void begin_link(TilemCalcEmulator *emu, CableHandle **cbl, CalcHandle **ch,
+                const char *title)
+{
+	tilem_em_unlock(emu);
+
+	*cbl = internal_link_handle_new(emu);
+
+	emu->link_update->max1 = 0;
+	emu->link_update->max2 = 0;
+	emu->link_update->text[0] = 0;
+
+	emu->link_update->pbar = &pbar_do_update;
+	emu->link_update->label = &pbar_do_update;
+
+	g_static_private_set(&current_emu_key, emu, NULL);
+
+	tilem_em_set_progress_title(emu, title);
+
+	*ch = ticalcs_handle_new(get_calc_model(emu->calc));
+	if (!*ch) {
+		g_critical("unsupported calc");
+		return;
+	}
+
+	ticalcs_update_set(*ch, emu->link_update);
+	ticalcs_cable_attach(*ch, *cbl);
+}
+
+/* Destroy calc handle */
+void end_link(TilemCalcEmulator *emu, CableHandle *cbl, CalcHandle *ch)
+{
+	tilem_em_set_progress_title(emu, NULL);
+
+	ticalcs_cable_detach(ch);
+	ticalcs_handle_del(ch);
+	ticables_handle_del(cbl);
+
+	tilem_em_lock(emu);
+}
+
+/**************** Error messages ****************/
+
+static char * get_tilibs_error(int errcode)
+{
+	char *p = NULL;
+
+	if (!ticalcs_error_get(errcode, &p)
+	    || !ticables_error_get(errcode, &p)
+	    || !tifiles_error_get(errcode, &p))
+		return p;
+	else
+		return g_strdup_printf("Unknown error (%d)", errcode);
+}
+
+static char * get_ti81_error(int errcode)
+{
+	switch (errcode) {
+	case TI81_ERR_FILE_IO:
+		return g_strdup("File I/O error");
+
+	case TI81_ERR_INVALID_FILE:
+		return g_strdup("Not a valid PRG file");
+
+	case TI81_ERR_MEMORY:
+		return g_strdup("The calculator does not have enough free memory"
+		                " to load the program.");
+
+	case TI81_ERR_SLOTS_FULL:
+		return g_strdup("All calculator program slots are in use. "
+		                " You must delete an existing program before"
+		                " loading a new program.");
+
+	case TI81_ERR_BUSY:
+		return g_strdup("The calculator is currently busy.  Please"
+		                " exit to the home screen before loading"
+		                " programs.");
+
+	default:
+		return g_strdup_printf("Unknown error code (%d)", errcode);
+	}
+}
+
+void show_error(TilemCalcEmulator *emu,
+                       const char *title, const char *message)
+{
+	if (emu->ewin)
+		messagebox11(emu->ewin->window, GTK_MESSAGE_ERROR,
+		             "%s", title, "%s", message);
+	else
+		g_printerr("\n=== %s ===\n%s\n\n", title, message);
+}
+
+/**************** Sending files ****************/
+
+/* Send a file to TI-81 */
+static gboolean send_file_ti81(TilemCalcEmulator *emu, struct TilemSendFileInfo *sf)
+{
+	TI81Program *prgm = NULL;
+	FILE *f;
+	int errnum;
+	sf->error_message = NULL;
+
+	f = g_fopen(sf->filename, "rb");
+	if (!f) {
+		sf->error_message = g_strdup_printf
+			("Failed to open %s for reading: %s",
+			 sf->display_name, g_strerror(errno));
+		return FALSE;
+	}
+
+	if (ti81_read_prg_file(f, &prgm)) {
+		sf->error_message = g_strdup_printf
+			("The file %s is not a valid TI-81 program file.",
+			 sf->display_name);
+		fclose(f);
+		return FALSE;
+	}
+
+	fclose(f);
+
+	tilem_em_wake_up(emu, TRUE);
+
+	prgm->info.slot = sf->slot;
+	errnum = ti81_load_program(emu->calc, prgm);
+	ti81_program_free(prgm);
+
+	if (errnum && !emu->task_abort)
+		sf->error_message = get_ti81_error(errnum);
+	return (errnum == 0);
+}
+
+/* Get application name */
+static gboolean get_app_name(const FlashContent *flashc, char *name)
+{
+	int i;
+	const unsigned char *data;
+	unsigned int type, length;
+
+	if (flashc->num_pages < 1 || flashc->pages[0]->size < 6
+	    || flashc->pages[0]->data[0] != 0x80
+	    || flashc->pages[0]->data[1] != 0x0f)
+		return FALSE;
+
+	i = 6;
+	data = flashc->pages[0]->data;
+	while (i < flashc->pages[0]->size && i < 128) {
+		type = (data[i] << 8 | (data[i + 1] & 0xf0));
+		length = data[i + 1] & 0x0f;
+		i += 2;
+
+		if (length == 0x0d) {
+			length = data[i];
+			i++;
+		}
+		else if (length == 0x0e) {
+			length = (data[i] << 8 | data[i + 1]);
+			i += 2;
+		}
+		else if (length == 0x0f) {
+			return FALSE;
+		}
+
+		if (type == 0x8070)
+			return FALSE;
+
+		if (type == 0x8040) {
+			memcpy(name, data + i, length > 8 ? 8 : length);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/* Try to delete an existing Flash app before we send a replacement */
+static void try_delete_app(CalcHandle *ch, const FlashContent *flashc)
+{
+	VarRequest vr;
+
+	/* TI-73 does not support remote deletion */
+	if (ch->model == CALC_TI73)
+		return;
+
+	memset(&vr, 0, sizeof(VarRequest));
+	if (!get_app_name(flashc, vr.name))
+		return;
+
+	/* Why does this use type 0x14 and not 0x24?  I don't know. */
+	vr.type = 0x14;
+	ticalcs_calc_del_var(ch, &vr);
+	/* if an error occurs, ignore it */
+}
+
+/* Send a file using ticalcs2 */
+static gboolean send_file_linkport(TilemCalcEmulator *emu, struct TilemSendFileInfo *sf)
+{
+	CalcModel model;
+	FileClass cls;
+	CableHandle *cbl;
+	CalcHandle *ch;
+	FileContent *filec;
+	BackupContent *backupc;
+	FlashContent *flashc;
+	TigContent *tigc;
+	CalcMode mode;
+	int e;
+	char *desc;
+
+	model = get_calc_model(emu->calc);
+	cls = tifiles_file_get_class(sf->filename);
+
+	desc = g_strdup_printf("Sending %s", sf->display_name);
+
+	/* Read input file */
+
+	switch (cls) {
+	case TIFILE_SINGLE:
+	case TIFILE_GROUP:
+	case TIFILE_REGULAR:
+		filec = tifiles_content_create_regular(model);
+		e = tifiles_file_read_regular(sf->filename, filec);
+		if (!e) {
+			begin_link(emu, &cbl, &ch, desc);
+			if (sf->first)
+				prepare_for_link_send(emu);
+			mode = (sf->last ? MODE_SEND_LAST_VAR : MODE_NORMAL);
+			e = ticalcs_calc_send_var(ch, mode, filec);
+			end_link(emu, cbl, ch);
+		}
+		tifiles_content_delete_regular(filec);
+		break;
+
+	case TIFILE_BACKUP:
+		backupc = tifiles_content_create_backup(model);
+		e = tifiles_file_read_backup(sf->filename, backupc);
+		if (!e) {
+			begin_link(emu, &cbl, &ch, desc);
+			prepare_for_link_send(emu);
+			e = ticalcs_calc_send_backup(ch, backupc);
+			end_link(emu, cbl, ch);
+		}
+		tifiles_content_delete_backup(backupc);
+		break;
+
+	case TIFILE_FLASH:
+	case TIFILE_OS:
+	case TIFILE_APP:
+		flashc = tifiles_content_create_flash(model);
+		e = tifiles_file_read_flash(sf->filename, flashc);
+		if (!e) {
+			begin_link(emu, &cbl, &ch, desc);
+			ticables_options_set_timeout(cbl, 30 * 10);
+			prepare_for_link_send(emu);
+			if (tifiles_file_is_os(sf->filename))
+				e = ticalcs_calc_send_os(ch, flashc);
+			else if (tifiles_file_is_app(sf->filename)) {
+				try_delete_app(ch, flashc);
+				e = ticalcs_calc_send_app(ch, flashc);
+			}
+			else
+				e = ticalcs_calc_send_cert(ch, flashc);
+			end_link(emu, cbl, ch);
+		}
+		tifiles_content_delete_flash(flashc);
+		break;
+
+	case TIFILE_TIGROUP:
+		tigc = tifiles_content_create_tigroup(model, 0);
+		e = tifiles_file_read_tigroup(sf->filename, tigc);
+		if (!e) {
+			begin_link(emu, &cbl, &ch, desc);
+			prepare_for_link_send(emu);
+			e = ticalcs_calc_send_tigroup(ch, tigc, TIG_ALL);
+			end_link(emu, cbl, ch);
+		}
+		tifiles_content_delete_tigroup(tigc);
+		break;
+
+	default:
+		g_free(desc);
+		sf->error_message = g_strdup_printf
+			("The file %s is not a valid program or"
+			 " variable file.",
+			 sf->display_name);
+		return FALSE;
+	}
+
+	g_free(desc);
+	if (e && !emu->task_abort)
+		sf->error_message = get_tilibs_error(e);
+	return (e == 0);
+}
+
+gboolean send_file_main(TilemCalcEmulator *emu, gpointer data)
+{
+	struct TilemSendFileInfo *sf = data;
+	/*emu->ilp.finished_cond = g_cond_new(); */
+
+	if (emu->calc->hw.model_id == TILEM_CALC_TI81)
+		return send_file_ti81(emu, sf);
+	else
+		return send_file_linkport(emu, sf);
+}
+
+static void send_file_finished(TilemCalcEmulator *emu, gpointer data,
+                               gboolean cancelled)
+{
+	struct TilemSendFileInfo *sf = data;
+
+	if (sf->error_message && !cancelled)
+		show_error(emu, "Unable to send file", sf->error_message);
+
+	g_free(sf->filename);
+	g_free(sf->display_name);
+	g_free(sf->error_message);
+	g_slice_free(struct TilemSendFileInfo, sf);
+	
+	/*g_cond_broadcast(emu->ilp.finished_cond);*/
+	
+	
+}
+
+void tilem_link_send_file(TilemCalcEmulator *emu, const char *filename,
+                          int slot, gboolean first, gboolean last)
+{
+	struct TilemSendFileInfo *sf;
+
+	sf = g_slice_new0(struct TilemSendFileInfo);
+	sf->filename = g_strdup(filename);
+	sf->display_name = g_filename_display_basename(filename);
+	sf->slot = slot;
+	sf->first = first;
+	sf->last = last;
+
+	tilem_calc_emulator_begin(emu, &send_file_main,
+	                          &send_file_finished, sf);
+}
+
+/**************** Get directory listing ****************/
+
+/* Make a copy of a TilemVarEntry */
+TilemVarEntry *tilem_var_entry_copy(const TilemVarEntry *tve)
+{
+	TilemVarEntry *nve;
+
+	g_return_val_if_fail(tve != NULL, NULL);
+
+	nve = g_slice_new(TilemVarEntry);
+	*nve = *tve;
+
+	if (tve->ve) {
+		nve->ve = g_slice_new(VarEntry);
+		*nve->ve = *tve->ve;
+		nve->ve->data = g_memdup(tve->ve->data, tve->ve->size);
+	}
+	if (tve->name_str)
+		nve->name_str = g_strdup(tve->name_str);
+	if (tve->type_str)
+		nve->type_str = g_strdup(tve->type_str);
+	if (tve->slot_str)
+		nve->slot_str = g_strdup(tve->slot_str);
+	if (tve->file_ext)
+		nve->file_ext = g_strdup(tve->file_ext);
+	if (tve->filetype_desc)
+		nve->filetype_desc = g_strdup(tve->filetype_desc);
+
+	return nve;
+}
+
+/* Free a TilemVarEntry */
+void tilem_var_entry_free(TilemVarEntry *tve)
+{
+	g_return_if_fail(tve != NULL);
+	if (tve->ve) {
+		g_free(tve->ve->data);
+		g_slice_free(VarEntry, tve->ve);
+	}
+	g_free(tve->name_str);
+	g_free(tve->type_str);
+	g_free(tve->slot_str);
+	g_free(tve->file_ext);
+	g_free(tve->filetype_desc);
+	g_slice_free(TilemVarEntry, tve);
+}
+
+struct dirlistinfo {
+	GSList *list;
+	char *error_message;
+	gboolean aborted;
+	gboolean no_gui;
+};
+
+/* Convert tifiles VarEntry into a TilemVarEntry */
+static TilemVarEntry *convert_ve(TilemCalcEmulator *emu, VarEntry *ve,
+                                 gboolean is_flash)
+{
+	TilemVarEntry *tve = g_slice_new0(TilemVarEntry);
+	CalcModel tfmodel = get_calc_model(emu->calc);
+	const char *model_str;
+	const char *type_str;
+	const char *fext;
+
+	tve->model = emu->calc->hw.model_id;
+
+	tve->ve = g_slice_new(VarEntry);
+	*tve->ve = *ve;
+	if (ve->data)
+		tve->ve->data = g_memdup(ve->data, ve->size);
+
+	tve->size = ve->size;
+	tve->archived = (ve->attr & ATTRB_ARCHIVED ? TRUE : FALSE);
+	tve->can_group = TRUE;
+
+	tve->name_str = ticonv_varname_to_utf8(tfmodel, ve->name, ve->type);
+	g_strchomp(tve->name_str);
+	tve->type_str = g_strdup(tifiles_vartype2string(tfmodel, ve->type));
+	fext = tifiles_vartype2fext(tfmodel, ve->type);
+	tve->file_ext = g_ascii_strdown(fext, -1);
+
+	/* FIXME: the filetype_desc string is used as a description in
+	   the file chooser.  It should be written in the same style
+	   as other such strings (e.g., "TI-83 Plus programs" rather
+	   than "TI83+ Program".)  But this is better than nothing. */
+	model_str = tifiles_model_to_string(tfmodel);
+	type_str = tifiles_vartype2type(tfmodel, ve->type);
+	tve->filetype_desc = g_strdup_printf("%s %s", model_str, type_str);
+
+	tve->can_group = !is_flash;
+
+	return tve;
+}
+
+/* Convert a complete directory listing */
+static void convert_dir_list(TilemCalcEmulator *emu, GNode *root,
+                             gboolean is_flash, GSList **list)
+{
+	GNode *dir, *var;
+	VarEntry *ve;
+	TilemVarEntry *tve;
+
+	if (!root)
+		return;
+
+	for (dir = root->children; dir; dir = dir->next) {
+		for (var = dir->children; var; var = var->next) {
+			ve = var->data;
+			tve = convert_ve(emu, ve, is_flash);
+			*list = g_slist_prepend(*list, tve);
+		}
+	}
+}
+
+/* Request directory listing using ticalcs */
+static gboolean get_dirlist_silent(TilemCalcEmulator *emu,
+                                   struct dirlistinfo *dl)
+{
+	CableHandle *cbl;
+	CalcHandle *ch;
+	GNode *vars = NULL, *apps = NULL;
+	GSList *list = NULL;
+	int e = 0;
+
+	begin_link(emu, &cbl, &ch, "Reading variable list");
+	prepare_for_link_receive(emu);
+
+	if (ticalcs_calc_features(ch) & OPS_DIRLIST) {
+		e = ticalcs_calc_get_dirlist(ch, &vars, &apps);
+		if (!e) {
+			convert_dir_list(emu, vars, FALSE, &list);
+			convert_dir_list(emu, apps, TRUE, &list);
+		}
+		ticalcs_dirlist_destroy(&vars);
+		ticalcs_dirlist_destroy(&apps);
+	}
+
+	end_link(emu, cbl, ch);
+
+	dl->list = g_slist_reverse(list);
+
+	dl->aborted = emu->task_abort;
+
+	if (e && !emu->task_abort)
+		dl->error_message = get_tilibs_error(e);
+	return (e == 0);
+}
+
+/* Transfer variables non-silently using ticalcs */
+static gboolean get_dirlist_nonsilent(TilemCalcEmulator *emu,
+                                      struct dirlistinfo *dl)
+{
+	CableHandle *cbl;
+	CalcHandle *ch;
+	FileContent *fc;
+	VarEntry *head_entry;
+	TilemVarEntry *tve;
+	GSList *list = NULL;
+	int e, i;
+
+	begin_link(emu, &cbl, &ch, "Receiving variables");
+	prepare_for_link_receive(emu);
+
+	fc = tifiles_content_create_regular(ch->model);
+	e = ticalcs_calc_recv_var_ns(ch, MODE_BACKUP, fc, &head_entry);
+	if (!e) {
+		for (i = 0; i < fc->num_entries; i++) {
+			tve = convert_ve(emu, fc->entries[i], FALSE);
+			list = g_slist_prepend(list, tve);
+		}
+	}
+	if (head_entry)
+		tifiles_ve_delete(head_entry);
+	tifiles_content_delete_regular(fc);
+
+	end_link(emu, cbl, ch);
+
+	dl->list = g_slist_reverse(list);
+
+	dl->aborted = emu->task_abort;
+
+	if (e && !emu->task_abort)
+		dl->error_message = get_tilibs_error(e);
+	return (e == 0);
+}
+
+/* Get TI-81 directory listing */
+static gboolean get_dirlist_ti81(TilemCalcEmulator *emu,
+                                 struct dirlistinfo *dl)
+{
+	int i, slot;
+	TI81ProgInfo info;
+	GSList *list = NULL;
+	TilemVarEntry *tve;
+	int e;
+
+	tilem_em_wake_up(emu, TRUE);
+
+	for (i = 0; i <= TI81_SLOT_MAX; i++) {
+		/* put Prgm0 after Prgm9, the way it appears in the menu */
+		if (i < 9)
+			slot = i + 1;
+		else if (i == 9)
+			slot = 0;
+		else
+			slot = i;
+
+		if ((e = ti81_get_program_info(emu->calc, slot, &info)))
+			break;
+
+		if (info.size == 0)
+			continue;
+
+		tve = g_slice_new0(TilemVarEntry);
+		tve->model = TILEM_CALC_TI81;
+		tve->slot = info.slot;
+		tve->name_str = ti81_program_name_to_string(info.name);
+		tve->slot_str = ti81_program_slot_to_string(info.slot);
+		tve->file_ext = g_strdup("prg");
+		tve->filetype_desc = g_strdup("TI-81 programs");
+		tve->size = info.size;
+		tve->archived = FALSE;
+		tve->can_group = FALSE;
+
+		list = g_slist_prepend(list, tve);
+	}
+
+	dl->list = g_slist_reverse(list);
+
+	if (e && !emu->task_abort)
+		dl->error_message = get_ti81_error(e);
+	return (e == 0);
+}
+
+static gboolean get_dirlist_main(TilemCalcEmulator *emu, gpointer data)
+{
+	switch (emu->calc->hw.model_id) {
+	case TILEM_CALC_TI81:
+		return get_dirlist_ti81(emu, data);
+
+	case TILEM_CALC_TI82:
+	case TILEM_CALC_TI85:
+		return get_dirlist_nonsilent(emu, data);
+
+	default:
+		return get_dirlist_silent(emu, data);
+	}
+}
+
+static void get_dirlist_finished(TilemCalcEmulator *emu, gpointer data,
+                                 gboolean cancelled)
+{
+	GSList *l;
+	struct dirlistinfo *dl = data;
+
+	if (dl->error_message && !cancelled)
+		show_error(emu, "Unable to receive variable list",
+		           dl->error_message);
+	else if (!cancelled && !dl->aborted && emu->ewin && !dl->no_gui) {
+		if (!emu->rcvdlg)
+			emu->rcvdlg = tilem_receive_dialog_new(emu);
+		tilem_receive_dialog_update(emu->rcvdlg, dl->list);
+		dl->list = NULL;
+	}
+
+	if (!dl->no_gui && emu->rcvdlg)
+		emu->rcvdlg->refresh_pending = FALSE;
+
+	for (l = dl->list; l; l = l->next)
+		tilem_var_entry_free(l->data);
+
+	g_slist_free(dl->list);
+	g_slice_free(struct dirlistinfo, dl);
+}
+
+void tilem_link_get_dirlist(TilemCalcEmulator *emu)
+{
+	struct dirlistinfo *dl = g_slice_new0(struct dirlistinfo);
+	tilem_calc_emulator_begin(emu, &get_dirlist_main,
+	                          &get_dirlist_finished, dl);
+}
+
+/**************** Receiving files ****************/
+
+static gboolean write_output(FileContent **vars, FlashContent **apps,
+                             const char *filename, gboolean output_tig,
+                             char **error_message)
+{
+	FileContent *group = NULL;
+	TigContent *tig = NULL;
+	int e, nvars, napps;
+
+	for (nvars = 0; vars && vars[nvars]; nvars++)
+		;
+	for (napps = 0; apps && apps[napps]; napps++)
+		;
+
+	g_return_val_if_fail(nvars > 0 || napps > 0, FALSE);
+
+	if (output_tig) {
+		e = tifiles_tigroup_contents(vars, apps, &tig);
+		if (!e)
+			e = tifiles_file_write_tigroup(filename, tig);
+	}
+	else if (nvars > 1 && napps == 0) {
+		e = tifiles_group_contents(vars, &group);
+		if (!e)
+			e = tifiles_file_write_regular(filename, group, NULL);
+	}
+	else if (nvars == 0 && napps == 1) {
+		e = tifiles_file_write_flash(filename, apps[0]);
+	}
+	else if (nvars == 1 && napps == 0) {
+		e = tifiles_file_write_regular(filename, vars[0], NULL);
+	}
+	else {
+		*error_message = g_strdup
+			("Applications cannot be saved in an XXg group"
+			 " file.  Try using TIG format or saving apps"
+			 " individually.");
+		return FALSE;
+	}
+
+	if (e)
+		*error_message = get_tilibs_error(e);
+
+	if (tig)
+		tifiles_content_delete_tigroup(tig);
+	if (group)
+		tifiles_content_delete_regular(group);
+
+	return (e == 0);
+}
+
+static gboolean receive_files_silent(TilemCalcEmulator* emu,
+                                     struct TilemReceiveFileInfo *rf)
+{
+	CableHandle *cbl;
+	CalcHandle *ch;
+	FileContent **vars, *filec;
+	FlashContent **apps, *flashc;
+	GSList *l;
+	TilemVarEntry *tve;
+	int e, i, nvars, napps;
+
+	g_return_val_if_fail(rf->entries != NULL, FALSE);
+
+	i = g_slist_length(rf->entries);
+
+	vars = g_new0(FileContent *, i + 1);
+	apps = g_new0(FlashContent *, i + 1);
+	nvars = napps = 0;
+
+	begin_link(emu, &cbl, &ch, "Receiving variables");
+
+	for (l = rf->entries; l; l = l->next) {
+		tve = l->data;
+
+		if (tve->ve->type == tifiles_flash_type(ch->model)) {
+			flashc = tifiles_content_create_flash(ch->model);
+			e = ticalcs_calc_recv_app(ch, flashc, tve->ve);
+			apps[napps++] = flashc;
+		}
+		else {
+			filec = tifiles_content_create_regular(ch->model);
+			e = ticalcs_calc_recv_var(ch, MODE_NORMAL,
+			                          filec, tve->ve);
+			vars[nvars++] = filec;
+		}
+		if (e)
+			break;
+	}
+
+	if (e && !emu->task_abort)
+		rf->error_message = get_tilibs_error(e);
+
+	end_link(emu, cbl, ch);
+
+	if (!e)
+		e = !write_output(vars, apps, rf->destination,
+		                  rf->output_tig, &rf->error_message);
+
+	for (i = 0; i < nvars; i++)
+		tifiles_content_delete_regular(vars[i]);
+	for (i = 0; i < napps; i++)
+		tifiles_content_delete_flash(apps[i]);
+
+	return (e == 0);
+}
+
+static gboolean receive_files_ti81(TilemCalcEmulator* emu,
+                                   struct TilemReceiveFileInfo *rf)
+{
+	TilemVarEntry *tve;
+	TI81Program *prgm = NULL;
+	int e;
+	FILE *f;
+	char *dname;
+
+	g_return_val_if_fail(rf->entries != NULL, FALSE);
+
+	if (rf->entries->next) {
+		rf->error_message = g_strdup
+			("TI-81 programs cannot be saved in a group file."
+			 " Try saving programs individually.");
+		return FALSE;
+	}
+
+	tve = rf->entries->data;
+	e = ti81_get_program(emu->calc, tve->slot, &prgm);
+	if (e) {
+		rf->error_message = get_ti81_error(e);
+		return FALSE;
+	}
+
+	f = g_fopen(rf->destination, "wb");
+	if (!f) {
+		e = errno;
+		dname = g_filename_display_basename(rf->destination);
+		rf->error_message = g_strdup_printf
+			("Failed to open %s for writing: %s",
+			 dname, g_strerror(e));
+		g_free(dname);
+		ti81_program_free(prgm);
+		return FALSE;
+	}
+
+	e = ti81_write_prg_file(f, prgm);
+	if (fclose(f) || e) {
+		e = errno;
+		dname = g_filename_display_basename(rf->destination);
+		rf->error_message = g_strdup_printf
+			("Error writing %s: %s",
+			 dname, g_strerror(e));
+		g_free(dname);
+		ti81_program_free(prgm);
+		return FALSE;
+	}
+
+	ti81_program_free(prgm);
+	return TRUE;
+}
+
+static gboolean receive_files_nonsilent(TilemCalcEmulator *emu,
+                                        struct TilemReceiveFileInfo *rf)
+{
+	const TilemVarEntry *tve;
+	FileContent **vars, *fc;
+	int i, nvars;
+	GSList *l;
+	gboolean status;
+
+	nvars = g_slist_length(rf->entries);
+
+	vars = g_new0(FileContent *, nvars + 1);
+	i = 0;
+	for (l = rf->entries; l; l = l->next) {
+		tve = l->data;
+		g_return_val_if_fail(tve->ve != NULL, FALSE);
+		g_return_val_if_fail(tve->ve->data != NULL, FALSE);
+
+		/* avoid copying variable data */
+		fc = tifiles_content_create_regular(get_calc_model(emu->calc));
+		fc->entries = g_new(VarEntry *, 1);
+		fc->num_entries = 1;
+		fc->entries[0] = tve->ve;
+		vars[i++] = fc;
+	}
+
+	status = write_output(vars, NULL, rf->destination, rf->output_tig,
+	                      &rf->error_message);
+
+	for (i = 0; i < nvars; i++) {
+		if (vars[i]) {
+			vars[i]->num_entries = 0;
+			g_free(vars[i]->entries);
+			vars[i]->entries = NULL;
+			tifiles_content_delete_regular(vars[i]);
+		}
+	}
+	g_free(vars);
+
+	return status;
+}
+
+static gboolean receive_files_main(TilemCalcEmulator *emu, gpointer data)
+{
+	struct TilemReceiveFileInfo *rf = data;
+	TilemVarEntry *tve;
+
+	g_return_val_if_fail(rf->entries != NULL, FALSE);
+
+	tve = rf->entries->data;
+
+	if (emu->calc->hw.model_id == TILEM_CALC_TI81)
+		return receive_files_ti81(emu, rf);
+	else if (tve->ve && tve->ve->data)
+		return receive_files_nonsilent(emu, rf);
+	else
+		return receive_files_silent(emu, rf);
+}
+
+static void receive_files_finished(TilemCalcEmulator *emu, gpointer data,
+                                   gboolean cancelled)
+{
+	struct TilemReceiveFileInfo *rf = data;
+	GSList *l;
+
+	if (rf->error_message && !cancelled)
+		show_error(emu, "Unable to save file", rf->error_message);
+
+	g_free(rf->destination);
+	g_free(rf->error_message);
+	for (l = rf->entries; l; l = l->next)
+		tilem_var_entry_free(l->data);
+	g_slist_free(rf->entries);
+	g_slice_free(struct TilemReceiveFileInfo, rf);
+}
+
+void tilem_link_receive_group(TilemCalcEmulator *emu,
+                              GSList *entries,
+                              const char *destination)
+{
+	struct TilemReceiveFileInfo *rf;
+	GSList *l;
+	TilemVarEntry *tve;
+	const char *p;
+	gboolean output_tig = FALSE;
+
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(emu->calc != NULL);
+	g_return_if_fail(entries != NULL);
+	g_return_if_fail(destination != NULL);
+
+	for (l = entries; l; l = l->next) {
+		tve = l->data;
+		g_return_if_fail(emu->calc->hw.model_id == tve->model);
+	}
+
+	p = strrchr(destination, '.');
+	if (p && (!g_ascii_strcasecmp(p, ".tig")
+	          || !g_ascii_strcasecmp(p, ".zip")))
+		output_tig = TRUE;
+
+	rf = g_slice_new0(struct TilemReceiveFileInfo);
+	rf->destination = g_strdup(destination);
+	rf->output_tig = output_tig;
+
+	tve = entries->data;
+	if (tve->ve && tve->ve->data) {
+		/* non-silent: we already have the data; save the file
+		   right now */
+		rf->entries = entries;
+		receive_files_nonsilent(emu, rf);
+		rf->entries = NULL;
+		receive_files_finished(emu, rf, FALSE);
+	}
+	else {
+		/* silent: retrieve and save data in background */
+		for (l = entries; l; l = l->next) {
+			tve = tilem_var_entry_copy(l->data);
+			rf->entries = g_slist_prepend(rf->entries, tve);
+		}
+		rf->entries = g_slist_reverse(rf->entries);
+
+		tilem_calc_emulator_begin(emu, &receive_files_main,
+		                          &receive_files_finished, rf);
+	}
+}
+
+void tilem_link_receive_file(TilemCalcEmulator *emu,
+                             const TilemVarEntry *varentry,
+                             const char* destination)
+{
+	GSList *l;
+	l = g_slist_prepend(NULL, (gpointer) varentry);
+	tilem_link_receive_group(emu, l, destination);
+	g_slist_free(l);
+}
+
+/**************** Receive matching files ****************/
+
+struct recmatchinfo {
+	char *pattern;
+	char *destdir;
+	struct dirlistinfo *dl;
+	struct TilemReceiveFileInfo *rf;
+};
+
+static gboolean receive_matching_main(TilemCalcEmulator *emu, gpointer data)
+{
+	struct recmatchinfo *rm = data;
+	GSList *l, *selected = NULL;
+	TilemVarEntry *tve;
+	char *defname, *defname_r, *defname_l;
+	GPatternSpec *pat;
+	gboolean status = TRUE;
+
+	if (!get_dirlist_main(emu, rm->dl))
+		return FALSE;
+
+	/* Find variables that match the pattern */
+
+	pat = g_pattern_spec_new(rm->pattern);
+
+	for (l = rm->dl->list; l; l = l->next) {
+		tve = l->data;
+
+		defname = get_default_filename(tve);
+		defname_r = g_utf8_normalize(defname, -1, G_NORMALIZE_NFKD);
+
+		if (g_pattern_match(pat, strlen(defname_r), defname_r, NULL))
+			selected = g_slist_prepend(selected, tve);
+
+		g_free(defname);
+		g_free(defname_r);
+	}
+	
+	g_pattern_spec_free(pat);
+
+	if (!selected) {
+		rm->rf->error_message = g_strdup_printf
+			("Variable %s not found", rm->pattern);
+		return FALSE;
+	}
+
+	/* Receive variables */
+
+	selected = g_slist_reverse(selected);
+
+	for (l = selected; l; l = l->next) {
+		tve = l->data;
+
+		g_free(rm->rf->destination);
+
+		defname = get_default_filename(tve);
+		defname_l = utf8_to_filename(defname);
+		rm->rf->destination = g_build_filename(rm->destdir,
+		                                       defname_l, NULL);
+		g_free(defname);
+		g_free(defname_l);
+
+		rm->rf->entries = g_slist_prepend(NULL, tve);
+		status = receive_files_main(emu, rm->rf);
+		g_slist_free(rm->rf->entries);
+		rm->rf->entries = NULL;
+
+		if (!status)
+			break;
+	}
+
+	g_slist_free(selected);
+
+	return status;
+}
+
+static void receive_matching_finished(TilemCalcEmulator *emu, gpointer data,
+                                      gboolean cancelled)
+{
+	struct recmatchinfo *rm = data;
+
+	get_dirlist_finished(emu, rm->dl, cancelled);
+	receive_files_finished(emu, rm->rf, cancelled);
+	g_free(rm->pattern);
+	g_free(rm->destdir);
+	g_slice_free(struct recmatchinfo, rm);
+}
+
+/* Receive variables with names matching a pattern.  PATTERN is a
+   glob-like pattern in UTF-8.  Files will be written out to
+   DESTDIR. */
+void tilem_link_receive_matching(TilemCalcEmulator *emu,
+                                 const char *pattern,
+                                 const char *destdir)
+{
+	struct recmatchinfo *rm;
+
+	g_return_if_fail(emu != NULL);
+	g_return_if_fail(pattern != NULL);
+	g_return_if_fail(destdir != NULL);
+
+	rm = g_slice_new0(struct recmatchinfo);
+	rm->pattern = g_utf8_normalize(pattern, -1, G_NORMALIZE_NFKD);
+	rm->destdir = g_strdup(destdir);
+
+	rm->dl = g_slice_new0(struct dirlistinfo);
+	rm->dl->no_gui = TRUE;
+	rm->rf = g_slice_new0(struct TilemReceiveFileInfo);
+
+	tilem_calc_emulator_begin(emu, &receive_matching_main,
+	                          &receive_matching_finished, rm);
+}
diff --git a/tool/tilem-src/gui/macro.c b/tool/tilem-src/gui/macro.c
new file mode 100644
index 0000000..ae8db8b
--- /dev/null
+++ b/tool/tilem-src/gui/macro.c
@@ -0,0 +1,274 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "filedlg.h"
+#include "emucore.h"
+
+
+/* Allocate a new TilemMacro structure which is empty */
+static TilemMacro* tilem_macro_new() {
+	TilemMacro *macro = g_new(TilemMacro, 1);
+	macro->n = 0;
+	macro->actions = NULL;
+	return macro;
+}
+
+/* New or renew the table of actions (each TilemMacroAtom is an action) */
+static TilemMacroAtom** tilem_macro_actions_new(TilemMacro *macro, int n) {
+	TilemMacroAtom **atom; 
+
+	if(n == 0) {
+		atom =  g_new(TilemMacroAtom*, n);
+	} else {
+		atom =  g_renew(TilemMacroAtom*, macro->actions, n);
+	}
+	return atom;
+}
+
+/* Try to destroy the TilemMacro if really allocated */
+static void tilem_macro_finalize(TilemMacro* macro) {
+	if(macro) {
+		if(macro->actions)
+			g_free(macro->actions);
+		g_free(macro);
+	}
+}
+
+/* Firstly free the memory then create a new TilemMacro */
+void tilem_macro_start(TilemCalcEmulator *emu) {
+	emu->isMacroRecording = TRUE;
+
+	/* Firstly destroy the macro if exists */
+	tilem_macro_finalize(emu->macro);
+
+	/* Then allocate a new one */	
+	emu->macro = tilem_macro_new(emu);
+}
+
+/* Add an action to the macro. The action could be :
+ * A keypress (type == 0) 
+ * A file load (type == 1)
+ * Or something else if I implement it :)
+ */
+void tilem_macro_add_action(TilemMacro* macro, int type, char * value) {
+	
+	int n = macro->n;
+
+	/* We want to allocate for 1 object, but index is 0 */
+	macro->actions = tilem_macro_actions_new(macro, n + 1);
+
+	/* Then we need to save the action */	
+	macro->actions[n] =  g_new(char, strlen(value)); /* FIXME : gcc says : "assignment from incompatible pointer type" ??? */
+	macro->actions[n]->value = g_strdup(value);
+	macro->actions[n]->type = type;
+	macro->n++;
+}
+
+/* Stop the macro */
+void tilem_macro_stop(TilemCalcEmulator *emu)
+{
+	if(emu->isMacroRecording)
+		emu->isMacroRecording = FALSE;
+}
+
+/* Print the macro actions content (debug) */
+void tilem_macro_print(TilemMacro *macro) {
+	int i = 0;
+
+	printf("macro->n : %d\n", macro->n);
+	for(i = 0; i < macro->n; i++ ){
+		printf("type : %d    value : %s\n", macro->actions[i]->type, macro->actions[i]->value);
+	}
+}
+
+/* Write a file using TilemMacro structure */
+void tilem_macro_write_file(TilemCalcEmulator *emu) {
+	char *dir, *filename;
+	tilem_config_get("macro",
+                 "directory/f", &dir,
+                 NULL);
+
+	filename = prompt_save_file("Save macro", 
+				    GTK_WINDOW(emu->ewin->window),
+				    NULL, 
+				    dir,
+				    "Macro files", "*.txt",
+                                    "All files", "*",
+				    NULL);
+	if(filename) {
+		FILE * fp = g_fopen(filename, "w");
+		if(fp) {
+			int i = 0;
+			for(i = 0; i< emu->macro->n; i++ ){
+				printf("type : %d    value : %s\n", emu->macro->actions[i]->type, emu->macro->actions[i]->value);
+				/* Test if it's a key press or a file loading action */
+				if(emu->macro->actions[i]->type == 1) {
+					char * lengthchar = g_new0(char, 4);
+					int length = strlen(emu->macro->actions[i]->value);
+					fwrite("file=", 1, 5, fp);
+					sprintf(lengthchar, "%04d", strlen(emu->macro->actions[i]->value));
+					fwrite(lengthchar, 1, sizeof(int), fp);
+					fwrite("-", 1, 1, fp);
+					fwrite(emu->macro->actions[i]->value, 1, length, fp);
+					g_free(lengthchar);
+				} else {
+					fwrite(emu->macro->actions[i]->value, 1, 4, fp);
+					fwrite(",", 1, 1, fp);
+				}
+			}
+			tilem_config_set("macro", "directory/f", g_path_get_dirname(filename), NULL);
+			fclose(fp);
+		}
+		g_free(filename);
+		g_free(dir);
+	}
+}
+
+#define MACRO_KEYPRESS 0
+#define MACRO_FILE 1
+
+/* Play the macro (macro should be created or loaded before else it does nothing) */
+static gboolean tilem_macro_play_main(TilemCalcEmulator *emu, G_GNUC_UNUSED gpointer data) {
+
+	if(!emu->macro) {	
+		printf("Nothing to play\n");
+		return FALSE;
+	}
+
+	int i;
+	for(i = 0; i < emu->macro->n; i++ ){
+		if(emu->macro->actions[i]->type == MACRO_FILE) {
+			/* Type == 1 is load file */
+			struct TilemSendFileInfo *sf;
+			sf = g_slice_new0(struct TilemSendFileInfo);
+			sf->filename = g_strdup(emu->macro->actions[i]->value);
+			sf->display_name = g_filename_display_basename(emu->macro->actions[i]->value);
+			sf->slot = -1;
+			sf->first = TRUE;
+			sf->last = TRUE;
+			send_file_main(emu, sf);
+	
+		} else {
+			/* type == 0 is keypress */
+			int code = atoi(emu->macro->actions[i]->value);
+			tilem_em_unlock(emu);
+			run_with_key_slowly(emu->calc, code);			
+			tilem_em_lock(emu);
+			
+		}
+	}
+
+
+	return TRUE;
+}
+
+
+static void tilem_macro_play_finished(G_GNUC_UNUSED TilemCalcEmulator *emu, G_GNUC_UNUSED gpointer data,
+                               G_GNUC_UNUSED gboolean cancelled) {
+
+}
+
+/* Play the macro */
+void tilem_macro_play(TilemCalcEmulator* emu) {
+
+	tilem_calc_emulator_begin(emu, &tilem_macro_play_main, &tilem_macro_play_finished, NULL);	
+}
+
+/* Load a macro (when finished, task manager will normally call tilem_macro_play) */
+static gboolean tilem_macro_load_main(TilemCalcEmulator* emu, gpointer data) {
+
+	char* filename = (char*) data;
+	char c = 'a';
+	
+	if(filename) {
+		FILE * fp = g_fopen(filename, "r");
+		/* printf("filename : %s\n", filename); */
+		
+		tilem_macro_start(emu);	
+		while(c != EOF) {	
+			char* codechar = g_new0(char, 4);
+			fread(codechar, 1, 4, fp);
+			if(strcmp(codechar, "file") == 0) {
+				c = fgetc(fp); /* Drop the "="*/
+				char *lengthchar = g_new0(char, 4);
+				fread(lengthchar, 1, 4, fp);
+				c = fgetc(fp); /* Drop the "-"*/
+				int length = atoi(lengthchar);
+				char* filetoload= g_new0(char, length);
+				fread(filetoload, 1, length, fp);
+				tilem_macro_add_action(emu->macro, 1, filetoload);	
+				g_free(lengthchar);
+				g_free(filetoload);
+			} else {
+				int code = atoi(codechar);
+				if(code <= 0)
+					break;
+				/*printf("code : %d, codechar : %s\n",code,  codechar); */
+				tilem_macro_add_action(emu->macro, 0, codechar);	
+				c = fgetc(fp);
+			}
+		}
+		tilem_macro_stop(emu);
+		fclose(fp);
+	}
+	
+	return TRUE;
+}
+
+/* When the macro is totally loaded, then we can play it ! */
+static void tilem_macro_load_finished(G_GNUC_UNUSED TilemCalcEmulator *emu, G_GNUC_UNUSED gpointer data,
+                               G_GNUC_UNUSED gboolean cancelled)
+{
+	tilem_calc_emulator_begin(emu, &tilem_macro_play_main, &tilem_macro_play_finished, NULL);
+}
+
+/* Load a macro from filename. 
+ * If filename == NULL prompt the user
+ */
+void tilem_macro_load(TilemCalcEmulator *emu, char* filename) {
+	
+	/* printf("tilem_macro_load : filename : %s\n", filename); */
+	if(!filename) {
+		char *dir;
+		tilem_config_get("macro", "directory/f", &dir, NULL);
+
+		filename = prompt_open_file("Open macro", 
+					    GTK_WINDOW(emu->ewin->window),
+					    dir,
+					    "Macro files", "*.txt",
+	                                    "All files", "*",
+					    NULL);
+		if(dir)
+			g_free(dir);
+	}
+	tilem_calc_emulator_begin(emu, &tilem_macro_load_main, &tilem_macro_load_finished, filename);	
+}
+	
diff --git a/tool/tilem-src/gui/memmodel.c b/tool/tilem-src/gui/memmodel.c
new file mode 100644
index 0000000..eefe43d
--- /dev/null
+++ b/tool/tilem-src/gui/memmodel.c
@@ -0,0 +1,529 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "memmodel.h"
+#include "charmap.h"
+
+/* GTK+ requires us to supply several property values for every byte,
+   and we might have hundreds of bytes that need to be refreshed each
+   time the window is repainted.  To avoid locking and unlocking the
+   calc for every call to tilem_mem_model_get_value(), we can retrieve
+   information for an entire block of memory and keep it in cache.
+
+   For each address, we cache the current byte value (8 bits), whether
+   or not it is editable (1 bit), and its physical address (up to 22
+   bits for current calculator models), so everything fits in a
+   guint32.  For future models, this scheme might need to be
+   modified. */
+
+#define CACHE_BLOCK_SIZE 256
+#define CACHE_NUM_BLOCKS 16
+
+typedef struct {
+	dword address;
+	guint32 *info;
+} MemModelCacheBlock;
+
+/* Check if a given physical address is editable (i.e., located in RAM
+   or in a non-protected Flash sector) */
+static gboolean address_editable(TilemCalc *calc, dword a)
+{
+	int start, end, i;
+
+	if (a >= calc->hw.romsize)
+		/* address is in RAM */
+		return TRUE;
+
+	if (!(calc->hw.flags & TILEM_CALC_HAS_FLASH))
+		/* calc does not use Flash */
+		return FALSE;
+
+	/* address is in Flash -> check if sector is protected */
+	start = 0;
+	end = calc->hw.nflashsectors;
+	while (start < end) {
+		i = (start + end) / 2;
+		if (a < calc->hw.flashsectors[i].start)
+			end = i;
+		else if (a >= (calc->hw.flashsectors[i].start
+		               + calc->hw.flashsectors[i].size))
+			start = i + 1;
+		else
+			return !(calc->hw.flashsectors[i].protectgroup
+			         & ~calc->flash.overridegroup);
+	}
+
+	g_return_val_if_reached(FALSE);
+}
+
+/* Copy calc memory contents into cache. */
+static void fill_cache_block(TilemMemModel *mm, MemModelCacheBlock *cb)
+{
+	TilemCalc *calc;
+	dword i, addr, phys;
+	byte value, editable;
+
+	g_return_if_fail(mm->emu != NULL);
+
+	tilem_calc_emulator_lock(mm->emu);
+	calc = mm->emu->calc;
+	if (!calc) {
+		tilem_calc_emulator_unlock(mm->emu);
+		return;
+	}
+
+	for (i = 0; i < CACHE_BLOCK_SIZE; i++) {
+		addr = (cb->address + i) % mm->wrap_addr;
+
+		if (mm->use_logical)
+			phys = (*calc->hw.mem_ltop)(calc, addr);
+		else
+			phys = addr;
+
+		editable = address_editable(calc, phys);
+		value = calc->mem[phys];
+
+		cb->info[i] = (value
+		               | (editable << 8)
+		               | (phys << 9));
+	}
+
+	tilem_calc_emulator_unlock(mm->emu);
+}
+
+/* Retrieve info for given address. */
+static guint32 get_mem_info(TilemMemModel *mm, dword addr)
+{
+	GList *l;
+	MemModelCacheBlock *cb;
+	dword start, index;
+
+	start = addr & ~(CACHE_BLOCK_SIZE - 1);
+	index = addr & (CACHE_BLOCK_SIZE - 1);
+
+	for (l = mm->cache->head; l; l = l->next) {
+		cb = l->data;
+		if (cb->address == start) {
+			if (l->prev) {
+				/* Move this cache block to the start
+				   of the list */
+				g_queue_unlink(mm->cache, l);
+				g_queue_push_head_link(mm->cache, l);
+			}
+
+			return cb->info[index];
+		}
+	}
+
+	/* Data not found in cache; drop the least recently used block
+	   and retrieve the requested block from the calc */
+	l = g_queue_pop_tail_link(mm->cache);
+	g_queue_push_head_link(mm->cache, l);
+	cb = l->data;
+	cb->address = start;
+	fill_cache_block(mm, cb);
+	return cb->info[index];
+}
+
+/* Get address's byte value. */
+static byte get_value(TilemMemModel *mm, dword addr)
+{
+	return (get_mem_info(mm, addr) & 0xff);
+}
+
+/* Get address's editability. */
+static gboolean get_editable(TilemMemModel *mm, dword addr)
+{
+	return ((get_mem_info(mm, addr) >> 8) & 1);
+}
+
+/* Get address's corresponding physical address. */
+static dword get_phys_addr(TilemMemModel *mm, dword addr)
+{
+	return (get_mem_info(mm, addr) >> 9);
+}
+
+/* Clear cache.  This function should be called any time something
+   happens that might affect memory contents. */
+void tilem_mem_model_clear_cache(TilemMemModel *mm)
+{
+	GList *l;
+	MemModelCacheBlock *cb;
+
+	g_return_if_fail(TILEM_IS_MEM_MODEL(mm));
+
+	for (l = mm->cache->head; l; l = l->next) {
+		cb = l->data;
+		cb->address = (dword) -1;
+	}
+}
+
+/* Get flags for the model */
+static GtkTreeModelFlags
+tilem_mem_model_get_flags(G_GNUC_UNUSED GtkTreeModel *model)
+{
+	return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
+}
+
+/* Get the number of columns */
+static int
+tilem_mem_model_get_n_columns(GtkTreeModel *model)
+{
+	TilemMemModel *mm;
+	g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), 0);
+	mm = TILEM_MEM_MODEL(model);
+	return (MM_COLUMNS_PER_BYTE * mm->row_size);
+}
+
+/* Get type of data for the given column.  Currently all columns are
+   strings. */
+static GType
+tilem_mem_model_get_column_type(G_GNUC_UNUSED GtkTreeModel *model,
+                                int index)
+{
+	index %= MM_COLUMNS_PER_BYTE;
+
+	switch (index) {
+	case MM_COL_ADDRESS_0:
+	case MM_COL_HEX_0:
+	case MM_COL_CHAR_0:
+		return G_TYPE_STRING;
+
+	case MM_COL_BYTE_PTR_0:
+		return G_TYPE_POINTER;
+
+	case MM_COL_EDITABLE_0:
+		return G_TYPE_BOOLEAN;
+
+	default:
+		g_return_val_if_reached(G_TYPE_INVALID);
+	}
+}
+
+/* Get an iterator pointing to the nth row */
+static gboolean get_nth_iter(GtkTreeModel *model, GtkTreeIter *iter, int n)
+{
+	TilemMemModel *mm;
+
+	g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), FALSE);
+	mm = TILEM_MEM_MODEL(model);
+
+	if (n >= mm->num_rows)
+		return FALSE;
+
+	iter->stamp = mm->stamp;
+	iter->user_data = GINT_TO_POINTER(n);
+	iter->user_data2 = NULL;
+	iter->user_data3 = NULL;
+	return TRUE;
+}
+
+/* Get row number for the given iterator */
+static int get_row_number(GtkTreeModel *model, GtkTreeIter *iter)
+{
+	TilemMemModel *mm;
+	int n;
+
+	g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), 0);
+	mm = TILEM_MEM_MODEL(model);
+	g_return_val_if_fail(iter != NULL, 0);
+	g_return_val_if_fail(iter->stamp == mm->stamp, 0);
+	n = GPOINTER_TO_INT(iter->user_data);
+	g_return_val_if_fail(n < mm->num_rows, 0);
+	return n;
+}
+
+/* Get iterator for a given path */
+static gboolean tilem_mem_model_get_iter(GtkTreeModel *model,
+                                         GtkTreeIter *iter,
+                                         GtkTreePath *path)
+{
+	int *indices;
+
+	if (gtk_tree_path_get_depth(path) != 1)
+		return FALSE;
+
+	indices = gtk_tree_path_get_indices(path);
+	return get_nth_iter(model, iter, indices[0]);
+}
+
+/* Get path for an iterator */
+static GtkTreePath * tilem_mem_model_get_path(GtkTreeModel *model,
+                                              GtkTreeIter *iter)
+{
+	int n;
+	n = get_row_number(model, iter);
+	return gtk_tree_path_new_from_indices(n, -1);
+}
+
+/* Get next (sibling) iterator */
+static gboolean tilem_mem_model_iter_next(GtkTreeModel *model,
+                                          GtkTreeIter *iter)
+{
+	int n;
+	n = get_row_number(model, iter);
+	return get_nth_iter(model, iter, n + 1);
+}
+
+/* Check if iterator has a child */
+static gboolean
+tilem_mem_model_iter_has_child(G_GNUC_UNUSED GtkTreeModel *model,
+			       G_GNUC_UNUSED GtkTreeIter *iter)
+{
+	return FALSE;
+}
+
+/* Get number of children (iter = NULL means get number of root
+   nodes) */
+static gint tilem_mem_model_iter_n_children(GtkTreeModel *model,
+                                            GtkTreeIter *iter)
+{
+	TilemMemModel *mm;
+
+	g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), 0);
+	mm = TILEM_MEM_MODEL(model);
+
+	if (iter)
+		return 0;
+	else
+		return (mm->num_rows);
+}
+
+/* Get nth child (parent = NULL means get nth root node */
+static gboolean tilem_mem_model_iter_nth_child( GtkTreeModel *model,
+                                               GtkTreeIter *iter,
+                                               GtkTreeIter *parent,
+                                               gint n)
+{
+	G_GNUC_UNUSED TilemMemModel* mm;
+
+	g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), FALSE);
+	mm = TILEM_MEM_MODEL(model);
+
+	if (parent)
+		return FALSE;
+	else
+		return get_nth_iter(model, iter, n);
+}
+
+/* Get first child */
+static gboolean tilem_mem_model_iter_children(GtkTreeModel *model,
+                                              GtkTreeIter *iter,
+                                              GtkTreeIter *parent)
+{
+	return tilem_mem_model_iter_nth_child(model, iter, parent, 0);
+}
+
+/* Get parent */
+static gboolean tilem_mem_model_iter_parent(G_GNUC_UNUSED GtkTreeModel *model,
+                                            G_GNUC_UNUSED GtkTreeIter *iter,
+                                            G_GNUC_UNUSED GtkTreeIter *child)
+{
+	return FALSE;
+}
+
+/* Retrieve value for a given column */
+static void tilem_mem_model_get_value(GtkTreeModel *model,
+                                      GtkTreeIter *iter,
+                                      gint column,
+                                      GValue *value)
+{
+	TilemMemModel *mm;
+	dword n, addr, phys;
+	TilemCalc *calc;
+	char buf[100], *s;
+
+	g_return_if_fail(TILEM_IS_MEM_MODEL(model));
+	mm = TILEM_MEM_MODEL(model);
+
+	g_return_if_fail(mm->emu != NULL);
+	g_return_if_fail(mm->emu->calc != NULL);
+
+	n = get_row_number(model, iter);
+
+	calc = mm->emu->calc;
+
+	addr = (mm->start_addr
+	        + n * mm->row_size
+	        + column / MM_COLUMNS_PER_BYTE) % mm->wrap_addr;
+
+	column %= MM_COLUMNS_PER_BYTE;
+
+	switch (column) {
+	case MM_COL_ADDRESS_0:
+		s = tilem_format_addr(mm->emu->dbg, addr, !mm->use_logical);
+		g_value_init(value, G_TYPE_STRING);
+		g_value_set_string(value, s);
+		g_free(s);
+		break;
+
+	case MM_COL_HEX_0:
+		g_snprintf(buf, sizeof(buf), "%02X", get_value(mm, addr));
+		g_value_init(value, G_TYPE_STRING);
+		g_value_set_string(value, buf);
+		break;
+
+	case MM_COL_CHAR_0:
+		s = ti_to_unicode(calc->hw.model_id, get_value(mm, addr));
+		g_value_init(value, G_TYPE_STRING);
+		g_value_set_string(value, s);
+		g_free(s);
+		break;
+
+	case MM_COL_BYTE_PTR_0:
+		phys = get_phys_addr(mm, addr);
+		g_value_init(value, G_TYPE_POINTER);
+		g_value_set_pointer(value, &calc->mem[phys]);
+		break;
+
+	case MM_COL_EDITABLE_0:
+		g_value_init(value, G_TYPE_BOOLEAN);
+		g_value_set_boolean(value, get_editable(mm, addr));
+		break;
+	}
+}
+
+static void tilem_mem_model_init(TilemMemModel *mm)
+{
+	int i;
+	MemModelCacheBlock *cb;
+
+	mm->stamp = g_random_int();
+	mm->row_size = 1;
+
+	mm->cache = g_queue_new();
+	for (i = 0; i < CACHE_NUM_BLOCKS; i++) {
+		cb = g_slice_new(MemModelCacheBlock);
+		cb->address = (dword) -1;
+		cb->info = g_new(guint32, CACHE_BLOCK_SIZE);
+		g_queue_push_head(mm->cache, cb);
+	}
+}
+
+static void tilem_mem_model_finalize(GObject *obj)
+{
+	TilemMemModel *mm;
+	MemModelCacheBlock *cb;
+
+	g_return_if_fail(TILEM_IS_MEM_MODEL(obj));
+	mm = TILEM_MEM_MODEL(obj);	                 
+
+	while ((cb = g_queue_pop_head(mm->cache))) {
+		g_free(cb->info);
+		g_slice_free(MemModelCacheBlock, cb);
+	}
+}
+
+static void tilem_mem_model_class_init(TilemMemModelClass *klass)
+{
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->finalize = &tilem_mem_model_finalize;
+}
+
+static void tilem_mem_tree_model_init(GtkTreeModelIface *iface)
+{
+	iface->get_flags = &tilem_mem_model_get_flags;
+	iface->get_n_columns = &tilem_mem_model_get_n_columns;
+	iface->get_column_type = &tilem_mem_model_get_column_type;
+	iface->get_iter = &tilem_mem_model_get_iter;
+	iface->get_path = &tilem_mem_model_get_path;
+	iface->get_value = &tilem_mem_model_get_value;
+	iface->iter_next = &tilem_mem_model_iter_next;
+	iface->iter_children = &tilem_mem_model_iter_children;
+	iface->iter_has_child = &tilem_mem_model_iter_has_child;
+	iface->iter_n_children = &tilem_mem_model_iter_n_children;
+	iface->iter_nth_child = &tilem_mem_model_iter_nth_child;
+	iface->iter_parent = &tilem_mem_model_iter_parent;
+}
+
+GType tilem_mem_model_get_type(void)
+{
+	static GType type = 0;
+
+	static const GTypeInfo type_info = {
+		sizeof(TilemMemModelClass),
+		NULL,
+		NULL,
+		(GClassInitFunc) tilem_mem_model_class_init,
+		NULL,
+		NULL,
+		sizeof(TilemMemModel),
+		0,
+		(GInstanceInitFunc) tilem_mem_model_init,
+		NULL
+	};
+
+	static const GInterfaceInfo tree_model_info = {
+		(GInterfaceInitFunc) tilem_mem_tree_model_init,
+		NULL,
+		NULL
+	};
+
+	if (!type) {
+		type = g_type_register_static(G_TYPE_OBJECT, "TilemMemModel",
+					      &type_info, 0);
+		g_type_add_interface_static(type, GTK_TYPE_TREE_MODEL,
+					    &tree_model_info);
+	}
+
+	return type;
+}
+
+GtkTreeModel * tilem_mem_model_new(TilemCalcEmulator* emu,
+                                   int rowsize, dword start,
+                                   gboolean logical)
+{
+	TilemMemModel* mm;
+
+	g_return_val_if_fail(emu != NULL, NULL);
+	g_return_val_if_fail(emu->calc != NULL, NULL);
+	g_return_val_if_fail(rowsize > 0, NULL);
+
+	mm = g_object_new(TILEM_TYPE_MEM_MODEL, NULL);
+
+	mm->emu = emu;
+	mm->row_size = rowsize;
+	mm->start_addr = start;
+
+	if (logical) {
+		mm->use_logical = TRUE;
+		mm->wrap_addr = 0x10000;
+	}
+	else {
+		mm->use_logical = FALSE;
+		mm->wrap_addr = (emu->calc->hw.romsize
+		                 + emu->calc->hw.ramsize);
+	}
+
+	mm->num_rows = (mm->wrap_addr + rowsize - 1) / rowsize;
+
+	return GTK_TREE_MODEL(mm);
+}
diff --git a/tool/tilem-src/gui/memmodel.h b/tool/tilem-src/gui/memmodel.h
new file mode 100644
index 0000000..58f84f4
--- /dev/null
+++ b/tool/tilem-src/gui/memmodel.h
@@ -0,0 +1,77 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+
+/* TilemMemModel object: an efficient GtkTreeModel interface for
+   viewing and editing calculator's memory */
+
+G_BEGIN_DECLS
+
+/* Column numbers (repeated for each byte in a given row) */
+enum {
+	MM_COL_ADDRESS_0 = 0,   /* Address (hexadecimal) */
+	MM_COL_HEX_0,           /* Byte value (hexadecimal) */
+	MM_COL_CHAR_0,          /* Byte value (character, converted to
+	                           UTF-8) */
+	MM_COL_BYTE_PTR_0,	/* Pointer to corresponding memory byte */
+	MM_COL_EDITABLE_0,      /* TRUE if byte is editable */
+	MM_COLUMNS_PER_BYTE
+};
+
+#define MM_COL_ADDRESS(n) (MM_COL_ADDRESS_0 + (n) * MM_COLUMNS_PER_BYTE)
+#define MM_COL_HEX(n) (MM_COL_HEX_0 + (n) * MM_COLUMNS_PER_BYTE)
+#define MM_COL_CHAR(n) (MM_COL_CHAR_0 + (n) * MM_COLUMNS_PER_BYTE)
+#define MM_COL_BYTE_PTR(n) (MM_COL_BYTE_PTR_0 + (n) * MM_COLUMNS_PER_BYTE)
+#define MM_COL_EDITABLE(n) (MM_COL_EDITABLE_0 + (n) * MM_COLUMNS_PER_BYTE)
+
+/* GObject stuff */
+
+#define TILEM_TYPE_MEM_MODEL           (tilem_mem_model_get_type())
+#define TILEM_MEM_MODEL(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), TILEM_TYPE_MEM_MODEL, TilemMemModel))
+#define TILEM_MEM_MODEL_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST((cls), TILEM_TYPE_MEM_MODEL, TilemMemModelClass))
+#define TILEM_IS_MEM_MODEL(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), TILEM_TYPE_MEM_MODEL))
+#define TILEM_IS_MEM_MODEL_CLASS(cls)  (G_TYPE_CHECK_CLASS_TYPE((cls), TILEM_TYPE_MEM_MODEL))
+#define TILEM_MEM_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TILEM_TYPE_MEM_MODEL, TilemMemModelClass))
+
+typedef struct _TilemMemModel {
+	GObject parent;
+	TilemCalcEmulator *emu;
+	gint stamp;
+	int row_size;
+	int num_rows;
+	dword start_addr;
+	dword wrap_addr;
+	gboolean use_logical;
+	GQueue *cache;
+} TilemMemModel;
+
+typedef struct _TilemMemModelClass {
+	GObjectClass parent_class;
+} TilemMemModelClass;
+
+GType tilem_mem_model_get_type(void) G_GNUC_CONST;
+
+GtkTreeModel * tilem_mem_model_new(TilemCalcEmulator* emu,
+                                   int rowsize, dword start,
+                                   gboolean logical);
+
+void tilem_mem_model_clear_cache(TilemMemModel *mm);
+
+G_END_DECLS
diff --git a/tool/tilem-src/gui/memory.c b/tool/tilem-src/gui/memory.c
new file mode 100644
index 0000000..03b09a1
--- /dev/null
+++ b/tool/tilem-src/gui/memory.c
@@ -0,0 +1,101 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <glib.h>
+#include <tilem.h>
+
+/* Memory management */
+
+void tilem_free(void* p)
+{
+	g_free(p);
+}
+
+void* tilem_malloc(size_t s)
+{
+	return g_malloc(s);
+}
+
+void* tilem_realloc(void* p, size_t s)
+{
+	return g_realloc(p, s);
+}
+
+void* tilem_try_malloc(size_t s)
+{
+	return g_try_malloc(s);
+}
+
+void* tilem_malloc0(size_t s)
+{
+	return g_malloc0(s);
+}
+
+void* tilem_try_malloc0(size_t s)
+{
+	return g_try_malloc0(s);
+}
+
+void* tilem_malloc_atomic(size_t s)
+{
+	return g_malloc(s);
+}
+
+void* tilem_try_malloc_atomic(size_t s)
+{
+	return g_try_malloc(s);
+}
+
+/* Logging */
+
+void tilem_message(TilemCalc* calc, const char* msg, ...)
+{
+	va_list ap;
+	va_start(ap, msg);
+	fprintf(stderr, "x%c: ", calc->hw.model_id);
+	vfprintf(stderr, msg, ap);
+	fputc('\n', stderr);
+	va_end(ap);
+}
+
+void tilem_warning(TilemCalc* calc, const char* msg, ...)
+{
+	va_list ap;
+	va_start(ap, msg);
+	fprintf(stderr, "x%c: WARNING: ", calc->hw.model_id);
+	vfprintf(stderr, msg, ap);
+	fputc('\n', stderr);
+	va_end(ap);
+}
+
+void tilem_internal(TilemCalc* calc, const char* msg, ...)
+{
+	va_list ap;
+	va_start(ap, msg);
+	fprintf(stderr, "x%c: INTERNAL ERROR: ", calc->hw.model_id);
+	vfprintf(stderr, msg, ap);
+	fputc('\n', stderr);
+	va_end(ap);
+}
diff --git a/tool/tilem-src/gui/memview.c b/tool/tilem-src/gui/memview.c
new file mode 100644
index 0000000..28d96bf
--- /dev/null
+++ b/tool/tilem-src/gui/memview.c
@@ -0,0 +1,291 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <tilem.h>
+#include <tilemdb.h>
+
+#include "gui.h"
+#include "memmodel.h"
+#include "fixedtreeview.h"
+
+static int get_column_index(GtkWidget *view, GtkTreeViewColumn *col)
+{
+	GList *cols;
+	int i;
+
+	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
+	i = g_list_index(cols, col);
+	g_list_free(cols);
+	return -1;
+}
+
+/* Determine current position in the memory view. */
+static void get_mem_view_position(GtkWidget *mem_view, dword *row_addr,
+                                  dword *col_addr, gboolean *cur_hex)
+{
+	GtkTreePath *path;
+	GtkTreeViewColumn *col;
+	GtkTreeModel *model;
+	TilemMemModel *mm;
+	const int *indices;
+	int n;
+
+	*row_addr = *col_addr = (dword) -1;
+	*cur_hex = FALSE;
+
+	gtk_tree_view_get_cursor(GTK_TREE_VIEW(mem_view), &path, &col);
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(mem_view));
+	if (!TILEM_IS_MEM_MODEL(model))
+		return;
+
+	mm = TILEM_MEM_MODEL(model);
+
+	if (!path)
+		return;
+
+	indices = gtk_tree_path_get_indices(path);
+	*row_addr = mm->start_addr + indices[0] * mm->row_size;
+
+	n = get_column_index(mem_view, col);
+	if (n > 0 && n <= mm->row_size) {
+		*col_addr = *row_addr + n - 1;
+		*cur_hex = TRUE;
+	}
+	else if (n > mm->row_size && n < mm->row_size * 2) {
+		*col_addr = *row_addr + n - mm->row_size - 1;
+		*cur_hex = FALSE;
+	}
+
+	gtk_tree_path_free(path);
+}
+
+static void addr_to_pos(TilemMemModel *mm, dword addr,
+                        int *rownum, int *colnum)
+{
+	if (addr < mm->start_addr)
+		addr += mm->wrap_addr;
+	addr -= mm->start_addr;
+	if (rownum) *rownum = (addr / mm->row_size);
+	if (colnum) *colnum = (addr % mm->row_size);
+}
+
+/* Move memory view cursor */
+static void set_mem_view_position(GtkWidget *mem_view, dword row_addr,
+                                  dword col_addr, gboolean cur_hex)
+{
+	int rownum, colnum;
+	GtkTreePath *path = NULL;
+	GtkTreeViewColumn *col = NULL;
+	GtkTreeModel *model;
+	TilemMemModel *mm;
+
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(mem_view));
+	if (!TILEM_IS_MEM_MODEL(model))
+		return;
+
+	mm = TILEM_MEM_MODEL(model);
+
+	if (col_addr != (dword) -1) {
+		addr_to_pos(mm, col_addr, &rownum, &colnum);
+		path = gtk_tree_path_new_from_indices(rownum, -1);
+
+		if (!cur_hex)
+			colnum += mm->row_size;
+
+		col = gtk_tree_view_get_column(GTK_TREE_VIEW(mem_view),
+		                               colnum + 1);
+	}
+	else if (row_addr != (dword) -1) {
+		addr_to_pos(mm, row_addr, &rownum, NULL);
+		path = gtk_tree_path_new_from_indices(rownum, -1);
+	}
+
+	if (path) {
+		gtk_tree_view_set_cursor(GTK_TREE_VIEW(mem_view),
+		                         path, col, FALSE);
+		gtk_tree_path_free(path);
+	}
+}
+
+/* Cell edited in memory view */
+static void hex_cell_edited(GtkCellRendererText *renderer,
+                            gchar *pathstr, gchar *text,
+                            gpointer data)
+{
+	GtkTreeView *mem_view = data;
+	TilemDebugger *dbg;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	byte *bptr = NULL;
+	int col;
+	int value;
+	char *end;
+
+	value = strtol(text, &end, 16);
+	if (end == text || *end != 0)
+		return;
+
+	dbg = g_object_get_data(G_OBJECT(mem_view), "tilem-debugger");
+	g_return_if_fail(dbg != NULL);
+
+	col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
+	                                        "tilem-mem-column"));
+
+	model = gtk_tree_view_get_model(mem_view);
+	path = gtk_tree_path_new_from_string(pathstr);
+	gtk_tree_model_get_iter(model, &iter, path);
+	gtk_tree_path_free(path);
+
+	gtk_tree_model_get(model, &iter, MM_COL_BYTE_PTR(col), &bptr, -1);
+	g_return_if_fail(bptr != NULL);
+
+	*bptr = (byte) value;
+	tilem_debugger_refresh(dbg, TRUE);
+}
+
+/* Create the GtkTreeView to show the memory */
+GtkWidget *tilem_debugger_mem_view_new(TilemDebugger *dbg)
+{
+	GtkCellRenderer     *renderer;
+	GtkTreeViewColumn   *column;
+	GtkWidget           *treeview;
+
+	/* Create the memory list tree view and set title invisible */
+	treeview = gtk_tree_view_new();
+	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
+	gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
+
+	g_object_set_data(G_OBJECT(treeview), "tilem-debugger", dbg);
+
+	/* Create the columns */
+	renderer = gtk_cell_renderer_text_new ();
+	column = gtk_tree_view_column_new_with_attributes
+		("ADDR", renderer, "text", MM_COL_ADDRESS(0), NULL);
+
+	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_expand(column, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+	return treeview;
+}
+
+static void create_columns(GtkWidget *mem_view, int width)
+{
+	GtkCellRenderer     *renderer;
+	GtkTreeViewColumn   *column;
+	int i;
+
+	for (i = 0; i < width; i++) {
+		renderer = gtk_cell_renderer_text_new();
+		column = gtk_tree_view_column_new_with_attributes
+			(NULL, renderer,
+			 "text", MM_COL_HEX(i),
+			 "editable", MM_COL_EDITABLE(i),
+			 NULL);
+
+		g_object_set_data(G_OBJECT(renderer), "tilem-mem-column",
+		                  GINT_TO_POINTER(i));
+		g_signal_connect(renderer, "edited",
+		                 G_CALLBACK(hex_cell_edited), mem_view);
+
+		gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
+		gtk_tree_view_column_set_expand(column, (i == width - 1));
+		gtk_tree_view_append_column(GTK_TREE_VIEW(mem_view), column);
+	}
+
+	for (i = 0; i < width; i++) {
+		renderer = gtk_cell_renderer_text_new();
+		column = gtk_tree_view_column_new_with_attributes
+			(NULL, renderer, "text", MM_COL_CHAR(i), NULL);
+
+		gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
+		gtk_tree_view_column_set_expand(column, (i == width - 1));
+		gtk_tree_view_append_column(GTK_TREE_VIEW(mem_view), column);
+	}
+}
+
+static dword translate_addr(TilemCalcEmulator *emu, dword a, gboolean ptol)
+{
+	if (!emu->calc || a == (dword) -1)
+		return a;
+	if (ptol)
+		return (*emu->calc->hw.mem_ptol)(emu->calc, a);
+	else
+		return (*emu->calc->hw.mem_ltop)(emu->calc, a & 0xffff);
+}
+
+void tilem_debugger_mem_view_configure(GtkWidget *mem_view,
+                                       TilemCalcEmulator *emu,
+                                       int rowsize, int start,
+                                       gboolean logical)
+{
+	GtkTreeModel *model;
+	dword row_addr, col_addr;
+	gboolean cur_hex;
+	GList *cols, *l;
+	int old_rowsize;
+
+	get_mem_view_position(mem_view, &row_addr, &col_addr, &cur_hex);
+
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(mem_view));
+	if (TILEM_IS_MEM_MODEL(model)
+	    && TILEM_MEM_MODEL(model)->use_logical != logical) {
+		tilem_calc_emulator_lock(emu);
+		row_addr = translate_addr(emu, row_addr, logical);
+		col_addr = translate_addr(emu, col_addr, logical);
+		tilem_calc_emulator_unlock(emu);
+	}
+
+	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(mem_view));
+	old_rowsize = (g_list_length(cols) - 1) / 2;
+	if (old_rowsize != rowsize)
+		for (l = g_list_next(cols); l; l = l->next)
+			gtk_tree_view_remove_column(GTK_TREE_VIEW(mem_view),
+			                            l->data);
+	g_list_free(cols);
+
+	model = tilem_mem_model_new(emu, rowsize, start, logical);
+	gtk_tree_view_set_model(GTK_TREE_VIEW(mem_view), model);
+	g_object_unref(model);
+
+	if (old_rowsize != rowsize)
+		create_columns(mem_view, rowsize);
+
+	fixed_tree_view_init(mem_view, MM_COLUMNS_PER_BYTE,
+	                     MM_COL_ADDRESS_0, "DD:DDDD ",
+	                     MM_COL_HEX_0, "DD ",
+	                     MM_COL_CHAR_0, "M ",
+	                     MM_COL_EDITABLE_0, TRUE,
+	                     -1);
+
+	set_mem_view_position(mem_view, row_addr, col_addr, cur_hex);
+}
diff --git a/tool/tilem-src/gui/menu.c b/tool/tilem-src/gui/menu.c
new file mode 100644
index 0000000..debf60a
--- /dev/null
+++ b/tool/tilem-src/gui/menu.c
@@ -0,0 +1,318 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2011-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "msgbox.h"
+
+static void action_send_file(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	load_file_dialog(ewin);
+}
+
+static void action_receive_file(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	//tilem_rcvmenu_new(ewin->emu);
+	//get_var(ewin->emu);
+	popup_receive_menu(ewin);
+}
+
+static void action_start_debugger(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	launch_debugger(ewin);
+}
+
+static void action_open_calc(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	tilem_calc_emulator_prompt_open_rom(ewin->emu);
+}
+
+static void action_save_calc(G_GNUC_UNUSED GtkAction *act, G_GNUC_UNUSED gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	GError *err = NULL;
+
+	if (!tilem_calc_emulator_save_state(ewin->emu, &err)) {
+		messagebox01(GTK_WINDOW(ewin->window), GTK_MESSAGE_ERROR,
+		             "Unable to save calculator state",
+		             "%s", err->message);
+		g_error_free(err);
+	}
+}
+
+static void action_revert_calc(G_GNUC_UNUSED GtkAction *act, G_GNUC_UNUSED gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	GError *err = NULL;
+
+	if (!tilem_calc_emulator_revert_state(ewin->emu, &err)) {
+		messagebox01(GTK_WINDOW(ewin->window), GTK_MESSAGE_ERROR,
+		             "Unable to load calculator state",
+		             "%s", err->message);
+		g_error_free(err);
+	}
+}
+
+static void action_reset_calc(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	tilem_calc_emulator_reset(ewin->emu);
+}
+
+static void action_begin_macro(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	tilem_macro_start(ewin->emu);
+}
+
+static void action_end_macro(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	tilem_macro_stop(ewin->emu);
+	/* tilem_macro_print(ewin->emu->macro); */
+}
+
+static void action_play_macro(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	tilem_macro_play(ewin->emu);
+}
+
+static void action_open_macro(G_GNUC_UNUSED GtkAction *act, G_GNUC_UNUSED gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	tilem_macro_load(ewin->emu, NULL);
+}
+
+/* I will improve macro creation by saving it firstly into a macro object
+ * Save macro will only be done if user choose to save it */
+static void action_save_macro(G_GNUC_UNUSED GtkAction *act, G_GNUC_UNUSED gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	tilem_macro_write_file(ewin->emu);
+}
+
+static void action_screenshot(G_GNUC_UNUSED GtkAction *act, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	popup_screenshot_window(ewin);
+}
+
+static void action_quick_screenshot(G_GNUC_UNUSED GtkAction *act,
+                                    gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	quick_screenshot(ewin);
+}
+
+static void action_preferences(G_GNUC_UNUSED GtkAction *act,
+                               gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	tilem_preferences_dialog(ewin);
+}
+
+static void action_about(G_GNUC_UNUSED GtkAction *act,
+                                    G_GNUC_UNUSED gpointer data)
+{
+	show_about();
+}
+
+static void action_quit(G_GNUC_UNUSED GtkAction *act,
+                        G_GNUC_UNUSED gpointer data)
+{
+	
+	TilemEmulatorWindow *ewin = data;
+	gtk_widget_destroy(ewin->window);
+}
+
+static const GtkActionEntry main_action_ents[] =
+	{{ "send-file",
+	   GTK_STOCK_OPEN, "Send _File...", "<ctrl>O",
+	   "Send a program or variable file to the calculator",
+	   G_CALLBACK(action_send_file) },
+
+	 { "receive-file",
+	   GTK_STOCK_SAVE_AS, "Re_ceive File...", "<ctrl>S",
+	   "Receive a program or variable from the calculator",
+	   G_CALLBACK(action_receive_file) },
+ 
+	 { "open-calc",
+	   GTK_STOCK_OPEN, "_Open Calculator...", "<shift><ctrl>O",
+	   "Open a calculator ROM file",
+	   G_CALLBACK(action_open_calc) },
+	 { "save-calc",
+	   GTK_STOCK_SAVE, "_Save Calculator", "<shift><ctrl>S",
+	   "Save current calculator state",
+	   G_CALLBACK(action_save_calc) },
+	 { "revert-calc",
+	   GTK_STOCK_REVERT_TO_SAVED, "Re_vert Calculator State", 0,
+	   "Revert to saved calculator state",
+	   G_CALLBACK(action_revert_calc) },
+	 { "reset-calc",
+	   GTK_STOCK_CLEAR, "_Reset Calculator", "<shift><ctrl>Delete",
+	   "Reset the calculator",
+	   G_CALLBACK(action_reset_calc) },
+
+	 { "start-debugger",
+	   0, "_Debugger", "Pause",
+	   "Pause emulation and start the debugger",
+	   G_CALLBACK(action_start_debugger) },
+
+	 { "begin-macro",
+	   GTK_STOCK_MEDIA_RECORD, "_Record", 0,
+	   "Begin recording a macro",
+	   G_CALLBACK(action_begin_macro) },
+	 { "end-macro",
+	   GTK_STOCK_MEDIA_STOP, "S_top", 0,
+	   "Begin recording a macro",
+	   G_CALLBACK(action_end_macro) },
+	 { "play-macro",
+	   GTK_STOCK_MEDIA_PLAY, "_Play", 0,
+	   "Play back the current macro",
+	   G_CALLBACK(action_play_macro) },
+	 { "open-macro",
+	   GTK_STOCK_OPEN, "_Open Macro File...", "",
+	   "Load a macro from a file",
+	   G_CALLBACK(action_open_macro) },
+	 { "save-macro",
+	   GTK_STOCK_SAVE_AS, "_Save Macro File...", "",
+	   "Save current macro to a file",
+	   G_CALLBACK(action_save_macro) },
+
+	 { "screenshot",
+	   0, "S_creenshot...", "<ctrl>Print",
+	   "Save a screenshot",
+	   G_CALLBACK(action_screenshot) },
+	 { "quick-screenshot",
+	   0, "_Quick Screenshot", "<shift><ctrl>Print",
+	   "Save a screenshot using default settings",
+	   G_CALLBACK(action_quick_screenshot) },
+
+	 { "preferences",
+	   GTK_STOCK_PREFERENCES, 0, 0,
+	   "Edit emulator settings",
+	   G_CALLBACK(action_preferences) },
+
+	 { "about",
+	   GTK_STOCK_ABOUT, "_About", "",
+	   "Print some informations about TilEm 2 and its authors",
+	   G_CALLBACK(action_about) },
+
+	 { "quit",
+	   GTK_STOCK_QUIT, "_Quit", "<ctrl>Q",
+	   "Quit the application",
+	   G_CALLBACK(action_quit) }};
+
+static GtkWidget *add_item(GtkWidget *menu, GtkAccelGroup *accelgrp,
+                           GtkActionGroup *actions, const char *name)
+{
+	GtkAction *action;
+	GtkWidget *item;
+
+	action = gtk_action_group_get_action(actions, name);
+	g_return_val_if_fail(action != NULL, NULL);
+
+	gtk_action_set_accel_group(action, accelgrp);
+	item = gtk_action_create_menu_item(action);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+	return item;
+}
+
+static GtkWidget *add_separator(GtkWidget *menu)
+{
+	GtkWidget *item;
+	item = gtk_separator_menu_item_new();
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+	return item;
+}
+
+static GtkWidget *add_submenu(GtkWidget *menu, const char *label)
+{
+	GtkWidget *item, *submenu;
+
+	item = gtk_menu_item_new_with_mnemonic(label);
+	submenu = gtk_menu_new();
+	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+	return submenu;
+}
+
+/* Build the menu */
+void build_menu(TilemEmulatorWindow* ewin)
+{
+	GtkActionGroup *acts;
+	GtkAccelGroup *ag;
+	GtkWidget *menu, *submenu;
+
+	ewin->actions = acts = gtk_action_group_new("Emulator");
+	gtk_action_group_add_actions(ewin->actions, main_action_ents,
+	                             G_N_ELEMENTS(main_action_ents), ewin);
+
+	ag = gtk_accel_group_new();
+	gtk_window_add_accel_group(GTK_WINDOW(ewin->window), ag);
+
+	ewin->popup_menu = menu = gtk_menu_new();
+
+	add_item(menu, ag, acts, "send-file");
+	add_item(menu, ag, acts, "receive-file");
+	add_separator(menu);
+
+	add_item(menu, ag, acts, "open-calc");
+	add_item(menu, ag, acts, "save-calc");
+	add_item(menu, ag, acts, "revert-calc");
+	add_item(menu, ag, acts, "reset-calc");
+	add_separator(menu);
+
+	add_item(menu, ag, acts, "start-debugger");
+	
+	submenu = add_submenu(menu, "_Macro");
+	add_item(submenu, ag, acts, "begin-macro");
+	add_item(submenu, ag, acts, "end-macro");
+	add_item(submenu, ag, acts, "play-macro");
+	add_separator(submenu);
+	add_item(submenu, ag, acts, "open-macro");
+	add_item(submenu, ag, acts, "save-macro");
+
+	add_item(menu, ag, acts, "screenshot");
+	add_item(menu, ag, acts, "quick-screenshot");
+	add_separator(menu);
+
+	add_item(menu, ag, acts, "preferences");
+	add_separator(menu);
+
+	add_item(menu, ag, acts, "about");
+	add_item(menu, ag, acts, "quit");
+}	
diff --git a/tool/tilem-src/gui/msgbox.h b/tool/tilem-src/gui/msgbox.h
new file mode 100644
index 0000000..7d88efd
--- /dev/null
+++ b/tool/tilem-src/gui/msgbox.h
@@ -0,0 +1,67 @@
+/*
+ * Tilem II
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Message box macros */
+
+#define messagebox00(prnt, mtyp, tprm, tsec) \
+  do {									\
+    GtkWidget* mbx;							\
+    mbx = gtk_message_dialog_new(GTK_WINDOW(prnt), GTK_DIALOG_MODAL,	\
+				 mtyp, GTK_BUTTONS_OK, tprm);		\
+    gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG(mbx),	\
+					       tsec);			\
+    gtk_dialog_set_default_response(GTK_DIALOG(mbx), GTK_RESPONSE_OK);	\
+    gtk_dialog_run(GTK_DIALOG(mbx));					\
+    gtk_widget_destroy(mbx);						\
+  } while(0)
+
+#define messagebox01(prnt, mtyp, tprm, tsec, aaaa) \
+  do {									\
+    GtkWidget* mbx;							\
+    mbx = gtk_message_dialog_new(GTK_WINDOW(prnt), GTK_DIALOG_MODAL,	\
+				 mtyp, GTK_BUTTONS_OK, tprm);		\
+    gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG(mbx),	\
+					       tsec, (aaaa));		\
+    gtk_dialog_set_default_response(GTK_DIALOG(mbx), GTK_RESPONSE_OK);	\
+    gtk_dialog_run(GTK_DIALOG(mbx));					\
+    gtk_widget_destroy(mbx);						\
+  } while(0)
+
+#define messagebox02(prnt, mtyp, tprm, tsec, aaaa, bbbb) \
+  do {									\
+    GtkWidget* mbx;							\
+    mbx = gtk_message_dialog_new(GTK_WINDOW(prnt), GTK_DIALOG_MODAL,	\
+				 mtyp, GTK_BUTTONS_OK, tprm);		\
+    gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG(mbx),	\
+					       tsec, (aaaa), (bbbb));	\
+    gtk_dialog_set_default_response(GTK_DIALOG(mbx), GTK_RESPONSE_OK);	\
+    gtk_dialog_run(GTK_DIALOG(mbx));					\
+    gtk_widget_destroy(mbx);						\
+  } while(0)
+
+#define messagebox11(prnt, mtyp, tprm, aaaa, tsec, bbbb) \
+  do {									\
+    GtkWidget* mbx;							\
+    mbx = gtk_message_dialog_new(GTK_WINDOW(prnt), GTK_DIALOG_MODAL,	\
+				 mtyp, GTK_BUTTONS_OK, tprm, (aaaa));	\
+    gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG(mbx),	\
+					       tsec, (bbbb));		\
+    gtk_dialog_set_default_response(GTK_DIALOG(mbx), GTK_RESPONSE_OK);	\
+    gtk_dialog_run(GTK_DIALOG(mbx));					\
+    gtk_widget_destroy(mbx);						\
+  } while(0)
diff --git a/tool/tilem-src/gui/pbar.c b/tool/tilem-src/gui/pbar.c
new file mode 100644
index 0000000..0d6354e
--- /dev/null
+++ b/tool/tilem-src/gui/pbar.c
@@ -0,0 +1,156 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+
+/* Update the progress_bar */
+static void progress_bar_update_activity(TilemLinkProgress *linkpb)
+{
+	char *s;
+	gdouble f;
+
+	if (!linkpb->window || !linkpb->emu->pbar_title)
+		return;
+
+	gtk_window_set_title(GTK_WINDOW(linkpb->window), linkpb->emu->pbar_title);
+
+	s = g_strdup_printf("<big><b>%s</b></big>", linkpb->emu->pbar_title);
+	gtk_label_set_markup(linkpb->title_lbl, s);
+	g_free(s);
+
+	if (linkpb->emu->paused && linkpb->emu->pbar_status) {
+		s = g_strconcat(linkpb->emu->pbar_status, " (paused)", NULL);
+		gtk_label_set_text(linkpb->status_lbl, s);
+		g_free(s);
+	}
+	else if (linkpb->emu->paused)
+		gtk_label_set_text(linkpb->status_lbl, "(paused)");
+	else
+		gtk_label_set_text(linkpb->status_lbl, linkpb->emu->pbar_status);
+
+	if (linkpb->emu->pbar_progress < 0.0) {
+		gtk_progress_bar_pulse(linkpb->progress_bar);
+	}
+	else {
+		f = CLAMP(linkpb->emu->pbar_progress, 0.0, 1.0);
+		gtk_progress_bar_set_fraction(linkpb->progress_bar, f);
+	}
+}
+
+/* Callback to destroy the progress bar */
+static void destroy_progress(G_GNUC_UNUSED GtkDialog *dlg,
+                             G_GNUC_UNUSED gint response,
+                             gpointer data)
+{
+	TilemLinkProgress* linkpb = data;
+	tilem_calc_emulator_cancel_tasks(linkpb->emu);
+	gtk_widget_destroy(linkpb->window);
+	linkpb->window = NULL;
+	linkpb->progress_bar = NULL;
+	linkpb->title_lbl = NULL;
+	linkpb->status_lbl = NULL;
+}
+
+/* Create the progress bar window */
+static void progress_bar_init(TilemLinkProgress* linkpb)
+{
+	GtkWidget *pw, *parent, *vbox, *vbox2, *lbl, *pb;
+
+	if (linkpb->emu->ewin)
+		parent = linkpb->emu->ewin->window;
+	else
+		parent = NULL;
+
+	pw = gtk_dialog_new_with_buttons("", GTK_WINDOW(parent), 0,
+	                                 GTK_STOCK_CANCEL,
+	                                 GTK_RESPONSE_CANCEL,
+	                                 NULL);
+	linkpb->window = pw;
+
+	gtk_window_set_resizable(GTK_WINDOW(pw), FALSE);
+
+	vbox = gtk_dialog_get_content_area(GTK_DIALOG(pw));
+
+	vbox2 = gtk_vbox_new(FALSE, 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox2), 6);
+
+	lbl = gtk_label_new(NULL);
+	gtk_label_set_width_chars(GTK_LABEL(lbl), 35);
+	gtk_misc_set_alignment(GTK_MISC(lbl), 0.0, 0.5);
+	gtk_box_pack_start(GTK_BOX(vbox2), lbl, FALSE, FALSE, 0);
+	linkpb->title_lbl = GTK_LABEL(lbl);
+
+	pb = gtk_progress_bar_new();
+	gtk_box_pack_start(GTK_BOX(vbox2), pb, FALSE, FALSE, 0);
+	linkpb->progress_bar = GTK_PROGRESS_BAR(pb);
+
+	lbl = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(lbl), 0.0, 0.5);
+	gtk_box_pack_start(GTK_BOX(vbox2), lbl, FALSE, FALSE, 0);
+	linkpb->status_lbl = GTK_LABEL(lbl);
+
+	gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 6);
+
+	g_signal_connect(pw, "response", G_CALLBACK(destroy_progress), linkpb);
+
+	progress_bar_update_activity(linkpb);
+
+	gtk_widget_show_all(pw);
+}
+
+/* Create or update the progress bar */
+void progress_bar_update(TilemCalcEmulator* emu)
+{
+	g_return_if_fail(emu != NULL);
+
+	g_mutex_lock(emu->pbar_mutex);
+
+	if (!emu->linkpb) {
+		emu->linkpb = g_slice_new0(TilemLinkProgress);
+		emu->linkpb->emu = emu;
+	}
+
+	if (!emu->linkpb->window && emu->pbar_title) {
+		progress_bar_init(emu->linkpb);
+	}
+	else if (emu->linkpb->window && !emu->pbar_title) {
+		gtk_widget_destroy(emu->linkpb->window);
+		emu->linkpb->window = NULL;
+		emu->linkpb->title_lbl = NULL;
+		emu->linkpb->status_lbl = NULL;
+		emu->linkpb->progress_bar = NULL;
+	}
+	else {
+		progress_bar_update_activity(emu->linkpb);
+	}
+
+	emu->pbar_update_pending = FALSE;
+	g_mutex_unlock(emu->pbar_mutex);
+}
+
diff --git a/tool/tilem-src/gui/preferences.c b/tool/tilem-src/gui/preferences.c
new file mode 100644
index 0000000..7758d38
--- /dev/null
+++ b/tool/tilem-src/gui/preferences.c
@@ -0,0 +1,270 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "files.h"
+#include "filedlg.h"
+
+/* Convert to an absolute path */
+static char *canonicalize_filename(const char *name)
+{
+#ifdef G_OS_WIN32
+	static const char delim[] = "/\\";
+#else
+	static const char delim[] = G_DIR_SEPARATOR_S;
+#endif
+	char *result, **parts, *p;
+	int i;
+
+	if (name == NULL || g_path_is_absolute(name))
+		return g_strdup(name);
+
+	result = g_get_current_dir();
+	parts = g_strsplit_set(name, delim, -1);
+	for (i = 0; parts[i]; i++) {
+		if (!strcmp(parts[i], "..")) {
+			p = g_path_get_dirname(result);
+			g_free(result);
+			result = p;
+		}
+		else if (strcmp(parts[i], ".")
+		         && strcmp(parts[i], "")) {
+			p = g_build_filename(result, parts[i], NULL);
+			g_free(result);
+			result = p;
+		}
+	}
+	g_strfreev(parts);
+	return result;
+}
+
+/* check if two file names are equivalent (of course, if this fails,
+   it doesn't necessarily mean the files are distinct) */
+static gboolean file_names_equal(const char *a, const char *b)
+{
+	char *ca, *cb;
+	gboolean status;
+	
+	if (a == NULL && b == NULL)
+		return TRUE;
+	else if (a == NULL || b == NULL)
+		return FALSE;
+
+	ca = canonicalize_filename(a);
+	cb = canonicalize_filename(b);
+	status = !strcmp(ca, cb);
+	g_free(ca);
+	g_free(cb);
+	return status;
+}
+
+static void save_skin_name(TilemEmulatorWindow *ewin)
+{
+	const char *model = ewin->emu->calc->hw.name;
+	char *base, *shared;
+
+	/* don't save pref unless skin was actually loaded */
+	if (!ewin->skin_file_name || !ewin->skin)
+		return;
+
+	/* if file is stored in shared skins directory, save
+	   only the relative path; otherwise, save the
+	   absolute path */
+	base = g_path_get_basename(ewin->skin_file_name);
+	shared = get_shared_file_path("skins", base, NULL);
+
+	if (file_names_equal(shared, ewin->skin_file_name))
+		tilem_config_set(model,
+		                 "skin/f", base,
+		                 NULL);
+	else
+		tilem_config_set(model,
+		                 "skin/f", ewin->skin_file_name,
+		                 NULL);
+
+	g_free(base);
+	g_free(shared);
+}
+
+static void speed_changed(GtkToggleButton *btn, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	gboolean setting = gtk_toggle_button_get_active(btn);
+	tilem_calc_emulator_set_limit_speed(ewin->emu, setting);
+	tilem_config_set("emulation",
+	                 "limit_speed/b", setting,
+	                 NULL);
+}
+
+static void grayscale_changed(GtkToggleButton *btn, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	gboolean setting = gtk_toggle_button_get_active(btn);
+	tilem_calc_emulator_set_grayscale(ewin->emu, setting);
+	tilem_config_set("emulation",
+	                 "grayscale/b", setting,
+	                 NULL);
+}
+
+static void smooth_changed(GtkToggleButton *btn, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	gboolean setting = gtk_toggle_button_get_active(btn);
+	ewin->lcd_smooth_scale = setting;
+	tilem_config_set("settings",
+	                 "smooth_scaling/b", setting,
+	                 NULL);
+}
+
+
+static void skin_enable_changed(GtkToggleButton *btn, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	gboolean enable = gtk_toggle_button_get_active(btn);
+
+	if (ewin->skin_disabled == !enable)
+		return;
+
+	tilem_emulator_window_set_skin_disabled(ewin, !enable);
+	tilem_config_set("settings",
+	                 "skin_disabled/b", !enable,
+	                 NULL);
+
+	save_skin_name(ewin);
+}
+
+static void skin_file_changed(GtkWidget *fe, gpointer data)
+{
+	TilemEmulatorWindow *ewin = data;
+	char *fname = file_entry_get_filename(fe);
+
+	if (fname && !file_names_equal(fname, ewin->skin_file_name)) {
+		tilem_emulator_window_set_skin(ewin, fname);
+		save_skin_name(ewin);
+		g_free(fname);
+	}
+}
+
+/* Run preferences dialog. */
+void tilem_preferences_dialog(TilemEmulatorWindow *ewin)
+{
+	GtkWidget *dlg, *vbox1, *vbox2, *frame, *slow_rb, *fast_rb,
+		*grayscale_cb, *smooth_cb, *hbox, *skin_cb, *skin_entry;
+
+	g_return_if_fail(ewin != NULL);
+	g_return_if_fail(ewin->emu != NULL);
+
+	dlg = gtk_dialog_new_with_buttons("Preferences",
+	                                  GTK_WINDOW(ewin->window),
+	                                  GTK_DIALOG_MODAL,
+	                                  GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+	                                  NULL);
+
+	vbox1 = gtk_vbox_new(FALSE, 12);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox1), 6);
+
+	/* Emulation speed */
+
+	vbox2 = gtk_vbox_new(FALSE, 6);
+
+	slow_rb = gtk_radio_button_new_with_mnemonic
+		(NULL, "_Limit to actual calculator speed");
+	gtk_box_pack_start(GTK_BOX(vbox2), slow_rb, FALSE, FALSE, 0);
+
+	fast_rb = gtk_radio_button_new_with_mnemonic_from_widget
+		(GTK_RADIO_BUTTON(slow_rb), "As _fast as possible");
+	gtk_box_pack_start(GTK_BOX(vbox2), fast_rb, FALSE, FALSE, 0);
+
+	if (!ewin->emu->limit_speed)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fast_rb), TRUE);
+
+	g_signal_connect(slow_rb, "toggled",
+	                 G_CALLBACK(speed_changed), ewin);
+
+	frame = new_frame("Emulation Speed", vbox2);
+	gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, FALSE, 0);
+
+	/* Display settings */
+
+	vbox2 = gtk_vbox_new(FALSE, 6);
+
+	grayscale_cb = gtk_check_button_new_with_mnemonic("Emulate _grayscale");
+	gtk_box_pack_start(GTK_BOX(vbox2), grayscale_cb, FALSE, FALSE, 0);
+
+	if (ewin->emu->grayscale)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grayscale_cb), TRUE);
+
+	g_signal_connect(grayscale_cb, "toggled",
+	                 G_CALLBACK(grayscale_changed), ewin);
+
+	smooth_cb = gtk_check_button_new_with_mnemonic("Use _smooth scaling");
+	gtk_box_pack_start(GTK_BOX(vbox2), smooth_cb, FALSE, FALSE, 0);
+
+	if (ewin->lcd_smooth_scale)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(smooth_cb), TRUE);
+
+	g_signal_connect(smooth_cb, "toggled",
+	                 G_CALLBACK(smooth_changed), ewin);
+
+	hbox = gtk_hbox_new(FALSE, 6);
+
+	skin_cb = gtk_check_button_new_with_mnemonic("Use s_kin:");
+	gtk_box_pack_start(GTK_BOX(hbox), skin_cb, FALSE, FALSE, 0);
+
+	skin_entry = file_entry_new("Select Skin",
+	                            "Skin files", "*.skn",
+	                            "All files", "*",
+	                            NULL);
+	gtk_box_pack_start(GTK_BOX(hbox), skin_entry, TRUE, TRUE, 0);
+
+	gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
+
+	if (!ewin->skin_disabled)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(skin_cb), TRUE);
+
+	if (ewin->skin_file_name)
+		file_entry_set_filename(skin_entry, ewin->skin_file_name);
+
+	g_signal_connect(skin_cb, "toggled",
+	                 G_CALLBACK(skin_enable_changed), ewin);
+
+	g_signal_connect(skin_entry, "selection-changed",
+	                 G_CALLBACK(skin_file_changed), ewin);
+
+	frame = new_frame("Display", vbox2);
+	gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, FALSE, 0);
+
+	vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
+	gtk_box_pack_start(GTK_BOX(vbox2), vbox1, FALSE, FALSE, 0);
+	gtk_widget_show_all(vbox1);
+
+	gtk_dialog_run(GTK_DIALOG(dlg));
+	gtk_widget_destroy(dlg);
+}
diff --git a/tool/tilem-src/gui/rcvmenu.c b/tool/tilem-src/gui/rcvmenu.c
new file mode 100644
index 0000000..22ec546
--- /dev/null
+++ b/tool/tilem-src/gui/rcvmenu.c
@@ -0,0 +1,665 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <ticonv.h>
+#include <tilem.h>
+#include <tilemdb.h>
+#include <scancodes.h>
+
+#include "gui.h"
+#include "disasmview.h"
+#include "memmodel.h"
+#include "files.h"
+#include "filedlg.h"
+#include "msgbox.h"
+#include "fixedtreeview.h"
+
+static GtkTreeModel* fill_varlist(TilemReceiveDialog *rcvdialog);
+TilemReceiveDialog* create_receive_menu(TilemCalcEmulator *emu);
+
+/* Columns */
+enum
+{
+	COL_ENTRY = 0,
+	COL_SLOT_STR,
+	COL_NAME_STR,
+	COL_TYPE_STR,
+	COL_SIZE_STR,
+	COL_SIZE,
+  	NUM_COLS
+};
+
+#define RESPONSE_REFRESH 1
+
+/* Prompt to overwrite a list of files. */
+static gboolean prompt_overwrite(GtkWindow *win, const char *dirname,
+                                 char **filenames)
+{
+	int i;
+	char *dname;
+	GString *conflicts = NULL;
+	int nconflicts = 0;
+	GtkWidget *dlg, *btn;
+	int response;
+
+	for (i = 0; filenames[i]; i++) {
+		if (g_file_test(filenames[i], G_FILE_TEST_EXISTS)) {
+			if (conflicts)
+				g_string_append_c(conflicts, '\n');
+			else
+				conflicts = g_string_new(NULL);
+			dname = g_filename_display_basename(filenames[i]);
+			g_string_append(conflicts, dname);
+			g_free(dname);
+			nconflicts++;
+		}
+	}
+
+	if (!conflicts)
+		return TRUE;
+
+	dname = g_filename_display_basename(dirname);
+
+	dlg = gtk_message_dialog_new
+		(win, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
+		 (nconflicts == 1
+		  ? "Replace existing file?"
+		  : "Replace existing files?"));
+
+	gtk_message_dialog_format_secondary_text
+		(GTK_MESSAGE_DIALOG(dlg),
+		 (nconflicts == 1
+		  ? "The file \"%2$s\" already exists in \"%1$s\"."
+		  "  Replacing it will overwrite its contents."
+		  : "The following files already exist in \"%s\"."
+		  "  Replacing them will overwrite their contents:\n%s"),
+		 dname, conflicts->str);
+
+	g_free(dname);
+	g_string_free(conflicts, TRUE);
+
+	gtk_dialog_add_button(GTK_DIALOG(dlg),
+	                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+
+	btn = gtk_button_new_with_mnemonic("_Replace");
+	gtk_button_set_image(GTK_BUTTON(btn),
+	                     gtk_image_new_from_stock(GTK_STOCK_SAVE,
+	                                              GTK_ICON_SIZE_BUTTON));
+	gtk_widget_show(btn);
+	gtk_dialog_add_action_widget(GTK_DIALOG(dlg), btn,
+	                             GTK_RESPONSE_ACCEPT);
+
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
+	                                        GTK_RESPONSE_ACCEPT,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+
+	response = gtk_dialog_run(GTK_DIALOG(dlg));
+	gtk_widget_destroy(dlg);
+	return (response == GTK_RESPONSE_ACCEPT);
+}
+
+/* #### SIGNALS CALLBACK #### */
+
+/* Prompt to save a single file. */
+static gboolean prompt_save_single(TilemReceiveDialog *rcvdialog, TilemVarEntry *tve)
+{
+	char *dir, *default_filename, *default_filename_r, *filename, *pattern;
+
+	default_filename = get_default_filename(tve);
+	default_filename_r = utf8_to_restricted_utf8(default_filename);
+	g_free(default_filename);
+
+	tilem_config_get("download", "receivefile_recentdir/f", &dir, NULL);	
+	if (!dir) dir = g_get_current_dir();
+
+	pattern = g_strconcat("*.", tve->file_ext, NULL);
+
+	filename = prompt_save_file("Save File", GTK_WINDOW(rcvdialog->window),
+	                            default_filename_r, dir,
+	                            tve->filetype_desc, pattern,
+	                            "All files", "*",
+	                            NULL);
+	g_free(default_filename_r);
+	g_free(pattern);
+	g_free(dir);
+
+	if (!filename)
+		return FALSE;
+
+	dir = g_path_get_dirname(filename);
+	tilem_config_set("download", "receivefile_recentdir/f", dir, NULL);
+	g_free(dir);
+
+	tilem_link_receive_file(rcvdialog->emu, tve, filename);
+	g_free(filename);
+	return TRUE;
+}
+
+/* Prompt to save a list of variables as a group file. */
+static gboolean prompt_save_group(TilemReceiveDialog *rcvdialog, GList *rows)
+{
+	char *dir, *default_filename, *pattern_desc, *pattern, *filename, *fext;
+	int tfmodel;
+	gboolean can_group = TRUE;
+	const char *model_str;
+	GList *l;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	TilemVarEntry *tve;
+	GSList *velist = NULL;
+
+	tilem_config_get("download", "receivefile_recentdir/f", &dir, NULL);	
+	if (!dir) dir = g_get_current_dir();
+
+	for (l = rows; l; l = l->next) {
+		path = (GtkTreePath*) l->data;
+		gtk_tree_model_get_iter(rcvdialog->model, &iter, path);
+		gtk_tree_model_get(rcvdialog->model, &iter, COL_ENTRY, &tve, -1);
+		velist = g_slist_prepend(velist, tve);
+		if (!tve->can_group)
+			can_group = FALSE;
+	}
+
+	velist = g_slist_reverse(velist);
+
+	tfmodel = get_calc_model(rcvdialog->emu->calc);
+
+	fext = g_ascii_strdown(tifiles_fext_of_group(tfmodel), -1);
+	pattern = g_strconcat("*.", fext, NULL);
+	default_filename = g_strdup_printf("untitled.%s",
+	                                   (can_group ? fext : "tig"));
+	g_free(fext);
+
+	model_str = tifiles_model_to_string(tfmodel);
+	pattern_desc = g_strdup_printf("%s group files", model_str);
+
+	filename = prompt_save_file("Save File", GTK_WINDOW(rcvdialog->window),
+	                            default_filename, dir,
+	                            pattern_desc, (can_group ? pattern : ""),
+	                            "TIGroup files", "*.tig",
+	                            "All files", "*",
+	                            NULL);
+
+	g_free(default_filename);
+	g_free(dir);
+	g_free(pattern_desc);
+	g_free(pattern);
+
+	if (!filename) {
+		g_slist_free(velist);
+		return FALSE;
+	}
+
+	dir = g_path_get_dirname(filename);
+	tilem_config_set("download",
+	                 "receivefile_recentdir/f", dir,
+	                 "save_as_group/b", TRUE,
+	                 NULL);
+	g_free(dir);
+
+	tilem_link_receive_group(rcvdialog->emu, velist, filename);
+
+	g_free(filename);
+	g_slist_free(velist);
+	return TRUE;
+}
+
+/* Prompt to save a list of files.  Input is a list of GtkTreePaths */
+static gboolean prompt_save_multiple(TilemReceiveDialog *rcvdialog, GList *rows)
+{
+	char *dir, *dir_selected, *default_filename, *default_filename_f;
+	GList *l;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	TilemVarEntry *tve, **vars;
+	char **names;
+	gboolean is_81, use_group;
+	int i;
+
+	is_81 = (rcvdialog->emu->calc->hw.model_id == TILEM_CALC_TI81);
+	use_group = gtk_toggle_button_get_active
+		(GTK_TOGGLE_BUTTON(rcvdialog->group_rb));
+
+	if (use_group && !is_81)
+		return prompt_save_group(rcvdialog, rows);
+
+	tilem_config_get("download", "receivefile_recentdir/f", &dir, NULL);	
+	if (!dir) dir = g_get_current_dir();
+
+	dir_selected = prompt_select_dir("Save Files to Directory",
+	                                 GTK_WINDOW(rcvdialog->window),
+	                                 dir);
+	g_free(dir);
+
+	if (!dir_selected)
+		return FALSE;
+
+	tilem_config_set("download",
+	                 "receivefile_recentdir/f", dir_selected,
+	                 "save_as_group/b", use_group,
+	                 NULL);
+
+	vars = g_new(TilemVarEntry *, g_list_length(rows) + 1);
+	names = g_new(char *, g_list_length(rows) + 1);
+
+	for (l = rows, i = 0; l; l = l->next, i++) {
+		path = (GtkTreePath*) l->data;
+		gtk_tree_model_get_iter(rcvdialog->model, &iter, path);
+		gtk_tree_model_get(rcvdialog->model, &iter, COL_ENTRY, &tve, -1);
+
+		vars[i] = tve;
+
+		default_filename = get_default_filename(tve);
+		default_filename_f = utf8_to_filename(default_filename);
+		names[i] = g_build_filename(dir_selected,
+		                            default_filename_f, NULL);
+		g_free(default_filename);
+		g_free(default_filename_f);
+	}
+
+	vars[i] = NULL;
+	names[i] = NULL;
+
+	if (!prompt_overwrite(GTK_WINDOW(rcvdialog->window),
+	                      dir_selected, names)) {
+		g_free(vars);
+		g_strfreev(names);
+		g_free(dir_selected);
+		return FALSE;
+	}
+
+	for (i = 0; vars[i]; i++)
+		tilem_link_receive_file(rcvdialog->emu, vars[i], names[i]);
+
+	g_free(vars);
+	g_strfreev(names);
+	g_free(dir_selected);
+	return TRUE;
+}
+
+/* Event called on Send button click. Get the selected var/app and save it. */
+static gboolean prompt_save(TilemReceiveDialog *rcvdialog)
+{
+	TilemVarEntry *tve;		/* Variable entry */
+	GtkTreeSelection* selection = NULL; /* GtkTreeSelection */
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GList *rows, *l;
+	GtkTreePath *path;
+	gboolean status;
+
+	/* Get the selected entry */
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(rcvdialog->treeview));
+	rows = gtk_tree_selection_get_selected_rows(selection, &model);
+
+	if (!rows)
+		return FALSE;
+
+	if (!rows->next) {
+		path = (GtkTreePath*) rows->data;
+		gtk_tree_model_get_iter(model, &iter, path);
+		gtk_tree_model_get(model, &iter, COL_ENTRY, &tve, -1);
+		status = prompt_save_single(rcvdialog, tve);
+	}
+	else {
+		status = prompt_save_multiple(rcvdialog, rows);
+	}
+
+	for (l = rows; l; l = l->next)
+		gtk_tree_path_free(l->data);
+	g_list_free(rows);
+	return status;
+}
+
+/* Dialog response button clicked */
+static void dialog_response(GtkDialog *dlg, gint response, gpointer data)
+{
+	TilemReceiveDialog* rcvdialog = (TilemReceiveDialog*) data;
+
+	switch (response) {
+	case RESPONSE_REFRESH:
+		if (!rcvdialog->refresh_pending) {
+			rcvdialog->refresh_pending = TRUE;
+			tilem_link_get_dirlist(rcvdialog->emu);
+		}
+		break;
+
+	case GTK_RESPONSE_ACCEPT:
+		if (!prompt_save(rcvdialog))
+			break;
+	default:
+		gtk_widget_hide(GTK_WIDGET(dlg));
+	}
+}
+
+/* Selection changed */
+static void selection_changed(GtkTreeSelection *sel, gpointer data)
+{
+	TilemReceiveDialog* rcvdialog = data;
+	int n = gtk_tree_selection_count_selected_rows(sel);
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(rcvdialog->window),
+	                                  GTK_RESPONSE_ACCEPT, (n > 0));
+	gtk_widget_set_sensitive(rcvdialog->mode_box, (n > 1));
+}
+
+/* Row activated in tree view */
+static void row_activated(G_GNUC_UNUSED GtkTreeView *treeview,
+                          G_GNUC_UNUSED GtkTreePath *path,
+                          G_GNUC_UNUSED GtkTreeViewColumn *col,
+                          gpointer data)
+{
+	TilemReceiveDialog* rcvdialog = (TilemReceiveDialog*) data;
+	gtk_dialog_response(GTK_DIALOG(rcvdialog->window), GTK_RESPONSE_ACCEPT);
+}
+
+/* #### WIDGET CREATION #### */
+
+/* Create a new scrolled window with sensible default settings. */
+static GtkWidget *new_scrolled_window(GtkWidget *contents)
+{
+        GtkWidget *sw; 
+        sw = gtk_scrolled_window_new(NULL, NULL);
+        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+                                       GTK_POLICY_NEVER,
+                                       GTK_POLICY_AUTOMATIC);
+        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+                                            GTK_SHADOW_IN);
+        gtk_container_add(GTK_CONTAINER(sw), contents);
+        return sw;
+}
+
+/* Create the (empty) GtkTreeView to show the vars list */
+static GtkWidget *create_varlist(TilemReceiveDialog *rcvdialog)
+{
+	GtkCellRenderer   *renderer;
+	GtkWidget         *treeview;
+	GtkTreeSelection  *sel;
+	GtkTreeViewColumn *c1, *c2, *c3, *c4;
+	gboolean           is_81;
+
+	g_return_val_if_fail(rcvdialog->emu != NULL, NULL);
+	g_return_val_if_fail(rcvdialog->emu->calc != NULL, NULL);
+
+	is_81 = (rcvdialog->emu->calc->hw.model_id == TILEM_CALC_TI81);
+
+	/* Create the stack list tree view and set title invisible */
+	treeview = gtk_tree_view_new();
+	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
+	
+	/* Allow multiple selection */
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+	gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
+
+	/* Create the columns */
+	renderer = gtk_cell_renderer_text_new();
+
+	if (is_81) {
+		c1 = gtk_tree_view_column_new_with_attributes
+			("Slot", renderer, "text", COL_SLOT_STR, NULL);
+
+		gtk_tree_view_column_set_sizing(c1, GTK_TREE_VIEW_COLUMN_FIXED);
+		gtk_tree_view_column_set_sort_column_id(c1, COL_SLOT_STR);
+		gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), c1);
+	}
+
+	c2 = gtk_tree_view_column_new_with_attributes
+		("Name", renderer, "text", COL_NAME_STR, NULL);
+
+	gtk_tree_view_column_set_sizing(c2, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_sort_column_id(c2, COL_NAME_STR);
+	gtk_tree_view_column_set_expand(c2, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), c2);
+
+	if (!is_81) {
+		c3 = gtk_tree_view_column_new_with_attributes
+			("Type", renderer, "text", COL_TYPE_STR, NULL);
+		
+		gtk_tree_view_column_set_sizing(c3, GTK_TREE_VIEW_COLUMN_FIXED);
+		gtk_tree_view_column_set_sort_column_id(c3, COL_TYPE_STR);
+		gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), c3);
+	}
+
+	renderer = gtk_cell_renderer_text_new();
+	g_object_set(renderer, "xalign", 1.0, NULL);
+	c4 = gtk_tree_view_column_new_with_attributes
+		("Size", renderer, "text", COL_SIZE_STR, NULL);
+
+	gtk_tree_view_column_set_sizing(c4, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_sort_column_id(c4, COL_SIZE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), c4);
+
+	g_signal_connect(sel, "changed",
+	                 G_CALLBACK(selection_changed), rcvdialog);
+
+	g_signal_connect(treeview, "row-activated",
+	                 G_CALLBACK(row_activated), rcvdialog);
+
+	return treeview;
+}
+
+/* Fill the list of vars. In fact, add all vars from list to a GtkListStore */
+static GtkTreeModel* fill_varlist(TilemReceiveDialog *rcvdialog)
+{
+	GSList *l;
+	TilemVarEntry *tve;
+	GtkListStore *store;
+	GtkTreeIter iter;
+	char *size_str;
+
+	store = gtk_list_store_new(6,
+	                           G_TYPE_POINTER,
+	                           G_TYPE_STRING,
+	                           G_TYPE_STRING,
+	                           G_TYPE_STRING,
+	                           G_TYPE_STRING,
+	                           G_TYPE_INT);
+
+	for (l = rcvdialog->vars; l; l = l->next) {
+		tve = l->data;
+		gtk_list_store_append(store, &iter);
+#ifdef G_OS_WIN32
+		size_str = g_strdup_printf("%d", tve->size);
+#else
+		size_str = g_strdup_printf("%'d", tve->size);
+#endif
+		gtk_list_store_set(store, &iter,
+		                   COL_ENTRY, tve,
+		                   COL_SLOT_STR, tve->slot_str,
+		                   COL_NAME_STR, tve->name_str,
+		                   COL_TYPE_STR, tve->type_str,
+		                   COL_SIZE_STR, size_str,
+		                   COL_SIZE, tve->size,
+		                   -1);
+		g_free(size_str);
+	}
+
+	return GTK_TREE_MODEL(store);
+}
+
+/* Create a new menu for receiving vars. */
+/* Previous allocated and filled varlist is needed */
+TilemReceiveDialog* tilem_receive_dialog_new(TilemCalcEmulator *emu)
+{
+	TilemReceiveDialog* rcvdialog = g_slice_new0(TilemReceiveDialog);
+	GtkWidget *scroll, *btn, *vbox, *lbl, *rb, *vbox2;
+	int defheight = 300;
+	gboolean is_81;
+	gboolean use_group;
+
+	g_return_val_if_fail(emu != NULL, NULL);
+	g_return_val_if_fail(emu->ewin != NULL, NULL);
+	g_return_val_if_fail(emu->calc != NULL, NULL);
+
+	rcvdialog->emu = emu;
+	emu->rcvdlg = rcvdialog;
+
+	is_81 = (emu->calc->hw.model_id == TILEM_CALC_TI81);
+
+	rcvdialog->window = gtk_dialog_new();
+	gtk_window_set_transient_for(GTK_WINDOW(rcvdialog->window),
+	                             GTK_WINDOW(emu->ewin->window));
+
+	gtk_window_set_title(GTK_WINDOW(rcvdialog->window), "Receive File");
+
+	btn = gtk_dialog_add_button(GTK_DIALOG(rcvdialog->window),
+	                            GTK_STOCK_REFRESH, RESPONSE_REFRESH);
+
+	if (is_81)
+		gtk_widget_hide(btn);
+
+	gtk_dialog_add_button(GTK_DIALOG(rcvdialog->window),
+	                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+	gtk_dialog_add_button(GTK_DIALOG(rcvdialog->window),
+	                      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT);
+
+	gtk_dialog_set_default_response(GTK_DIALOG(rcvdialog->window),
+	                                GTK_RESPONSE_ACCEPT);
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(rcvdialog->window),
+	                                        RESPONSE_REFRESH,
+	                                        GTK_RESPONSE_ACCEPT,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+
+	/* Set the size of the dialog */
+	gtk_window_set_default_size(GTK_WINDOW(rcvdialog->window), -1, defheight);
+	
+	/* Create and fill tree view */
+	rcvdialog->treeview = create_varlist(rcvdialog);
+
+	/* Allow scrolling the list because we can't know how many vars the calc contains */
+	scroll = new_scrolled_window(rcvdialog->treeview);
+
+	vbox = gtk_vbox_new(FALSE, 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scroll), TRUE, TRUE, 0);
+
+	rcvdialog->mode_box = gtk_hbox_new(FALSE, 6);
+	lbl = gtk_label_new("Save as:");
+	gtk_box_pack_start(GTK_BOX(rcvdialog->mode_box), lbl, FALSE, FALSE, 0);
+
+	rb = gtk_radio_button_new_with_mnemonic(NULL, "S_eparate files");
+	gtk_box_pack_start(GTK_BOX(rcvdialog->mode_box), rb, FALSE, FALSE, 0);
+	rcvdialog->multiple_rb = rb;
+
+	rb = gtk_radio_button_new_with_mnemonic_from_widget
+		(GTK_RADIO_BUTTON(rb), "_Group file");
+	gtk_box_pack_start(GTK_BOX(rcvdialog->mode_box), rb, FALSE, FALSE, 0);
+	rcvdialog->group_rb = rb;
+
+	tilem_config_get("download", "save_as_group/b=1", &use_group, NULL);
+	if (use_group)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
+
+	if (is_81)
+		gtk_widget_set_no_show_all(rcvdialog->mode_box, TRUE);
+
+	gtk_box_pack_start(GTK_BOX(vbox), rcvdialog->mode_box, FALSE, FALSE, 0);
+	vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(rcvdialog->window));
+	gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
+
+	/* Signals callback */
+	g_signal_connect(rcvdialog->window, "response",
+	                 G_CALLBACK(dialog_response), rcvdialog);
+	g_signal_connect(rcvdialog->window, "delete-event",
+	                 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
+	
+	gtk_widget_show_all(vbox);
+
+	return rcvdialog;
+}
+
+/* Destroy a TilemReceiveDialog */
+void tilem_receive_dialog_free(TilemReceiveDialog *rcvdialog)
+{
+	GSList *l;
+
+	g_return_if_fail(rcvdialog != NULL);
+
+	gtk_widget_destroy(rcvdialog->window);
+
+	for (l = rcvdialog->vars; l; l = l->next)
+		tilem_var_entry_free(l->data);
+	g_slist_free(rcvdialog->vars);
+
+	g_slice_free(TilemReceiveDialog, rcvdialog);
+}
+
+void tilem_receive_dialog_update(TilemReceiveDialog *rcvdialog, GSList *varlist)
+{
+	GSList *l;
+
+	g_return_if_fail(rcvdialog != NULL);
+
+	rcvdialog->refresh_pending = FALSE;
+
+	for (l = rcvdialog->vars; l; l = l->next)
+		tilem_var_entry_free(l->data);
+	g_slist_free(rcvdialog->vars);
+
+	rcvdialog->vars = varlist;
+	rcvdialog->model = fill_varlist(rcvdialog);
+	gtk_tree_view_set_model(GTK_TREE_VIEW(rcvdialog->treeview), rcvdialog->model);
+
+	fixed_tree_view_init(rcvdialog->treeview, 0,
+	                     COL_SLOT_STR, "PrgmM ",
+	                     COL_NAME_STR, "MMMMMMMMM ",
+	                     COL_TYPE_STR, "MMMMMM ",
+	                     COL_SIZE_STR, "00,000,000",
+	                     -1);
+
+	gtk_widget_grab_focus(rcvdialog->treeview);
+	gtk_window_present(GTK_WINDOW(rcvdialog->window));
+}
+
+/* Popup the receive window */
+/* This is the entry point */
+void popup_receive_menu(TilemEmulatorWindow *ewin)
+{
+	g_return_if_fail(ewin != NULL);
+	g_return_if_fail(ewin->emu != NULL);
+	g_return_if_fail(ewin->emu->calc != NULL);
+
+	if (ewin->emu->rcvdlg && ewin->emu->rcvdlg->refresh_pending)
+		return;
+
+	/* TI-81 takes no time to refresh, so do it automatically */
+	if (!ewin->emu->rcvdlg
+	    || ewin->emu->calc->hw.model_id == TILEM_CALC_TI81) {
+		tilem_link_get_dirlist(ewin->emu);
+	}
+	else {
+		gtk_widget_grab_focus(ewin->emu->rcvdlg->treeview);
+		gtk_window_present(GTK_WINDOW(ewin->emu->rcvdlg->window));
+	}
+}
diff --git a/tool/tilem-src/gui/screenshot.c b/tool/tilem-src/gui/screenshot.c
new file mode 100644
index 0000000..d8b7e51
--- /dev/null
+++ b/tool/tilem-src/gui/screenshot.c
@@ -0,0 +1,810 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "files.h"
+#include "filedlg.h"
+#include "msgbox.h"
+
+#define DEFAULT_WIDTH_96    192
+#define DEFAULT_HEIGHT_96   128
+#define DEFAULT_WIDTH_128   256
+#define DEFAULT_HEIGHT_128  128
+#define DEFAULT_FORMAT      "png"
+
+struct imgsize {
+	int width;
+	int height;
+};
+
+static const struct imgsize normal_sizes[] =
+	{ { 96, 64 }, { 192, 128 }, { 288, 192 } };
+
+static const struct imgsize wide_sizes[] =
+	/* actual aspect ratio is 92:55 or 1.673:1 */
+	{ { 128, 64 }, { 128, 77 },
+	  { 214, 128 }, { 256, 128 }, { 256, 153 },
+	  { 321, 192 }, { 384, 192 } };
+
+static void grab_screen(GtkButton *btn, TilemScreenshotDialog *ssdlg);
+static void begin_animation(GtkButton *btn, TilemScreenshotDialog *ssdlg);
+static void end_animation(GtkButton *btn, TilemScreenshotDialog *ssdlg);
+static gboolean save_output(TilemScreenshotDialog *ssdlg);
+
+static char* find_free_filename(const char* directory,
+                                const char* filename,
+                                const char* extension);
+
+/* Test if the calc has a wide screen (ti86) */
+static gboolean is_wide_screen(TilemCalcEmulator *emu)
+{
+	g_return_val_if_fail(emu != NULL, FALSE);
+	g_return_val_if_fail(emu->calc != NULL, FALSE);
+
+	return (emu->calc->hw.lcdwidth == 128);
+}
+
+/* Quick screenshot: save a screenshot with predefined settings,
+   without prompting the user */
+void quick_screenshot(TilemEmulatorWindow *ewin)
+{
+	char *folder, *filename, *format;
+	int grayscale, w96, h96, w128, h128, width, height;
+	TilemAnimation *anim;
+	GError *err = NULL;
+	GdkColor fg, bg;
+
+	tilem_config_get("screenshot",
+	                 "directory/f", &folder,
+	                 "format/s", &format,
+	                 "grayscale/b=1", &grayscale,
+	                 "width_96x64/i", &w96,
+	                 "height_96x64/i", &h96,
+	                 "width_128x64/i", &w128,
+	                 "height_128x64/i", &h128,
+	                 "foreground/c=#000", &fg,
+	                 "background/c=#fff", &bg,
+	                 NULL);
+
+	anim = tilem_calc_emulator_get_screenshot(ewin->emu, grayscale);
+	if (!anim) {
+		g_free(folder);
+		g_free(format);
+		return;
+	}
+
+	if (is_wide_screen(ewin->emu)) {
+		width = (w128 > 0 ? w128 : DEFAULT_WIDTH_128);
+		height = (h128 > 0 ? h128 : DEFAULT_HEIGHT_128);
+	}
+	else {
+		width = (w96 > 0 ? w96 : DEFAULT_WIDTH_96);
+		height = (h96 > 0 ? h96 : DEFAULT_HEIGHT_96);
+	}
+
+	tilem_animation_set_size(anim, width, height);
+	tilem_animation_set_colors(anim, &fg, &bg);
+
+	if (!folder)
+		folder = get_config_file_path("screenshots", NULL);
+
+	if (!format)
+		format = g_strdup(DEFAULT_FORMAT);
+
+	g_mkdir_with_parents(folder, 0755);
+
+	filename = find_free_filename(folder, "screenshot", format);
+	if (!filename) {
+		g_free(folder);
+		g_free(format);
+		g_object_unref(anim);
+		return;
+	}
+
+	printf("screenshot saved : %s\n", filename);
+
+	if (!tilem_animation_save(anim, filename, format, NULL, NULL, &err)) {
+		messagebox01(ewin->window, GTK_MESSAGE_ERROR,
+		             "Unable to save screenshot",
+		             "%s", err->message);
+		g_error_free(err);
+	}
+
+	g_object_unref(anim);
+	g_free(filename);
+	g_free(folder);
+	g_free(format);
+}
+
+/* Look for a free filename by testing [folder]/[basename]000.[extension] to [folder]/[basename]999.[extension]
+   Return a newly allocated string if success
+   Return null if no filename found */
+static char* find_free_filename(const char* folder,
+                                const char* basename,
+                                const char* extension)
+{
+	int i;
+	char *filename, *prefix;
+
+	if(folder)
+		prefix = g_build_filename(folder, basename, NULL);
+	else
+		prefix = g_build_filename(basename, NULL);
+
+	/* I do not use a while and limit number to 1000 because for any reason, if there's a problem in this scope
+	   I don't want to freeze tilem (if tilem don't find a free filename and never return anything)
+	   Limit to 1000 prevent this problem but if you prefer we could use a while wich wait a valid filename... */
+	for(i=0; i<999; i++) {
+		filename = g_strdup_printf("%s%03d.%s", prefix, i, extension);
+		if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+			g_free(prefix);
+			return filename;
+		}
+		g_free(filename);
+	}
+
+	g_free(prefix);
+	return NULL;
+}
+
+/* Change the review image to set the current animation */
+static void set_current_animation(TilemScreenshotDialog *ssdlg,
+                                  TilemAnimation *anim)
+{
+	GtkImage *img = GTK_IMAGE(ssdlg->screenshot_preview_image);
+	int width, height;
+	GdkColor fg, bg;
+	gdouble speed;
+
+	if (anim)
+		g_object_ref(anim);
+	if (ssdlg->current_anim)
+		g_object_unref(ssdlg->current_anim);
+	ssdlg->current_anim = anim;
+
+	if (!anim) {
+		gtk_image_set_from_animation(img, NULL);
+		gtk_dialog_set_response_sensitive(GTK_DIALOG(ssdlg->window),
+		                                  GTK_RESPONSE_ACCEPT, FALSE);
+	}
+	else {
+		width = gtk_spin_button_get_value_as_int
+			(GTK_SPIN_BUTTON(ssdlg->width_spin));
+		height = gtk_spin_button_get_value_as_int
+			(GTK_SPIN_BUTTON(ssdlg->height_spin));
+		tilem_animation_set_size(anim, width, height);
+
+		gtk_color_button_get_color
+			(GTK_COLOR_BUTTON(ssdlg->foreground_color), &fg);
+		gtk_color_button_get_color
+			(GTK_COLOR_BUTTON(ssdlg->background_color), &bg);
+		tilem_animation_set_colors(anim, &fg, &bg);
+
+		speed = gtk_spin_button_get_value
+			(GTK_SPIN_BUTTON(ssdlg->animation_speed));
+		tilem_animation_set_speed(anim, speed);
+
+		gtk_image_set_from_animation(img, GDK_PIXBUF_ANIMATION(anim));
+
+		/* Need to call gtk_widget_show because we hide it
+		   while recording */
+		gtk_widget_show(ssdlg->screenshot_preview_image);
+
+		gtk_dialog_set_response_sensitive(GTK_DIALOG(ssdlg->window),
+		                                  GTK_RESPONSE_ACCEPT, TRUE);
+	}
+}
+
+static void dialog_response(G_GNUC_UNUSED GtkDialog *dialog, gint response, gpointer data)
+{
+	TilemScreenshotDialog *ssdlg = data;
+
+	if (response == GTK_RESPONSE_ACCEPT) {
+		if (!save_output(ssdlg))
+			return;
+	}
+
+	gtk_widget_hide(GTK_WIDGET(dialog));
+	end_animation(NULL, ssdlg);
+	set_current_animation(ssdlg, NULL);
+}
+
+static void set_size_spin_buttons(TilemScreenshotDialog *ssdlg,
+                                  int width, int height)
+{
+	gtk_spin_button_set_value(GTK_SPIN_BUTTON(ssdlg->width_spin), width);
+	gtk_spin_button_set_value(GTK_SPIN_BUTTON(ssdlg->height_spin), height);
+}
+
+enum {
+	COL_TEXT,
+	COL_WIDTH,
+	COL_HEIGHT
+};
+
+static void animation_speed_changed(GtkSpinButton *animation_speed,
+                                    gpointer data)
+{
+	TilemScreenshotDialog *ssdlg = data;
+	TilemAnimation * anim = ssdlg->current_anim;
+	gdouble value = gtk_spin_button_get_value(animation_speed);
+	tilem_animation_set_speed(anim, value);
+}
+
+/* Combo box changed.  Update spin buttons accordingly. */
+static void size_combo_changed(GtkComboBox *combo,
+                               TilemScreenshotDialog *ssdlg)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	int width, height;
+
+	if (gtk_combo_box_get_active_iter(combo, &iter)) {
+		model = gtk_combo_box_get_model(combo);
+		gtk_tree_model_get(model, &iter,
+		                   COL_WIDTH, &width,
+		                   COL_HEIGHT, &height,
+		                   -1);
+		if (width && height)
+			set_size_spin_buttons(ssdlg, width, height);
+	}
+}
+
+static void size_spin_changed(G_GNUC_UNUSED GtkSpinButton *sb,
+                              TilemScreenshotDialog *ssdlg)
+{
+	GtkComboBox *combo = GTK_COMBO_BOX(ssdlg->ss_size_combo);
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	int width, height, w, h;
+
+	model = gtk_combo_box_get_model(combo);
+	if (!model || !gtk_tree_model_get_iter_first(model, &iter))
+		return;
+
+	width = gtk_spin_button_get_value_as_int
+		(GTK_SPIN_BUTTON(ssdlg->width_spin));
+	height = gtk_spin_button_get_value_as_int
+		(GTK_SPIN_BUTTON(ssdlg->height_spin));
+
+	do {
+		gtk_tree_model_get(model, &iter,
+		                   COL_WIDTH, &w,
+		                   COL_HEIGHT, &h,
+		                   -1);
+
+		if ((w == 0 && h == 0) || (w == width && h == height)) {
+			gtk_combo_box_set_active_iter(combo, &iter);
+			break;
+		}
+	} while (gtk_tree_model_iter_next(model, &iter));
+
+	set_current_animation(ssdlg, ssdlg->current_anim);
+}
+
+static void fill_size_combobox(GtkComboBox *combo,
+                               const struct imgsize *sizes,
+                               int nsizes)
+{
+	GtkListStore *store;
+	GtkTreeIter iter;
+	int i;
+	char *s;
+
+	store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
+
+	for (i = 0; i < nsizes; i++) {
+		s = g_strdup_printf("%d \303\227 %d",
+		                    sizes[i].width,
+		                    sizes[i].height);
+
+		gtk_list_store_append(store, &iter);
+		gtk_list_store_set(store, &iter,
+		                   COL_TEXT, s,
+		                   COL_WIDTH, sizes[i].width,
+		                   COL_HEIGHT, sizes[i].height,
+		                   -1);
+		g_free(s);
+	}
+
+	gtk_list_store_append(store, &iter);
+	gtk_list_store_set(store, &iter,
+	                   COL_TEXT, "Custom",
+	                   COL_WIDTH, 0,
+	                   COL_HEIGHT, 0,
+	                   -1);
+
+	gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(store));
+}
+
+/* This method is called when a color is set (foreground or background)
+ * It set a new palette based on new custom colors
+ * It refresh the screen to print new colors 
+ */
+static void color_changed(G_GNUC_UNUSED GtkSpinButton *sb,
+                              TilemScreenshotDialog *ssdlg)
+{
+	set_current_animation(ssdlg, ssdlg->current_anim);
+}
+
+/* Create the screenshot menu */
+static TilemScreenshotDialog * create_screenshot_window(TilemCalcEmulator *emu)
+{
+	TilemScreenshotDialog *ssdlg = g_slice_new0(TilemScreenshotDialog);
+	GtkWidget *main_table, *vbox, *frame, *config_expander,
+		*tbl, *lbl, *align;
+	GtkCellRenderer *cell;
+
+	ssdlg->emu = emu;
+
+	ssdlg->window = gtk_dialog_new_with_buttons
+		("Screenshot",
+		 (emu->ewin ? GTK_WINDOW(emu->ewin->window) : NULL),
+		 GTK_DIALOG_DESTROY_WITH_PARENT,
+		 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+		 NULL);
+	
+	gtk_window_set_resizable(GTK_WINDOW(ssdlg->window), FALSE);
+
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(ssdlg->window),
+	                                        GTK_RESPONSE_ACCEPT,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+
+	gtk_dialog_set_default_response(GTK_DIALOG(ssdlg->window),
+	                                GTK_RESPONSE_ACCEPT);
+
+	g_signal_connect(ssdlg->window, "response",
+	                 G_CALLBACK(dialog_response), ssdlg);
+
+	g_signal_connect(ssdlg->window, "delete-event",
+	                 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
+
+	main_table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(main_table), 6);
+	gtk_table_set_col_spacings(GTK_TABLE(main_table), 12);
+	gtk_container_set_border_width(GTK_CONTAINER(main_table), 6);
+
+	/* Preview */
+
+	frame = gtk_frame_new("Preview");
+	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+
+	ssdlg->screenshot_preview_image = gtk_image_new();
+	align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
+	gtk_container_add(GTK_CONTAINER(align), ssdlg->screenshot_preview_image);
+
+	gtk_container_add(GTK_CONTAINER(frame), align);
+	gtk_table_attach(GTK_TABLE(main_table), frame, 0, 1, 0, 1,
+	                 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+
+	/* Buttons */
+
+	vbox = gtk_vbutton_box_new();
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(vbox), GTK_BUTTONBOX_START);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+
+	ssdlg->screenshot = gtk_button_new_with_mnemonic("_Grab");
+	gtk_box_pack_start(GTK_BOX(vbox), ssdlg->screenshot, FALSE, FALSE, 0);
+
+	ssdlg->record = gtk_button_new_with_mnemonic("_Record");
+	gtk_box_pack_start(GTK_BOX(vbox), ssdlg->record, FALSE, FALSE, 0);
+
+	ssdlg->stop = gtk_button_new_with_mnemonic("_Stop");
+	gtk_box_pack_start(GTK_BOX(vbox), ssdlg->stop, FALSE, FALSE, 0);
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->stop), FALSE);
+
+	gtk_table_attach(GTK_TABLE(main_table), vbox, 1, 2, 0, 2,
+	                 GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+
+	/* Options */
+
+	config_expander = gtk_expander_new("Options");
+
+	tbl = gtk_table_new(7, 2, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
+	gtk_table_set_col_spacings(GTK_TABLE(tbl), 6);
+
+	ssdlg->grayscale_tb = gtk_check_button_new_with_mnemonic("Gra_yscale");
+	gtk_table_attach(GTK_TABLE(tbl), ssdlg->grayscale_tb,
+	                 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	lbl = gtk_label_new_with_mnemonic("Image si_ze:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(GTK_TABLE(tbl), lbl,
+	                 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+
+	ssdlg->ss_size_combo = gtk_combo_box_new();
+	cell = gtk_cell_renderer_text_new();
+	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(ssdlg->ss_size_combo),
+	                           cell, TRUE);
+	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(ssdlg->ss_size_combo),
+	                               cell, "text", COL_TEXT, NULL);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->ss_size_combo);
+	gtk_table_attach(GTK_TABLE(tbl), ssdlg->ss_size_combo,
+	                 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+
+	lbl = gtk_label_new_with_mnemonic("_Width:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(GTK_TABLE(tbl), lbl,
+	                 0, 1, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
+
+	ssdlg->width_spin = gtk_spin_button_new_with_range(1, 750, 1);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->width_spin);
+	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
+	gtk_container_add(GTK_CONTAINER(align), ssdlg->width_spin);
+	gtk_table_attach(GTK_TABLE(tbl), align,
+	                 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
+
+	lbl = gtk_label_new_with_mnemonic("_Height:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(GTK_TABLE(tbl), lbl,
+	                 0, 1, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
+
+	ssdlg->height_spin = gtk_spin_button_new_with_range(1, 500, 1);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->height_spin);
+	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
+	gtk_container_add(GTK_CONTAINER(align), ssdlg->height_spin);
+	gtk_table_attach(GTK_TABLE(tbl), align,
+	                 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
+
+
+	lbl = gtk_label_new_with_mnemonic("Animation s_peed:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(GTK_TABLE(tbl), lbl,
+	                 0, 1, 4, 5, GTK_FILL, GTK_FILL, 0, 0);
+
+	ssdlg->animation_speed = gtk_spin_button_new_with_range(0.1, 100.0, 0.1);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->animation_speed);
+	gtk_spin_button_set_value(GTK_SPIN_BUTTON(ssdlg->animation_speed), 1.0);
+	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
+	gtk_container_add(GTK_CONTAINER(align), ssdlg->animation_speed);
+	gtk_table_attach(GTK_TABLE(tbl), align,
+	               1, 2, 4, 5, GTK_FILL, GTK_FILL, 0, 0);
+
+	/* Foreground color and background color */
+	lbl = gtk_label_new_with_mnemonic("_Foreground:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(GTK_TABLE(tbl), lbl,
+	                 0, 1, 5, 6, GTK_FILL, GTK_FILL, 0, 0);
+
+	ssdlg->foreground_color = gtk_color_button_new();
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->foreground_color);
+	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
+	gtk_container_add(GTK_CONTAINER(align), ssdlg->foreground_color);
+	gtk_table_attach(GTK_TABLE(tbl), align,
+	                 1, 2, 5, 6, GTK_FILL, GTK_FILL, 0, 0);
+	
+	lbl = gtk_label_new_with_mnemonic("_Background:");
+	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
+	gtk_table_attach(GTK_TABLE(tbl), lbl,
+	                 0, 1, 6, 7, GTK_FILL, GTK_FILL, 0, 0);
+
+	ssdlg->background_color = gtk_color_button_new();
+	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->background_color);
+	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
+	gtk_container_add(GTK_CONTAINER(align), ssdlg->background_color);
+	gtk_table_attach(GTK_TABLE(tbl), align,
+	                 1, 2, 6, 7, GTK_FILL, GTK_FILL, 0, 0);
+
+	align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
+	gtk_container_add(GTK_CONTAINER(align), tbl);
+
+	gtk_container_add(GTK_CONTAINER(config_expander), align);
+
+	gtk_table_attach(GTK_TABLE(main_table), config_expander, 0, 1, 1, 2,
+	                 GTK_FILL, GTK_FILL, 0, 0);
+
+	g_signal_connect(ssdlg->screenshot, "clicked",
+	                 G_CALLBACK(grab_screen), ssdlg);
+	g_signal_connect(ssdlg->record, "clicked",
+	                 G_CALLBACK(begin_animation), ssdlg);
+	g_signal_connect(ssdlg->stop, "clicked",
+	                 G_CALLBACK(end_animation), ssdlg);
+
+	g_signal_connect(ssdlg->ss_size_combo, "changed",
+	                 G_CALLBACK(size_combo_changed), ssdlg);
+	g_signal_connect(ssdlg->width_spin, "value-changed",
+	                 G_CALLBACK(size_spin_changed), ssdlg);
+	g_signal_connect(ssdlg->height_spin, "value-changed",
+	                 G_CALLBACK(size_spin_changed), ssdlg);
+	g_signal_connect(ssdlg->animation_speed, "value-changed",
+	                 G_CALLBACK(animation_speed_changed), ssdlg);
+	
+	g_signal_connect(ssdlg->foreground_color, "color-set",
+	                 G_CALLBACK(color_changed), ssdlg);
+	g_signal_connect(ssdlg->background_color, "color-set",
+	                 G_CALLBACK(color_changed), ssdlg);
+	/*g_signal_connect(config_expander, "activate",
+	                 G_CALLBACK(on_config_expander_activate), ssdlg);
+	*/
+	vbox = gtk_dialog_get_content_area(GTK_DIALOG(ssdlg->window));
+	gtk_container_add(GTK_CONTAINER(vbox), main_table);
+	gtk_widget_show_all(main_table);
+
+	return ssdlg;
+}
+
+/* Popup the screenshot window */
+void popup_screenshot_window(TilemEmulatorWindow *ewin)
+{
+	TilemScreenshotDialog *ssdlg;
+	int w96, h96, w128, h128, width, height, grayscale;
+	GdkColor fg, bg;
+
+	g_return_if_fail(ewin != NULL);
+	g_return_if_fail(ewin->emu != NULL);
+
+	if (!ewin->emu->ssdlg)
+		ewin->emu->ssdlg = create_screenshot_window(ewin->emu);
+	ssdlg = ewin->emu->ssdlg;
+
+	tilem_config_get("screenshot",
+	                 "grayscale/b=1", &grayscale,
+	                 "width_96x64/i", &w96,
+	                 "height_96x64/i", &h96,
+	                 "width_128x64/i", &w128,
+	                 "height_128x64/i", &h128,
+	                 "foreground/c=#000", &fg,
+	                 "background/c=#fff", &bg,
+	                 NULL);
+
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ssdlg->grayscale_tb),
+	                             grayscale);
+
+	if (is_wide_screen(ewin->emu)) {
+		fill_size_combobox(GTK_COMBO_BOX(ssdlg->ss_size_combo),
+		                   wide_sizes, G_N_ELEMENTS(wide_sizes));
+		width = (w128 > 0 ? w128 : DEFAULT_WIDTH_128);
+		height = (h128 > 0 ? h128 : DEFAULT_HEIGHT_128);
+	}
+	else {
+		fill_size_combobox(GTK_COMBO_BOX(ssdlg->ss_size_combo),
+		                   normal_sizes, G_N_ELEMENTS(normal_sizes));
+		width = (w96 > 0 ? w96 : DEFAULT_WIDTH_96);
+		height = (h96 > 0 ? h96 : DEFAULT_HEIGHT_96);
+	}
+
+	set_size_spin_buttons(ssdlg, width, height);
+	size_spin_changed(NULL, ssdlg);
+
+	gtk_color_button_set_color(GTK_COLOR_BUTTON(ssdlg->foreground_color), &fg);
+	gtk_color_button_set_color(GTK_COLOR_BUTTON(ssdlg->background_color), &bg);
+
+	grab_screen(NULL, ssdlg);
+	gtk_window_present(GTK_WINDOW(ssdlg->window));
+}
+
+/* Save the current (static) output */
+static gboolean save_output(TilemScreenshotDialog *ssdlg)
+{
+	char *dir, *format, *filename, *basename;
+	TilemAnimation *anim = ssdlg->current_anim;
+	GdkPixbufAnimation *ganim = GDK_PIXBUF_ANIMATION(anim);
+	const char *format_opt, *width_opt, *height_opt;
+	gboolean is_static;
+	int width, height;
+	GdkColor fg, bg;
+	GError *err = NULL;
+
+	g_return_val_if_fail(anim != NULL, FALSE);
+
+	is_static = gdk_pixbuf_animation_is_static_image(ganim);
+	width = gdk_pixbuf_animation_get_width(ganim);
+	height = gdk_pixbuf_animation_get_height(ganim);
+
+	gtk_color_button_get_color
+		(GTK_COLOR_BUTTON(ssdlg->foreground_color), &fg);
+	gtk_color_button_get_color
+		(GTK_COLOR_BUTTON(ssdlg->background_color), &bg);
+
+	tilem_config_get("screenshot",
+	                 "directory/f", &dir,
+	                 "static_format/s", &format,
+	                 NULL);
+
+	if (!dir)
+		dir = g_get_current_dir();
+
+	if (!is_static) {
+		g_free(format);
+		format = g_strdup("gif");
+	}
+	else if (!format) {
+		format = g_strdup(DEFAULT_FORMAT);
+	}
+
+	filename = find_free_filename(dir, "screenshot", format);
+	basename = (filename ? g_filename_display_basename(filename) : NULL);
+	g_free(filename);
+	g_free(format);
+
+	if (!is_static) {
+		filename = prompt_save_file("Save Screenshot",
+		                            GTK_WINDOW(ssdlg->window),
+		                            basename, dir,
+		                            "GIF images", "*.gif",
+		                            "All files", "*",
+		                            NULL);
+	}
+	else {
+		/* FIXME: perhaps check the list of supported output
+		   formats (gdk_pixbuf_get_formats()) - e.g., tiff is
+		   usually supported, although it requires libtiff
+		   installed (png and jpeg also require external
+		   libraries, but we need those libraries anyway for
+		   other reasons) */
+		filename = prompt_save_file("Save Screenshot",
+		                            GTK_WINDOW(ssdlg->window),
+		                            basename, dir,
+		                            "PNG images", "*.png",
+		                            "GIF images", "*.gif",
+		                            "BMP images", "*.bmp",
+		                            "JPEG images", "*.jpg;*.jpe;*.jpeg",
+		                            "All files", "*",
+		                            NULL);
+	}
+
+	g_free(basename);
+	g_free(dir);
+
+	if (!filename)
+		return FALSE;
+
+	if (!is_static) {
+		format = g_strdup("gif");
+	}
+	else {
+		basename = g_path_get_basename(filename);
+		format = strrchr(basename, '.');
+		if (!format) {
+			messagebox00(ssdlg->window, GTK_MESSAGE_ERROR,
+			             "Unable to save screenshot",
+			             "File name does not have a"
+			             " recognized suffix");
+			g_free(filename);
+			g_free(basename);
+			return FALSE;
+		}
+		else {
+			format = g_strdup(format + 1);
+		}
+	}
+
+	tilem_animation_save(anim, filename, format, NULL, NULL, &err);
+
+	dir = g_path_get_dirname(filename);
+
+	if (err) {
+		messagebox01(ssdlg->window, GTK_MESSAGE_ERROR,
+		             "Unable to save screenshot",
+		             "%s", err->message);
+		g_error_free(err);
+		g_free(dir);
+		g_free(filename);
+		g_free(format);
+		return FALSE;
+	}
+
+	if (is_static)
+		format_opt = "static_format/s";
+	else
+		format_opt = NULL;
+
+	if (is_wide_screen(ssdlg->emu)) {
+		width_opt = "width_128x64/i";
+		height_opt = "height_128x64/i";
+	}
+	else {
+		width_opt = "width_96x64/i";
+		height_opt = "height_96x64/i";
+	}
+
+	tilem_config_set("screenshot",
+	                 "directory/f", dir,
+	                 "grayscale/b", ssdlg->current_anim_grayscale,
+	                 "foreground/c", &fg,
+	                 "background/c", &bg,
+	                 width_opt, width,
+	                 height_opt, height,
+	                 format_opt, format,
+	                 NULL);
+
+	g_free(dir);
+	g_free(filename);
+	g_free(format);
+	return TRUE;
+}
+
+/* Callback for record button */
+static void begin_animation(G_GNUC_UNUSED GtkButton *btn,
+                            TilemScreenshotDialog *ssdlg)
+{
+	gboolean grayscale = gtk_toggle_button_get_active
+		(GTK_TOGGLE_BUTTON(ssdlg->grayscale_tb));
+
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->animation_speed), FALSE);
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->screenshot), FALSE);
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->record), FALSE);
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->stop), TRUE);
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(ssdlg->window),
+	                                  GTK_RESPONSE_ACCEPT, FALSE);
+
+	tilem_calc_emulator_begin_animation(ssdlg->emu, grayscale);
+	ssdlg->current_anim_grayscale = grayscale;
+
+	/* You can choose to hide current animation while recording or not
+	   It's as you prefer... For the moment I hide it */
+	/*gtk_widget_hide(GTK_WIDGET(ssdlg->screenshot_preview_image)); */
+
+	//set_current_animation(ssdlg, NULL);
+}
+
+/* Callback for stop button (stop the recording) */
+static void end_animation(G_GNUC_UNUSED GtkButton *btn,
+                          TilemScreenshotDialog *ssdlg)
+{
+	TilemAnimation *anim;
+
+	if (ssdlg->emu->anim) {
+		anim = tilem_calc_emulator_end_animation(ssdlg->emu);
+		set_current_animation(ssdlg, anim);
+		g_object_unref(anim);
+	}
+	else {
+		set_current_animation(ssdlg, NULL);
+	}
+
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->animation_speed), TRUE);
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->screenshot), TRUE);
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->record), TRUE);
+	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->stop), FALSE);
+}
+
+/* Callback for screenshot button (take a screenshot) */
+static void grab_screen(G_GNUC_UNUSED GtkButton *btn,
+                        TilemScreenshotDialog *ssdlg)
+{
+	TilemAnimation *anim;
+		
+	gboolean grayscale = gtk_toggle_button_get_active
+		(GTK_TOGGLE_BUTTON(ssdlg->grayscale_tb));
+
+	anim = tilem_calc_emulator_get_screenshot(ssdlg->emu, grayscale);
+	ssdlg->current_anim_grayscale = grayscale;
+	set_current_animation(ssdlg, anim);
+	g_object_unref(anim);
+}
+
diff --git a/tool/tilem-src/gui/sendfile.c b/tool/tilem-src/gui/sendfile.c
new file mode 100644
index 0000000..13eb32d
--- /dev/null
+++ b/tool/tilem-src/gui/sendfile.c
@@ -0,0 +1,526 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "emucore.h"
+#include "filedlg.h"
+#include "ti81prg.h"
+
+
+/* Send a series of files */
+static void send_files(TilemCalcEmulator *emu, char **filenames, int *slots)
+{
+	int i;
+
+	for (i = 0; filenames[i]; i++) {
+		tilem_link_send_file(emu, filenames[i],
+		                     slots ? slots[i] : -1,
+		                     (i == 0),
+		                     (filenames[i + 1] == NULL));
+
+		/* FIXME: macros should record slot numbers */
+		if (emu->isMacroRecording)
+			tilem_macro_add_action(emu->macro, 1, filenames[i]);
+	}
+}
+
+static int string_to_slot(const char *str)
+{
+	if (!g_ascii_strncasecmp(str, "prgm", 4))
+		str += 4;
+	else if (!g_ascii_strncasecmp(str, "ti81_", 5))
+		str += 5;
+	else
+		return TI81_SLOT_AUTO;
+
+	if (g_ascii_isdigit(str[0]) && !g_ascii_isalnum(str[1]))
+		return TI81_SLOT_0 + str[0] - '0';
+	else if (g_ascii_isalpha(str[0]) && !g_ascii_isalnum(str[1]))
+		return TI81_SLOT_A + g_ascii_toupper(str[0]) - 'A';
+	else if (str[0] == '@'
+	         || !g_ascii_strncasecmp(str, "theta", 5)
+	         || !strncmp(str, "\316\270", 2)
+	         || !strncmp(str, "\316\230", 2))
+		return TI81_SLOT_THETA;
+	else
+		return TI81_SLOT_AUTO;
+}
+
+/* Guess program slot for a filename */
+static int guess_slot(const char *filename)
+{
+	char *base;
+	int slot;
+	base = g_filename_display_basename(filename);
+	slot = string_to_slot(base);
+	g_free(base);
+	return slot;
+}
+
+static int display_index_to_slot(int i)
+{
+	if (i < 9)
+		return i + 1;
+	else if (i == 9)
+		return 0;
+	else
+		return i;
+}
+
+struct slotdialog {
+	int nfiles;
+	char **filenames;
+	int *slots;
+	TI81ProgInfo info[TI81_SLOT_MAX + 1];
+	GtkTreeModel *prgm_model;
+	GtkTreeModel *slot_model;
+};
+
+static void slot_edited(G_GNUC_UNUSED GtkCellRendererText *cell,
+                        gchar *pathstr, gchar *text, gpointer data)
+{
+	struct slotdialog *slotdlg = data;
+	GtkTreeIter iter;
+	int n, slot;
+	char *end;
+
+	slot = string_to_slot(text);
+	if (slot < 0)
+		return;
+
+	n = strtol(pathstr, &end, 10);
+	gtk_tree_model_iter_nth_child(slotdlg->prgm_model, &iter, NULL, n);
+	gtk_list_store_set(GTK_LIST_STORE(slotdlg->prgm_model),
+	                   &iter, 1, text, -1);
+
+	slotdlg->slots[n] = slot;
+}
+
+/* Prompt user to assign program slots to filenames */
+static void prompt_program_slots(TilemCalcEmulator *emu,
+                                 struct slotdialog *slotdlg)
+{
+	GtkWidget *parent, *dlg, *vbox, *vbox2, *sw, *tv, *lbl;
+	GtkListStore *prgmstore, *slotstore;
+	GtkTreeIter iter;
+	GtkCellRenderer *cell;
+	GtkTreeViewColumn *col;
+	int i, j, slot;
+	int used[TI81_SLOT_MAX + 1];
+	char *slotstr, *namestr;
+	char *slotlabel[TI81_SLOT_MAX + 1];
+
+	if (emu->ewin)
+		parent = emu->ewin->window;
+	else
+		parent = NULL;
+
+	/* Generate list of existing programs */
+
+	slotstore = gtk_list_store_new(1, G_TYPE_STRING);
+	slotdlg->slot_model = GTK_TREE_MODEL(slotstore);
+
+	for (i = 0; i <= TI81_SLOT_MAX; i++) {
+		slot = display_index_to_slot(i);
+		slotstr = ti81_program_slot_to_string(slot);
+		namestr = ti81_program_name_to_string(slotdlg->info[slot].name);
+	
+		if (slotdlg->info[slot].size == 0) {
+			slotlabel[slot] = g_strdup(slotstr);
+			used[slot] = 0;
+		}
+		else if (namestr && namestr[0]) {
+			slotlabel[slot] = g_strdup_printf("%s (in use: %s)",
+			                                  slotstr, namestr);
+			used[slot] = 1;
+		}
+		else {
+			slotlabel[slot] = g_strdup_printf("%s (in use)", slotstr);
+			used[slot] = 1;
+		}
+
+		gtk_list_store_append(slotstore, &iter);
+		gtk_list_store_set(slotstore, &iter, 0, slotlabel[slot], -1);
+		g_free(slotstr);
+		g_free(namestr);
+	}
+
+	/* Assign default slots to files */
+
+	for (i = 0; i < slotdlg->nfiles; i++) {
+		slot = guess_slot(slotdlg->filenames[i]);
+		if (slotdlg->slots[i] < 0)
+			slotdlg->slots[i] = slot;
+		if (slot >= 0)
+			used[slot] = 1;
+	}
+
+	for (i = 0; i < slotdlg->nfiles; i++) {
+		if (slotdlg->slots[i] < 0) {
+			for (j = 0; j <= TI81_SLOT_MAX; j++) {
+				slot = display_index_to_slot(j);
+				if (!used[slot]) {
+					slotdlg->slots[i] = slot;
+					used[slot] = 1;
+					break;
+				}
+			}
+		}
+
+		if (slotdlg->slots[i] < 0)
+			slotdlg->slots[i] = TI81_SLOT_1;
+	}
+
+	/* Generate list of filenames and assigned slots */
+
+	prgmstore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+	slotdlg->prgm_model = GTK_TREE_MODEL(prgmstore);
+
+	for (i = 0; i < slotdlg->nfiles; i++) {
+		namestr = g_filename_display_basename(slotdlg->filenames[i]);
+		slot = slotdlg->slots[i];
+
+		gtk_list_store_append(prgmstore, &iter);
+		gtk_list_store_set(prgmstore, &iter,
+		                   0, namestr,
+		                   1, slotlabel[slot],
+		                   -1);
+		g_free(namestr);
+	}
+
+	for (i = 0; i <= TI81_SLOT_MAX; i++)
+		g_free(slotlabel[i]);
+
+	/* Create tree view */
+
+	tv = gtk_tree_view_new_with_model(slotdlg->prgm_model);
+	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), TRUE);
+
+	cell = gtk_cell_renderer_text_new();
+	col = gtk_tree_view_column_new_with_attributes
+		("File", cell, "text", 0, NULL);
+	gtk_tree_view_column_set_expand(col, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tv), col);
+
+	cell = gtk_cell_renderer_combo_new();
+	g_object_set(cell, "model", slotstore, "text-column", 0,
+	             "editable", TRUE, "has-entry", FALSE, NULL);
+	col = gtk_tree_view_column_new_with_attributes
+		("Slot", cell, "text", 1, NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tv), col);
+
+	g_signal_connect(cell, "edited", G_CALLBACK(slot_edited), slotdlg);
+
+	/* Create dialog */
+
+	dlg = gtk_dialog_new_with_buttons("Select Program Slots",
+	                                  GTK_WINDOW(parent), GTK_DIALOG_MODAL,
+	                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                                  GTK_STOCK_OK, GTK_RESPONSE_OK,
+	                                  NULL);
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
+	                                        GTK_RESPONSE_OK,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+	gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
+
+	gtk_window_set_default_size(GTK_WINDOW(dlg), -1, 250);
+
+	sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+	                               GTK_POLICY_NEVER,
+	                               GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+	                                    GTK_SHADOW_IN);
+	gtk_container_add(GTK_CONTAINER(sw), tv);
+
+	vbox = gtk_vbox_new(FALSE, 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+
+	lbl = gtk_label_new("Select a slot where each program should be"
+	                    " loaded.  If a program slot is already in use,"
+	                    " its contents will be overwritten.");
+	gtk_misc_set_alignment(GTK_MISC(lbl), 0.0, 0.0);
+	gtk_label_set_line_wrap(GTK_LABEL(lbl), TRUE);
+	gtk_label_set_width_chars(GTK_LABEL(lbl), 45);
+	gtk_box_pack_start(GTK_BOX(vbox), lbl, FALSE, FALSE, 0);
+
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	gtk_widget_show_all(vbox);
+
+	vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
+	gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
+
+	if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK)
+		send_files(emu, slotdlg->filenames, slotdlg->slots);
+
+	gtk_widget_destroy(dlg);
+}
+
+/* Check status of existing programs */
+static gboolean check_prog_slots_main(TilemCalcEmulator *emu, gpointer data)
+{
+	struct slotdialog *slotdlg = data;
+	int i;
+
+	tilem_em_wake_up(emu, TRUE);
+	for (i = 0; i <= TI81_SLOT_MAX; i++)
+		ti81_get_program_info(emu->calc, i, &slotdlg->info[i]);
+
+	return TRUE;
+}
+
+static void check_prog_slots_finished(TilemCalcEmulator *emu, gpointer data,
+                                      gboolean cancelled)
+{
+	struct slotdialog *slotdlg = data;
+
+	if (!cancelled)
+		prompt_program_slots(emu, slotdlg);
+
+	g_free(slotdlg->slots);
+	g_strfreev(slotdlg->filenames);
+	g_slice_free(struct slotdialog, slotdlg);
+}
+
+
+#define PAT_TI81       "*.prg"
+#define PAT_TI73       "*.73?"
+#define PAT_TI73_NUM   "*.73n;*.73l;*.73m;*.73i"
+#define PAT_TI82       "*.82?"
+#define PAT_TI82_NUM   "*.82n;*.82l;*.82m;*.82i"
+#define PAT_TI82_TEXT  "*.82s;*.82y;*.82p"
+#define PAT_TI83       "*.83?"
+#define PAT_TI83_NUM   "*.83n;*.83l;*.83m;*.83i"
+#define PAT_TI83_TEXT  "*.83s;*.83y;*.83p"
+#define PAT_TI83P      "*.8x?;*.8xgrp"
+#define PAT_TI83P_NUM  "*.8xn;*.8xl;*.8xm;*.8xi"
+#define PAT_TI83P_TEXT "*.8xs;*.8xy;*.8xp"
+#define PAT_TI85       "*.85?"
+#define PAT_TI86       "*.86?"
+#define PAT_TIG        "*.tig"
+
+#define FLT_TI81       "TI-81 programs", PAT_TI81
+#define FLT_TI73       "TI-73 files", PAT_TI73
+#define FLT_TI82       "TI-82 files", PAT_TI82
+#define FLT_TI83       "TI-83 files", PAT_TI83
+#define FLT_TI83P      "TI-83 Plus files", PAT_TI83P
+#define FLT_TI85       "TI-85 files", PAT_TI85
+#define FLT_TI86       "TI-86 files", PAT_TI86
+#define FLT_TIG        "TIGroup files", PAT_TIG
+#define FLT_ALL        "All files", "*"
+
+#define DESC_COMPAT "All compatible files"
+
+#define FLT_TI73_COMPAT    DESC_COMPAT, (PAT_TI73 ";" PAT_TIG ";" \
+                                         PAT_TI82_NUM ";" \
+                                         PAT_TI83_NUM ";" \
+                                         PAT_TI83P_NUM)
+
+#define FLT_TI82_COMPAT    DESC_COMPAT, (PAT_TI82 ";" PAT_TIG ";" \
+                                         PAT_TI83_TEXT ";" PAT_TI83_NUM ";" \
+                                         PAT_TI83P_TEXT ";" PAT_TI83P_NUM ";" \
+                                         PAT_TI73_NUM)
+
+#define FLT_TI83_COMPAT    DESC_COMPAT, (PAT_TI83 ";" PAT_TIG ";" \
+                                         PAT_TI82_TEXT ";" PAT_TI82_NUM ";" \
+                                         PAT_TI83P_TEXT ";" PAT_TI83P_NUM ";" \
+                                         PAT_TI73_NUM)
+
+#define FLT_TI83P_COMPAT   DESC_COMPAT, (PAT_TI83P ";" PAT_TIG ";" \
+                                         PAT_TI82_TEXT ";" PAT_TI82_NUM ";" \
+                                         PAT_TI83_TEXT ";" PAT_TI83_NUM ";" \
+                                         PAT_TI73_NUM)
+
+#define FLT_TI8586_COMPAT  DESC_COMPAT, (PAT_TI85 ";" PAT_TI86 ";" PAT_TIG)
+
+static char ** prompt_link_files(const char *title,
+                                 GtkWindow *parent,
+                                 const char *dir,
+                                 int model)
+{
+	switch (model) {
+	case TILEM_CALC_TI73:
+		return prompt_open_files(title, parent, dir,
+		                         FLT_TI73_COMPAT, FLT_TI73,
+		                         FLT_TI82, FLT_TI83, FLT_TI83P,
+		                         FLT_TIG, FLT_ALL, NULL);
+	case TILEM_CALC_TI81:
+		return prompt_open_files(title, parent, dir,
+		                         FLT_TI81, FLT_ALL, NULL);
+	case TILEM_CALC_TI82:
+		return prompt_open_files(title, parent, dir,
+		                         FLT_TI82_COMPAT, FLT_TI73,
+		                         FLT_TI82, FLT_TI83, FLT_TI83P,
+		                         FLT_TIG, FLT_ALL, NULL);
+	case TILEM_CALC_TI83:
+	case TILEM_CALC_TI76:
+		return prompt_open_files(title, parent, dir,
+		                         FLT_TI83_COMPAT, FLT_TI73,
+		                         FLT_TI82, FLT_TI83, FLT_TI83P,
+		                         FLT_TIG, FLT_ALL, NULL);
+	case TILEM_CALC_TI83P:
+	case TILEM_CALC_TI83P_SE:
+	case TILEM_CALC_TI84P:
+	case TILEM_CALC_TI84P_SE:
+	case TILEM_CALC_TI84P_NSPIRE:
+		return prompt_open_files(title, parent, dir,
+		                         FLT_TI83P_COMPAT, FLT_TI73,
+		                         FLT_TI82, FLT_TI83, FLT_TI83P,
+		                         FLT_TIG, FLT_ALL, NULL);
+	case TILEM_CALC_TI85:
+	case TILEM_CALC_TI86:
+		return prompt_open_files(title, parent, dir,
+		                         FLT_TI8586_COMPAT, FLT_TI85,
+		                         FLT_TI86, FLT_TIG, FLT_ALL, NULL);
+	default:
+		return prompt_open_files(title, parent, dir, FLT_ALL, NULL);
+	}
+}
+
+/* Load a list of files through the GUI.  The list of filenames must
+   end with NULL. */
+void load_files(TilemEmulatorWindow *ewin, char **filenames)
+{
+	struct slotdialog *slotdlg;
+	int i;
+
+	g_return_if_fail(ewin->emu->calc != NULL);
+
+	if (ewin->emu->calc->hw.model_id == TILEM_CALC_TI81) {
+		slotdlg = g_slice_new0(struct slotdialog);
+		slotdlg->filenames = g_strdupv(filenames);
+		slotdlg->nfiles = g_strv_length(filenames);
+		slotdlg->slots = g_new(int, slotdlg->nfiles);
+		for (i = 0; i < slotdlg->nfiles; i++)
+			slotdlg->slots[i] = TI81_SLOT_AUTO;
+		tilem_calc_emulator_begin(ewin->emu, &check_prog_slots_main,
+		                          &check_prog_slots_finished, slotdlg);
+	}
+	else {
+		send_files(ewin->emu, filenames, NULL);
+	}
+}
+
+static int get_cmdline_slot(const char *str, const char **name)
+{
+	char *e;
+	int n;
+
+	n = strtol(str, &e, 10);
+	if (*e == '=') {
+		*name = e + 1;
+		return n;
+	}
+
+	if (g_ascii_isalpha(str[0]) && str[1] == '=') {
+		*name = str + 2;
+		return TI81_SLOT_A + g_ascii_toupper(str[0]) - 'A';
+	}
+
+	if (str[0] == '@' && str[1] == '=') {
+		*name = str + 2;
+		return TI81_SLOT_THETA;
+	}
+
+	if (!g_ascii_strncasecmp(str, "theta=", 6)) {
+		*name = str + 6;
+		return TI81_SLOT_THETA;
+	}
+
+	*name = str;
+	return TI81_SLOT_AUTO;
+}
+
+/* Load a list of files from the command line.  Filenames may begin
+   with an optional slot designation. */
+void load_files_cmdline(TilemEmulatorWindow *ewin, char **filenames)
+{
+	struct slotdialog *slotdlg;
+	int i;
+	gboolean need_prompt = FALSE;
+	const char *name;
+
+	g_return_if_fail(ewin->emu->calc != NULL);
+
+	slotdlg = g_slice_new0(struct slotdialog);
+	slotdlg->nfiles = g_strv_length(filenames);
+	slotdlg->slots = g_new(int, slotdlg->nfiles);
+	slotdlg->filenames = g_new0(char *, slotdlg->nfiles + 1);
+
+	for (i = 0; i < slotdlg->nfiles; i++) {
+		slotdlg->slots[i] = get_cmdline_slot(filenames[i], &name);
+		slotdlg->filenames[i] = g_strdup(name);
+
+		if (slotdlg->slots[i] < 0)
+			need_prompt = TRUE;
+	}
+
+	if (need_prompt && ewin->emu->calc->hw.model_id == TILEM_CALC_TI81) {
+		tilem_calc_emulator_begin(ewin->emu, &check_prog_slots_main,
+		                          &check_prog_slots_finished, slotdlg);
+	}
+	else {
+		send_files(ewin->emu, slotdlg->filenames, slotdlg->slots);
+		g_free(slotdlg->slots);
+		g_strfreev(slotdlg->filenames);
+		g_slice_free(struct slotdialog, slotdlg);
+	}
+}
+
+/* Prompt user to load a file */
+void load_file_dialog(TilemEmulatorWindow *ewin)
+{
+	char **filenames, *dir;
+
+	tilem_config_get("upload",
+	                 "sendfile_recentdir/f", &dir,
+	                 NULL);
+
+	filenames = prompt_link_files("Send File",
+	                              GTK_WINDOW(ewin->window),
+	                              dir, ewin->emu->calc->hw.model_id);
+	g_free(dir);
+
+	if (!filenames || !filenames[0]) {
+		g_free(filenames);
+		return;
+	}
+
+	dir = g_path_get_dirname(filenames[0]);
+	tilem_config_set("upload",
+	                 "sendfile_recentdir/f", dir,
+	                 NULL);
+	g_free(dir);
+
+	load_files(ewin, filenames);
+	g_strfreev(filenames);
+}
diff --git a/tool/tilem-src/gui/skinops.c b/tool/tilem-src/gui/skinops.c
new file mode 100644
index 0000000..6a23acc
--- /dev/null
+++ b/tool/tilem-src/gui/skinops.c
@@ -0,0 +1,299 @@
+/*
+ *   skinedit - a skin editor for the TiEmu emulator
+ *   Copyright (C) 2002 Julien BLACHE <jb@tilp.info>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+contra-sh :
+   This file is a (quasi) perfect copy of the tiemu skinops.c file ...
+   Thank's to rom's and JB for this wonderful work.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <math.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include "skinops.h"
+
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+
+#define SKIN_ERROR g_quark_from_static_string("skin-error")
+enum {
+	SKIN_ERROR_INVALID
+};
+
+/*
+	Determine skin type
+*/
+int skin_get_type(SKIN_INFOS *si, const char *filename)
+{
+	FILE *fp;
+	char str[17];
+
+	fp = g_fopen(filename, "rb");
+	if (fp == NULL) {
+		fprintf(stderr, "Unable to open this file: <%s>\n", filename);
+		return -1;
+	}
+
+	memset(str, 0, sizeof(str));
+	fread(str, 16, 1, fp);
+
+	if(!strncmp(str, "TiEmu v2.00", 16))
+		si->type = SKIN_TYPE_TIEMU;
+	else if(!strncmp(str, "TilEm v2.00 ", 16))
+		si->type = SKIN_TYPE_TIEMU;
+	else if(!strncmp(str, "VTIv2.1 ", 8))
+		si->type = SKIN_TYPE_OLD_VTI;
+	else if(!strncmp(str, "VTIv2.5 ", 8))
+		si->type = SKIN_TYPE_VTI;
+	else {
+		fprintf(stderr, "Bad skin format\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+  Read TilEm skin informations (header)
+*/
+int skin_read_header(SKIN_INFOS *si, FILE *fp)
+{
+	int i;
+	uint32_t endian;
+	uint32_t jpeg_offset;
+	uint32_t length;
+	char str[17];
+
+	/* signature & offsets */
+	fread(str, 16, 1, fp);
+	if ((strncmp(str, "TilEm v2.00", 16))
+	    && (strncmp(str, "TiEmu v2.00", 16))) {
+		return -1;
+	}
+	fread(&endian, 4, 1, fp);
+	fread(&jpeg_offset, 4, 1, fp);
+
+	if (endian != ENDIANNESS_FLAG)
+		jpeg_offset = GUINT32_SWAP_LE_BE(jpeg_offset);
+
+	/* Skin name */
+	fread(&length, 4, 1, fp);
+	if (endian != ENDIANNESS_FLAG)
+		length = GUINT32_SWAP_LE_BE(length);
+
+	if (length > 0) {
+		si->name = (char *)malloc(length + 1);
+		if (si->name == NULL)
+			return -1;
+
+		memset(si->name, 0, length + 1);
+		fread(si->name, length, 1, fp);
+	}
+
+	/* Skin author */
+	fread(&length, 4, 1, fp);
+	if (endian != ENDIANNESS_FLAG)
+		length = GUINT32_SWAP_LE_BE(length);
+
+	if (length > 0) {
+		si->author = (char *)malloc(length + 1);
+		if (si->author == NULL)
+			return -1;
+
+		memset(si->author, 0, length + 1);
+		fread(si->author, length, 1, fp);
+	}
+
+	/* LCD colors */
+	fread(&si->colortype, 4, 1, fp);
+	fread(&si->lcd_white, 4, 1, fp);
+	fread(&si->lcd_black, 4, 1, fp);
+
+	/* Calc type */
+	fread(si->calc, 8, 1, fp);
+
+	/* LCD position */
+	fread(&si->lcd_pos.left, 4, 1, fp);
+	fread(&si->lcd_pos.top, 4, 1, fp);
+	fread(&si->lcd_pos.right, 4, 1, fp);
+	fread(&si->lcd_pos.bottom, 4, 1, fp);
+
+	/* Number of RECT struct to read */
+	fread(&length, 4, 1, fp);
+	if (endian != ENDIANNESS_FLAG)
+		length = GUINT32_SWAP_LE_BE(length);
+
+	if (length > SKIN_KEYS)
+		return -1;
+
+	for (i = 0; i < (int)length; i++) {
+		fread(&si->keys_pos[i].left, 4, 1, fp);
+		fread(&si->keys_pos[i].top, 4, 1, fp);
+		fread(&si->keys_pos[i].right, 4, 1, fp);
+		fread(&si->keys_pos[i].bottom, 4, 1, fp);
+	}
+
+	if (endian != ENDIANNESS_FLAG) {
+		si->colortype = GUINT32_SWAP_LE_BE(si->colortype);
+		si->lcd_white = GUINT32_SWAP_LE_BE(si->lcd_white);
+		si->lcd_black = GUINT32_SWAP_LE_BE(si->lcd_black);
+
+		si->lcd_pos.top = GUINT32_SWAP_LE_BE(si->lcd_pos.top);
+		si->lcd_pos.left = GUINT32_SWAP_LE_BE(si->lcd_pos.left);
+		si->lcd_pos.bottom = GUINT32_SWAP_LE_BE(si->lcd_pos.bottom);
+		si->lcd_pos.right = GUINT32_SWAP_LE_BE(si->lcd_pos.right);
+
+		for (i = 0; i < (int)length; i++) {
+			si->keys_pos[i].top = GUINT32_SWAP_LE_BE(si->keys_pos[i].top);
+			si->keys_pos[i].bottom = GUINT32_SWAP_LE_BE(si->keys_pos[i].bottom);
+			si->keys_pos[i].left = GUINT32_SWAP_LE_BE(si->keys_pos[i].left);
+			si->keys_pos[i].right = GUINT32_SWAP_LE_BE(si->keys_pos[i].right);
+		}
+	}
+
+	si->jpeg_offset = ftell(fp);
+
+	return 0;
+}
+
+/*
+  Read skin image (pure jpeg data)
+*/
+int skin_read_image(SKIN_INFOS *si, FILE *fp, GError **err)
+{
+	GdkPixbufLoader *loader;
+	gboolean result;
+	guchar *buf;
+	gsize count;
+	struct stat st;
+
+	// Extract image from skin
+	fseek(fp, si->jpeg_offset, SEEK_SET);
+	fstat(fileno(fp), &st);
+	count = st.st_size - si->jpeg_offset;
+
+	buf = g_malloc(count * sizeof(guchar));
+	count = fread(buf, sizeof(guchar), count, fp);
+
+	// Feed the pixbuf loader with our jpeg data
+	loader = gdk_pixbuf_loader_new();
+	result = gdk_pixbuf_loader_write(loader, buf, count, err);
+	g_free(buf);
+
+	if(result == FALSE) {
+		g_object_unref(loader);
+		return -1;
+	}
+
+	result = gdk_pixbuf_loader_close(loader, err);
+	if(result == FALSE) {
+		g_object_unref(loader);
+		return -1;
+	}
+
+	// and get the pixbuf
+	si->raw = gdk_pixbuf_loader_get_pixbuf(loader);
+	if(si->raw == NULL) {
+		g_set_error(err, SKIN_ERROR, SKIN_ERROR_INVALID,
+		            "Unable to load background image");
+		g_object_unref(loader);
+		return -1;
+	}
+
+	si->sx = si->sy = 1.0;
+	si->image = g_object_ref(si->raw);
+	g_object_ref(si->raw);
+
+	// Get new skin size
+	si->width = gdk_pixbuf_get_width(si->image);
+	si->height = gdk_pixbuf_get_height(si->image);
+
+	g_object_unref(loader);
+
+	return 0;
+}
+
+/* Load a skin (TilEm v2.00 only) */
+int skin_load(SKIN_INFOS *si, const char *filename, GError **err)
+{
+	FILE *fp;
+	int ret = 0, errnum;
+	char *dname;
+
+	g_return_val_if_fail(err == NULL || *err == NULL, -1);
+
+	fp = g_fopen(filename, "rb");
+	if (fp == NULL) {
+		errnum = errno;
+		dname = g_filename_display_basename(filename);
+		g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum),
+		            "Unable to open %s for reading: %s",
+		            dname, g_strerror(errnum));
+		g_free(dname);
+		return -1;
+	}
+
+	ret = skin_read_header(si, fp);
+	if (ret) {
+		fclose(fp);
+		dname = g_filename_display_basename(filename);
+		g_set_error(err, SKIN_ERROR, SKIN_ERROR_INVALID,
+		            "The file %s is not a valid skin.", dname);
+		g_free(dname);
+		return -1;
+	}
+
+	ret = skin_read_image(si, fp, err);
+
+	fclose(fp);
+
+	return ret;
+}
+
+/* Unload skin by freeing allocated memory */
+int skin_unload(SKIN_INFOS *si)
+{
+	if (si->image != NULL) {
+		g_object_unref(si->image);
+		si->image = NULL;
+	}
+
+	if (si->raw) {
+		g_object_unref(si->raw);
+		si->raw = NULL;
+	}
+
+	free(si->name);
+	free(si->author);
+
+	memset(si, 0, sizeof(SKIN_INFOS));
+
+	return 0;
+}
+
diff --git a/tool/tilem-src/gui/skinops.h b/tool/tilem-src/gui/skinops.h
new file mode 100644
index 0000000..8c5c5a6
--- /dev/null
+++ b/tool/tilem-src/gui/skinops.h
@@ -0,0 +1,133 @@
+/* Hey EMACS -*- linux-c -*- */
+/* $Id$ */
+
+/*
+ *   skinedit - a skin editor for the TiEmu emulator
+ *   Copyright (C) 2002 Julien BLACHE <jb@tilp.info>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/* 
+From Romain Lievins(?) :
+   Most of these definitions and code comes from the JB's SkinEdit
+   which is based on TiEmu skin code. TiEmu skin code is also based on
+   VTi's skin code.
+   
+contra-sh :
+   This file is a (quasi ?) perfect copy of the tiemu skinops.h file ...
+   Thank's to Romain Lievins and Julien Blache for this wonderful work.
+
+*/
+
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+
+/***************/
+/* Definitions */
+/***************/
+
+#define LCD_COLORTYPE_LOW    0
+#define LCD_COLORTYPE_HIGH   1
+#define LCD_COLORTYPE_CUSTOM 2
+
+#define LCD_HI_WHITE 0xb0ccae
+#define LCD_HI_BLACK 0x8a6f53
+
+#define LCD_LOW_WHITE 0xcfe0cc
+#define LCD_LOW_BLACK 0x222e31
+
+#define MAX_COLORS (256 - 16)		// we need to keep 16 colors for grayscales
+#define SKIN_KEYS  80
+
+#define SKIN_TI73   "TI-73"
+#define SKIN_TI82   "TI-82"
+#define SKIN_TI83   "TI-83"
+#define SKIN_TI83P  "TI-83+"
+#define SKIN_TI85   "TI-85"
+#define SKIN_TI86   "TI-86"
+#define SKIN_TI89   "TI-89"
+#define SKIN_TI92   "TI-92"
+#define SKIN_TI92P  "TI-92+"
+#define SKIN_V200   "V200PLT"
+#define SKIN_TI89T  "TI-89TM"
+
+#define SKIN_TYPE_TIEMU   10
+#define SKIN_TYPE_VTI     2
+#define SKIN_TYPE_OLD_VTI 1
+#define SKIN_TYPE_NEW     0
+
+#define ENDIANNESS_FLAG 0xfeedbabe
+#define TIEMU_SKIN_ID   "TiEmu v2.00"
+
+
+
+/*********/
+/* Types */
+/*********/
+
+
+typedef struct
+{
+  uint32_t left;
+  uint32_t top;
+  uint32_t right;
+  uint32_t bottom;
+} RECT;
+
+
+typedef struct
+{
+  int type;
+
+  GdkPixbuf *image;
+
+  int width;
+  int height;
+
+  GdkPixbuf *raw;	// raw jpeg image
+  double sx, sy;		// scaling factor
+
+  char calc[9];
+  uint32_t colortype;
+
+  uint32_t lcd_black;
+  uint32_t lcd_white;
+
+  char *name;
+  char *author;
+
+  RECT lcd_pos;
+  RECT keys_pos[SKIN_KEYS];
+
+  long	jpeg_offset;
+
+} SKIN_INFOS;
+
+/*************/
+/* Functions */
+/*************/
+
+int skin_load(SKIN_INFOS *infos, const char *filename, GError **err);
+int skin_unload(SKIN_INFOS *infos);
+
diff --git a/tool/tilem-src/gui/ti81prg.c b/tool/tilem-src/gui/ti81prg.c
new file mode 100644
index 0000000..afd407e
--- /dev/null
+++ b/tool/tilem-src/gui/ti81prg.c
@@ -0,0 +1,410 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <tilem.h>
+
+#include "ti81prg.h"
+
+#define tSpace  0x56
+#define t0      0x10
+#define t9      0x19
+#define tDecPt	0x1A
+#define tA      0x59
+#define tZ      0x72
+#define tTheta  0x73
+
+#define ramStart      0xE000
+#define cxCurApp      0xE347
+#define cxOldApp      (cxCurApp + 1)
+#define DimX_old      0xEEF9
+#define DimX_new      0xF12D
+#define prgm0Name     0xF1D3
+#define prgm0Start    0xF2FB
+#define prgm0End      (prgm0Start + 2)
+#define prgmThetaEnd  (prgm0End + 36*2)
+#define progMem       0xF347
+#define progMemEnd    0xFCA6
+
+#define cxPrgmEdit  2
+#define cxPrgmExec  10
+#define cxMenu      11
+
+TI81Program * ti81_program_new(int size)
+{
+	TI81Program *prgm = tilem_new0(TI81Program, 1);
+
+	prgm->info.slot = TI81_SLOT_AUTO;
+	memset(prgm->info.name, tSpace, 8);
+
+	if (size > 0) {
+		prgm->info.size = size;
+		prgm->data = tilem_new_atomic(byte, size);
+	}
+
+	return prgm;
+}
+
+void ti81_program_free(TI81Program *prgm)
+{
+	if (!prgm)
+		return;
+
+	if (prgm->data)
+		tilem_free(prgm->data);
+	tilem_free(prgm);
+}
+
+static byte *get_byte_ptr(const TilemCalc *calc, dword addr)
+{
+	if (addr < ramStart || addr > 0xffff)
+		return NULL;
+
+	return &calc->ram[addr - ramStart];
+}
+
+static int read_byte(const TilemCalc *calc, dword addr)
+{
+	const byte *p = get_byte_ptr(calc, addr);
+
+	if (!p)
+		return -1;
+	else
+		return *p;
+}
+
+static dword read_word(const TilemCalc *calc, dword addr)
+{
+	const byte *p = get_byte_ptr(calc, addr);
+
+	if (!p)
+		return 0;
+	else
+		return (p[0] | p[1] << 8);
+}
+
+static void write_word(TilemCalc *calc, dword addr, dword value)
+{
+	byte *p = get_byte_ptr(calc, addr);
+
+	if (p) {
+		p[0] = value & 0xff;
+		p[1] = (value >> 8) & 0xff;
+	}
+}
+
+static int check_busy(const TilemCalc *calc)
+{
+	int cur, old;
+
+	cur = read_byte(calc, cxCurApp);
+	old = read_byte(calc, cxCurApp);
+
+	if (cur == cxPrgmEdit || cur == cxPrgmExec)
+		return 1;
+	else if (cur == cxMenu && (old == cxPrgmEdit || old == cxPrgmExec))
+		return 1;
+	else
+		return 0;
+}
+
+static dword get_free_mem_end(const TilemCalc *calc)
+{
+	const byte *p;
+	int n, i;
+
+	p = get_byte_ptr(calc, DimX_new);
+
+	/* DimX is always a small positive integer, so the first byte
+	   must be between 80h and 82h, and the last five bytes must
+	   always be zero.  On 1.1K, DimX_new is part of textShadow,
+	   so none of these byte values make any sense. */
+
+	if (p[0] < 0x80 || p[0] > 0x82 || p[7] != 0)
+		p = get_byte_ptr(calc, DimX_old);
+
+	if (p[0] < 0x80 || p[0] > 0x82)
+		return 0;
+
+	for (i = 3; i < 7; i++)
+		if (p[i])
+			return 0;
+
+	n = ((p[2] & 0xf)
+	     + ((p[2] >> 4) * 10)
+	     + ((p[1] & 0xf) * 100)
+	     + ((p[1] >> 4) * 1000));
+
+	for (i = p[0]; i < 0x83; i++) {
+		if (n % 10)
+			return 0;
+		n /= 10;
+	}
+
+	return (progMemEnd + 1 - 16 * n);
+}
+
+int ti81_get_program_info(const TilemCalc *calc, int slot, TI81ProgInfo *info)
+{
+	const byte *p;
+	dword progstart, progend;
+
+	if (slot < 0 || slot > TI81_SLOT_MAX)
+		return TI81_ERR_INTERNAL;
+
+	if (check_busy(calc))
+		return TI81_ERR_BUSY;
+
+	progstart = read_word(calc, prgm0Start + 2 * slot);
+	progend = read_word(calc, prgm0Start + 2 * slot + 2);
+
+	if (progstart < ramStart || progend < ramStart || progend < progstart)
+		return TI81_ERR_BUSY;
+
+	info->slot = slot;
+	info->size = progend - progstart;
+	info->addr = progstart;
+
+	p = get_byte_ptr(calc, prgm0Name + 8 * slot);
+	if (!p) return TI81_ERR_INTERNAL;
+	memcpy(info->name, p, 8);
+
+	return 0;
+}
+
+int ti81_get_program(const TilemCalc *calc, int slot, TI81Program **prgm)
+{
+	TI81ProgInfo info;
+	const byte *p;
+	int s;
+
+	if ((s = ti81_get_program_info(calc, slot, &info))) {
+		*prgm = NULL;
+		return s;
+	}
+
+	*prgm = ti81_program_new(info.size);
+	(*prgm)->info = info;
+	if (info.size > 0 && (p = get_byte_ptr(calc, info.addr)))
+		memcpy((*prgm)->data, p, info.size);
+
+	return 0;
+}
+
+int ti81_load_program(TilemCalc *calc, const TI81Program *prgm)
+{
+	TI81ProgInfo info;
+	int slot = prgm->info.slot;
+	int s, i;
+	dword progs_start, progs_end, mem_end, x;
+	byte *p;
+
+	if (slot == TI81_SLOT_AUTO) {
+		for (slot = 0; slot <= TI81_SLOT_MAX; slot++) {
+			if ((s = ti81_get_program_info(calc, slot, &info)))
+				return s;
+			if (info.size == 0 && info.name[0] == tSpace)
+				break;
+		}
+
+		if (slot > TI81_SLOT_MAX)
+			return TI81_ERR_SLOTS_FULL;
+	}
+
+	if ((s = ti81_get_program_info(calc, slot, &info)))
+		return s;
+
+	/* move later programs forward/backward in memory */
+
+	progs_start = info.addr + info.size;
+	progs_end = read_word(calc, prgmThetaEnd);
+	if (progs_end < progs_start)
+		return TI81_ERR_BUSY;
+
+	mem_end = get_free_mem_end(calc);
+	if (progs_end + prgm->info.size - info.size > mem_end)
+		return TI81_ERR_MEMORY;
+
+	if (prgm->info.size != info.size && progs_start != progs_end) {
+		p = get_byte_ptr(calc, progs_start);
+		if (!p) return TI81_ERR_INTERNAL;
+		memmove(p + prgm->info.size - info.size, p,
+		        progs_end - progs_start);
+	}
+
+	/* update program pointers */
+
+	for (i = slot; i <= TI81_SLOT_MAX; i++) {
+		x = read_word(calc, prgm0End + 2 * i);
+		write_word(calc, prgm0End + 2 * i,
+		           x + prgm->info.size - info.size);
+	}
+
+	/* copy program data */
+
+	if (prgm->info.size != 0) {
+		p = get_byte_ptr(calc, info.addr);
+		if (!p) return TI81_ERR_INTERNAL;
+		memcpy(p, prgm->data, prgm->info.size);
+	}
+
+	/* copy program name */
+
+	p = get_byte_ptr(calc, prgm0Name + 8 * slot);
+	if (!p) return TI81_ERR_INTERNAL;
+	memcpy(p, prgm->info.name, 8);
+
+	return 0;
+}
+
+int ti81_read_prg_file(FILE *f, TI81Program **prgm)
+{
+	byte buf[20];
+	unsigned int size, i;
+	unsigned int sum = 0;
+	TI81Program *p;
+
+	*prgm = NULL;
+
+	if (fread(buf, 1, 20, f) != 20)
+		return TI81_ERR_INVALID_FILE;
+
+	if (strcmp((char *) buf, "**TI81**") || buf[9] != 0x6e)
+		return TI81_ERR_INVALID_FILE;
+
+	size = buf[10] | buf[11] << 8;
+
+	p = ti81_program_new(size);
+
+	memcpy(p->info.name, buf + 12, 8);
+
+	for (i = 0; i < 8; i++)
+		sum += buf[12 + i];
+
+	if (fread(p->data, 1, size, f) != size) {
+		ti81_program_free(p);
+		return TI81_ERR_INVALID_FILE;
+	}
+
+	for (i = 0; i < size; i++)
+		sum += p->data[i];
+
+	if (fread(buf, 1, 2, f) != 2) {
+		ti81_program_free(p);
+		return TI81_ERR_INVALID_FILE;
+	}
+
+	sum -= (buf[0] | buf[1] << 8);
+	if (sum & 0xffff)
+		fprintf(stderr, "warning: checksum incorrect\n");
+
+	*prgm = p;
+	return 0;
+}
+
+int ti81_write_prg_file(FILE *f, const TI81Program *prgm)
+{
+	byte buf[20];
+	unsigned int size, i;
+	unsigned int sum = 0;
+
+	memcpy(buf, "**TI81**\0n", 10);
+	size = prgm->info.size;
+	buf[10] = size & 0xff;
+	buf[11] = (size >> 8) & 0xff;
+
+	memcpy(buf + 12, prgm->info.name, 8);
+
+	for (i = 0; i < 8; i++)
+		sum += buf[12 + i];
+
+	if (fwrite(buf, 1, 20, f) != 20)
+		return TI81_ERR_FILE_IO;
+
+	if (fwrite(prgm->data, 1, size, f) != size)
+		return TI81_ERR_FILE_IO;
+
+	for (i = 0; i < size; i++)
+		sum += prgm->data[i];
+
+	buf[0] = sum & 0xff;
+	buf[1] = (sum >> 8) & 0xff;
+	if (fwrite(buf, 1, 2, f) != 2)
+		return TI81_ERR_FILE_IO;
+
+	return 0;
+}
+
+char * ti81_program_slot_to_string(int slot)
+{
+	char buf[50];
+	char *s;
+
+	if (slot == TI81_SLOT_AUTO)
+		strcpy(buf, "Automatic");
+	else if (slot < 0 || slot > 36)
+		strcpy(buf, "?");
+	else if (slot < 10)
+		sprintf(buf, "Prgm%c", slot + '0');
+	else if (slot < 36)
+		sprintf(buf, "Prgm%c", slot + 'A' - 10);
+	else
+		strcpy(buf, "Prgm\316\270");
+
+	s = tilem_new_atomic(char, strlen(buf) + 1);
+	strcpy(s, buf);
+	return s;
+}
+
+char * ti81_program_name_to_string(const byte *prgname)
+{
+	char buf[50];
+	char *s;
+	int i, j;
+
+	for (i = j = 0; i < 8; i++) {
+		if (prgname[i] == tSpace)
+			buf[j++] = '_';
+		else if (prgname[i] == tDecPt)
+			buf[j++] = '.';
+		else if (prgname[i] == tTheta) {
+			buf[j++] = '\316';
+			buf[j++] = '\270';
+		}
+		else if (prgname[i] >= t0 && prgname[i] <= t9)
+			buf[j++] = '0' + prgname[i] - t0;
+		else if (prgname[i] >= tA && prgname[i] <= tZ)
+			buf[j++] = 'A' + prgname[i] - tA;
+		else
+			buf[j++] = '?';
+	}
+
+	while (j > 0 && buf[j - 1] == '_')
+		j--;
+	buf[j] = 0;
+
+	s = tilem_new_atomic(char, strlen(buf) + 1);
+	strcpy(s, buf);
+	return s;
+}
diff --git a/tool/tilem-src/gui/ti81prg.h b/tool/tilem-src/gui/ti81prg.h
new file mode 100644
index 0000000..fc65224
--- /dev/null
+++ b/tool/tilem-src/gui/ti81prg.h
@@ -0,0 +1,89 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2011 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+enum {
+	TI81_SLOT_AUTO = -1,
+	TI81_SLOT_0, TI81_SLOT_1, TI81_SLOT_2, TI81_SLOT_3, TI81_SLOT_4,
+	TI81_SLOT_5, TI81_SLOT_6, TI81_SLOT_7, TI81_SLOT_8, TI81_SLOT_9,
+	TI81_SLOT_A, TI81_SLOT_B, TI81_SLOT_C, TI81_SLOT_D, TI81_SLOT_E,
+	TI81_SLOT_F, TI81_SLOT_G, TI81_SLOT_H, TI81_SLOT_I, TI81_SLOT_J,
+	TI81_SLOT_K, TI81_SLOT_L, TI81_SLOT_M, TI81_SLOT_N, TI81_SLOT_O,
+	TI81_SLOT_P, TI81_SLOT_Q, TI81_SLOT_R, TI81_SLOT_S, TI81_SLOT_T,
+	TI81_SLOT_U, TI81_SLOT_V, TI81_SLOT_W, TI81_SLOT_X, TI81_SLOT_Y,
+	TI81_SLOT_Z, TI81_SLOT_THETA
+};
+
+#define TI81_SLOT_MAX TI81_SLOT_THETA
+
+typedef struct _TI81ProgInfo {
+	int slot;               /* program slot number */
+	int size;               /* size of program contents */
+	dword addr;		/* address of program contents */
+	byte name[8];           /* program name, tokens */
+} TI81ProgInfo;
+
+typedef struct _TI81Program {
+	TI81ProgInfo info;
+	byte *data;
+} TI81Program;
+
+/* Error codes */
+enum {
+	TI81_ERR_FILE_IO = 1,       /* File I/O error */
+	TI81_ERR_INVALID_FILE,      /* PRG file is invalid */
+	TI81_ERR_MEMORY,            /* Not enough memory to load program */
+	TI81_ERR_SLOTS_FULL,        /* No free program slots */
+	TI81_ERR_BUSY,              /* Calculator is busy and unable
+	                               to load/save programs */
+	TI81_ERR_INTERNAL
+};
+
+/* Create a new TI81Program with the given size. */
+TI81Program * ti81_program_new(int size)
+	TILEM_ATTR_MALLOC;
+
+/* Free a TI81Program. */
+void ti81_program_free(TI81Program *prgm);
+
+/* Get information about the program in the given slot. */
+int ti81_get_program_info(const TilemCalc *calc, int slot, TI81ProgInfo *info);
+
+/* Retrieve a program from calculator memory.  Free the resulting
+   program with ti81_program_free() when you're done with it. */
+int ti81_get_program(const TilemCalc *calc, int slot, TI81Program **prgm);
+
+/* Load a program into calculator memory. */
+int ti81_load_program(TilemCalc *calc, const TI81Program *prgm);
+
+/* Read a program from a PRG file.  Free the resulting program with
+   ti81_program_free() when you're done with it. */
+int ti81_read_prg_file(FILE *f, TI81Program **prgm);
+
+/* Write a program to a PRG file. */
+int ti81_write_prg_file(FILE *f, const TI81Program *prgm);
+
+/* Convert program slot number into a UTF-8 string.  Free the result
+   with tilem_free() when you're done with it. */
+char * ti81_program_slot_to_string(int slot)
+	TILEM_ATTR_MALLOC;
+
+/* Convert program name to a UTF-8 string.  Free the result with
+   tilem_free() when you're done with it. */
+char * ti81_program_name_to_string(const byte *prgname)
+	TILEM_ATTR_MALLOC;
diff --git a/tool/tilem-src/gui/tilem2.c b/tool/tilem-src/gui/tilem2.c
new file mode 100644
index 0000000..4be876a
--- /dev/null
+++ b/tool/tilem-src/gui/tilem2.c
@@ -0,0 +1,309 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle
+ * Copyright (c) 2010-2012 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+#include "files.h"
+#include "icons.h"
+#include "msgbox.h"
+
+/* CMD LINE OPTIONS */
+static gchar* cl_romfile = NULL;
+static gchar* cl_skinfile = NULL;
+static gchar* cl_model = NULL;
+static gchar* cl_statefile = NULL;
+static gchar** cl_files_to_load = NULL;
+static gboolean cl_skinless_flag = FALSE;
+static gboolean cl_reset_flag = FALSE;
+static gchar* cl_getvar = NULL;
+static gchar* cl_macro_to_run = NULL;
+static gboolean cl_debug_flag = FALSE;
+static gboolean cl_normalspeed_flag = FALSE;
+static gboolean cl_fullspeed_flag = FALSE;
+
+
+static GOptionEntry entries[] =
+{
+	{ "rom", 'r', 0, G_OPTION_ARG_FILENAME, &cl_romfile, "The rom file to run", "FILE" },
+	{ "skin", 'k', 0, G_OPTION_ARG_FILENAME, &cl_skinfile, "The skin file to use", "FILE" },
+	{ "model", 'm', 0, G_OPTION_ARG_STRING, &cl_model, "The model to use", "NAME" },
+	{ "state-file", 's', 0, G_OPTION_ARG_FILENAME, &cl_statefile, "The state-file to use", "FILE" },
+	{ "without-skin", 'l', 0, G_OPTION_ARG_NONE, &cl_skinless_flag, "Start in skinless mode", NULL },
+	{ "reset", 0, 0, G_OPTION_ARG_NONE, &cl_reset_flag, "Reset the calc at startup", NULL },
+	{ "get-var", 0, 0, G_OPTION_ARG_STRING, &cl_getvar, "Get a var at startup", "FILE" },
+	{ "play-macro", 'p', 0, G_OPTION_ARG_FILENAME, &cl_macro_to_run, "Run this macro at startup", "FILE" },
+	{ "debug", 'd', 0, G_OPTION_ARG_NONE, &cl_debug_flag, "Launch debugger", NULL },
+	{ "normal-speed", 0, 0, G_OPTION_ARG_NONE, &cl_normalspeed_flag, "Run at normal speed", NULL },
+	{ "full-speed", 0, 0, G_OPTION_ARG_NONE, &cl_fullspeed_flag, "Run at maximum speed", NULL },
+	{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &cl_files_to_load, NULL, "FILE" },
+	{ 0, 0, 0, 0, 0, 0, 0 }
+};
+
+
+/* #########  MAIN  ######### */
+
+/* Order of preference for automatic model selection. */
+static const char model_search_order[] =
+	{ TILEM_CALC_TI81,
+	  TILEM_CALC_TI73,
+	  TILEM_CALC_TI82,
+	  TILEM_CALC_TI83,
+	  TILEM_CALC_TI76,
+	  TILEM_CALC_TI84P_SE,
+	  TILEM_CALC_TI84P,
+	  TILEM_CALC_TI83P_SE,
+	  TILEM_CALC_TI83P,
+	  TILEM_CALC_TI84P_NSPIRE,
+	  TILEM_CALC_TI85,
+	  TILEM_CALC_TI86, 0 };
+
+/* Check if given calc model should be used for these file types. */
+static gboolean check_file_types(int calc_model,
+                                 const int *file_models,
+                                 int nfiles)
+{
+	/* Only choose a calc model if it supports all of the given
+	   file types, and at least one of the files is of the calc's
+	   "preferred" type.  This means if we have a mixture of 82Ps
+	   and 83Ps, we can use either a TI-83 or TI-76.fr ROM image,
+	   but not a TI-83 Plus. */
+
+	gboolean preferred = FALSE;
+	int i;
+
+	calc_model = model_to_base_model(calc_model);
+
+	for (i = 0; i < nfiles; i++) {
+		if (file_models[i] == calc_model)
+			preferred = TRUE;
+		else if (!model_supports_file(calc_model, file_models[i]))
+			return FALSE;
+	}
+
+	return preferred;
+}
+
+static void load_initial_rom(TilemCalcEmulator *emu,
+                             const char *cmdline_rom_name,
+                             const char *cmdline_state_name,
+                             char **cmdline_files,
+                             int model)
+{
+	GError *err = NULL;
+	char *modelname;
+	int nfiles, *file_models, i;
+
+	/* If a ROM file is specified on the command line, use that
+	   (and no other) */
+
+	if (cmdline_rom_name) {
+		if (tilem_calc_emulator_load_state(emu, cmdline_rom_name,
+		                                   cmdline_state_name,
+		                                   model, &err))
+			return;
+		else if (!err)
+			exit(0);
+		else {
+			g_printerr("%s\n", err->message);
+			exit(1);
+		}
+	}
+
+	/* Choose model by file names */
+
+	if (!model && cmdline_files) {
+		nfiles = g_strv_length(cmdline_files);
+		file_models = g_new(int, nfiles);
+
+		/* determine model for each filename */
+		for (i = 0; i < nfiles; i++)
+			file_models[i] = file_to_model(cmdline_files[i]);
+
+		/* iterate over all known models... */
+		for (i = 0; model_search_order[i]; i++) {
+			model = model_search_order[i];
+
+			/* check if this model supports the named files */
+			if (!check_file_types(model, file_models, nfiles))
+				continue;
+
+			/* try to load model, but no error message if
+			   no ROM is present in config */
+			if (tilem_calc_emulator_load_state(emu, NULL, NULL,
+			                                   model, &err)) {
+				g_free(file_models);
+				return;
+			}
+			else if (!err)
+				exit(0);
+			else if (!g_error_matches(err, TILEM_EMULATOR_ERROR,
+			                          TILEM_EMULATOR_ERROR_NO_ROM)) {
+				messagebox01(NULL, GTK_MESSAGE_ERROR,
+				             "Unable to load calculator state",
+				             "%s", err->message);
+			}
+			g_clear_error(&err);
+		}
+
+		g_free(file_models);
+		model = 0;
+	}
+
+	/* If no model specified on command line (either explicitly or
+	   implicitly), then choose the most recently used model */
+
+	if (!model && !cmdline_files) {
+		tilem_config_get("recent", "last_model/s", &modelname, NULL);
+		if (modelname)
+			model = name_to_model(modelname);
+	}
+
+	/* Try to load the most recently used ROM for chosen model */
+
+	if (model) {
+		if (tilem_calc_emulator_load_state(emu, NULL, NULL,
+		                                   model, &err))
+			return;
+		else if (!err)
+			exit(0);
+		else {
+			messagebox01(NULL, GTK_MESSAGE_ERROR,
+			             "Unable to load calculator state",
+			             "%s", err->message);
+			g_clear_error(&err);
+		}
+	}
+
+	/* Prompt user for a ROM file */
+
+	while (!emu->calc) {
+		if (!tilem_calc_emulator_prompt_open_rom(emu))
+			exit(0);
+	}
+}
+
+int main(int argc, char **argv)
+{
+	TilemCalcEmulator* emu;
+	char *menurc_path;
+	GOptionContext *context;
+	GError *error = NULL;
+	int model = 0;
+
+	g_thread_init(NULL);
+	gtk_init(&argc, &argv);
+	set_program_path(argv[0]);
+	g_set_application_name("TilEm");
+
+	menurc_path = get_shared_file_path("menurc", NULL);
+	if (menurc_path)
+		gtk_accel_map_load(menurc_path);
+	g_free(menurc_path);
+
+	init_custom_icons();
+	gtk_window_set_default_icon_name("tilem");
+
+	emu = tilem_calc_emulator_new();
+
+	context = g_option_context_new(NULL);
+	g_option_context_add_main_entries(context, entries, NULL);
+	g_option_context_add_group(context, gtk_get_option_group(TRUE));
+	if (!g_option_context_parse(context, &argc, &argv, &error))
+	{
+		g_printerr("%s: %s\n", g_get_prgname(), error->message);
+		exit (1);
+	}
+
+	if (cl_model) {
+		model = name_to_model(cl_model);
+		if (!model) {
+			g_printerr("%s: unknown model %s\n",
+			           g_get_prgname(), cl_model);
+			return 1;
+		}
+	}
+
+	load_initial_rom(emu, cl_romfile, cl_statefile, cl_files_to_load, model);
+
+	emu->ewin = tilem_emulator_window_new(emu);
+
+	if (cl_skinless_flag)
+		tilem_emulator_window_set_skin_disabled(emu->ewin, TRUE);
+	else if (cl_skinfile) {
+		tilem_emulator_window_set_skin(emu->ewin, cl_skinfile);
+		tilem_emulator_window_set_skin_disabled(emu->ewin, FALSE);
+	}
+
+	gtk_widget_show(emu->ewin->window);
+
+	ticables_library_init();
+	tifiles_library_init();
+	ticalcs_library_init();
+
+	if (cl_reset_flag)
+		tilem_calc_emulator_reset(emu);
+
+	if (cl_fullspeed_flag)
+		tilem_calc_emulator_set_limit_speed(emu, FALSE);
+	else if (cl_normalspeed_flag)
+		tilem_calc_emulator_set_limit_speed(emu, TRUE);
+
+	if (cl_files_to_load)
+		load_files_cmdline(emu->ewin, cl_files_to_load);
+	if (cl_macro_to_run)
+		tilem_macro_load(emu, cl_macro_to_run);
+	if (cl_getvar)
+		tilem_link_receive_matching(emu, cl_getvar, ".");
+
+	if (cl_debug_flag)
+		launch_debugger(emu->ewin);
+	else
+		tilem_calc_emulator_run(emu);
+
+	g_signal_connect(emu->ewin->window, "destroy",
+	                 G_CALLBACK(gtk_main_quit), NULL);
+
+	gtk_main();
+
+	tilem_calc_emulator_pause(emu);
+
+	tilem_emulator_window_free(emu->ewin);
+	tilem_calc_emulator_free(emu);
+
+	menurc_path = get_config_file_path("menurc", NULL);
+	gtk_accel_map_save(menurc_path);
+	g_free(menurc_path);
+
+	ticables_library_exit();
+	tifiles_library_exit();
+	ticalcs_library_exit();
+
+	return 0;
+}
diff --git a/tool/tilem-src/gui/tilem2.ico b/tool/tilem-src/gui/tilem2.ico
new file mode 100644
index 0000000..633d49c
Binary files /dev/null and b/tool/tilem-src/gui/tilem2.ico differ
diff --git a/tool/tilem-src/gui/tilem2.rc.in b/tool/tilem-src/gui/tilem2.rc.in
new file mode 100644
index 0000000..7e451ad
--- /dev/null
+++ b/tool/tilem-src/gui/tilem2.rc.in
@@ -0,0 +1,32 @@
+#include <winver.h>
+
+101 ICON "@srcdir@/tilem2.ico"
+
+VS_VERSION_INFO VERSIONINFO
+    FILEVERSION     BUILD_VERSION
+    PRODUCTVERSION  BUILD_VERSION
+    FILEFLAGSMASK   0
+    FILEFLAGS       0
+    FILEOS          VOS__WINDOWS32
+    FILETYPE        VFT_APP
+    BEGIN
+        BLOCK "StringFileInfo"
+        BEGIN
+            BLOCK "040904E4"
+            BEGIN
+                VALUE "CompanyName",      "Linux Programmer Group"
+                VALUE "FileDescription",  "TilEm"
+                VALUE "FileVersion",      "@PACKAGE_VERSION@"
+                VALUE "InternalName",     "tilem2"
+                VALUE "LegalCopyright",   "Copyright \xa9 2012 The TilEm Project"
+                VALUE "OriginalFilename", "tilem2.exe"
+                VALUE "ProductName",      "@PACKAGE_NAME@"
+                VALUE "ProductVersion",   "@PACKAGE_VERSION@"
+                VALUE "Comments",         "Licensed under the GNU GPLv3+ (see http://www.gnu.org/licenses/.)"
+            END
+        END
+        BLOCK "VarFileInfo"
+        BEGIN
+            VALUE "Translation", 0x0409, 1252
+        END
+    END
diff --git a/tool/tilem-src/gui/tool.c b/tool/tilem-src/gui/tool.c
new file mode 100644
index 0000000..417df05
--- /dev/null
+++ b/tool/tilem-src/gui/tool.c
@@ -0,0 +1,433 @@
+/*
+ * TilEm II
+ *
+ * Copyright (c) 2010-2011 Thibault Duponchelle 
+ * Copyright (c) 2010 Benjamin Moody
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <ticalcs.h>
+#include <tilem.h>
+
+#include "gui.h"
+
+/* Create a frame around the given widget, with a boldface label in
+   the GNOME style */
+GtkWidget* new_frame(const gchar* label, GtkWidget* contents)
+{
+	GtkWidget *frame, *align;
+	char *str;
+
+	str = g_strconcat("<b>", label, "</b>", NULL);
+	frame = gtk_frame_new(str);
+	g_free(str);
+
+	g_object_set(gtk_frame_get_label_widget(GTK_FRAME(frame)),
+	             "use-markup", TRUE, NULL);
+	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+
+	align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 6, 0, 12, 0);
+	gtk_widget_show(align);
+	gtk_container_add(GTK_CONTAINER(frame), align);
+	gtk_container_add(GTK_CONTAINER(align), contents);
+	gtk_widget_show(frame);
+
+	return frame;
+}
+
+/* Get model name (abbreviation) for a TilEm model ID. */
+const char * model_to_name(int model)
+{
+	const TilemHardware **models;
+	int nmodels, i;
+
+	tilem_get_supported_hardware(&models, &nmodels);
+	for (i = 0; i < nmodels; i++)
+		if (models[i]->model_id == model)
+			return models[i]->name;
+
+	return NULL;
+}
+
+/* Convert model name to a model ID. */
+int name_to_model(const char *name)
+{
+	char *s;
+	const TilemHardware **models;
+	int nmodels, i, j;
+
+	s = g_new(char, strlen(name) + 1);
+	for (i = j = 0; name[i]; i++) {
+		if (name[i] == '+')
+			s[j++] = 'p';
+		else if (name[i] != '-')
+			s[j++] = g_ascii_tolower(name[i]);
+	}
+	s[j] = 0;
+
+	tilem_get_supported_hardware(&models, &nmodels);
+	for (i = 0; i < nmodels; i++) {
+		if (!strcmp(s, models[i]->name)) {
+			g_free(s);
+			return models[i]->model_id;
+		}
+	}
+
+	g_free(s);
+	return 0;
+}
+
+/* Convert TilEm model ID to tifiles2 model ID. */
+CalcModel model_to_calcmodel(int model)
+{
+	switch (model) {
+	case TILEM_CALC_TI73:
+		return CALC_TI73;
+
+	case TILEM_CALC_TI82:
+		return CALC_TI82;
+
+	case TILEM_CALC_TI83:
+	case TILEM_CALC_TI76:
+		return CALC_TI83;
+
+	case TILEM_CALC_TI83P:
+	case TILEM_CALC_TI83P_SE:
+		return CALC_TI83P;
+
+	case TILEM_CALC_TI84P:
+	case TILEM_CALC_TI84P_SE:
+	case TILEM_CALC_TI84P_NSPIRE:
+		return CALC_TI84P;
+
+	case TILEM_CALC_TI85:
+		return CALC_TI85;
+
+	case TILEM_CALC_TI86:
+		return CALC_TI86;
+
+	default:
+		return CALC_NONE;
+	}
+}
+
+/* Convert tifiles2 model ID to TilEm model ID. */
+int calcmodel_to_model(CalcModel model)
+{
+	switch (model) {
+	case CALC_TI73:
+		return TILEM_CALC_TI73;
+	case CALC_TI82:
+		return TILEM_CALC_TI82;
+	case CALC_TI83:
+		return TILEM_CALC_TI83;
+	case CALC_TI83P:
+		return TILEM_CALC_TI83P;
+	case CALC_TI84P:
+		return TILEM_CALC_TI84P;
+	case CALC_TI85:
+		return TILEM_CALC_TI85;
+	case CALC_TI86:
+		return TILEM_CALC_TI86;
+	default:
+		return 0;
+	}
+}
+
+/* Get model ID for a given file. */
+int file_to_model(const char *name)
+{
+	const char *p;
+	TigContent *tig;
+	int model;
+
+	p = strrchr(name, '.');
+	if (!p || strlen(p) < 4 || strchr(p, '/') || strchr(p, '\\'))
+		return 0;
+	p++;
+
+	if (!g_ascii_strcasecmp(p, "prg"))
+		return TILEM_CALC_TI81;
+
+	if (!g_ascii_strncasecmp(p, "73", 2))
+		return TILEM_CALC_TI73;
+	if (!g_ascii_strncasecmp(p, "82", 2))
+		return TILEM_CALC_TI82;
+	if (!g_ascii_strncasecmp(p, "83", 2))
+		return TILEM_CALC_TI83;
+	if (!g_ascii_strncasecmp(p, "8x", 2))
+		return TILEM_CALC_TI83P;
+	if (!g_ascii_strncasecmp(p, "85", 2))
+		return TILEM_CALC_TI85;
+	if (!g_ascii_strncasecmp(p, "86", 2))
+		return TILEM_CALC_TI86;
+
+	if (!g_ascii_strcasecmp(p, "tig")
+	    || !g_ascii_strcasecmp(p, "zip")) {
+		/* read file and see what tifiles thinks the type is */
+		tig = tifiles_content_create_tigroup(CALC_NONE, 0);
+		tifiles_file_read_tigroup(name, tig);
+		model = calcmodel_to_model(tig->model);
+		tifiles_content_delete_tigroup(tig);
+		return model;
+	}
+
+	return 0;
+}
+
+/* Get "base" model for file type support. */
+int model_to_base_model(int calc_model)
+{
+	switch (calc_model) {
+	case TILEM_CALC_TI83:
+	case TILEM_CALC_TI76:
+		return TILEM_CALC_TI83;
+
+	case TILEM_CALC_TI83P:
+	case TILEM_CALC_TI83P_SE:
+	case TILEM_CALC_TI84P:
+	case TILEM_CALC_TI84P_SE:
+	case TILEM_CALC_TI84P_NSPIRE:
+		return TILEM_CALC_TI83P;
+
+	default:
+		return calc_model;
+	}
+}
+
+/* Check if calc is compatible with given file type. */
+gboolean model_supports_file(int calc_model, int file_model)
+{
+	calc_model = model_to_base_model(calc_model);
+	file_model = model_to_base_model(file_model);
+
+	if (file_model == calc_model)
+		return TRUE;
+
+	if (file_model == TILEM_CALC_TI82
+	    && (calc_model == TILEM_CALC_TI83
+	        || calc_model == TILEM_CALC_TI83P))
+		return TRUE;
+
+	if (file_model == TILEM_CALC_TI83
+	    && (calc_model == TILEM_CALC_TI83P))
+		return TRUE;
+
+	if (file_model == TILEM_CALC_TI85
+	    && (calc_model == TILEM_CALC_TI86))
+		return TRUE;
+
+	return FALSE;
+}
+
+/* A popup which is used to let the user choose the model at startup */
+char choose_rom_popup(GtkWidget *parent_window, const char *filename,
+                      char default_model)
+{
+	const TilemHardware **models;
+	GtkWidget *dlg, *vbox, *frame, *btn;
+	GtkToggleButton **btns;
+	char *ids, id = 0;
+	int nmodels, noptions, i, j, defoption = 0, response;
+	dword romsize;
+	char *fn, *msg;
+
+	tilem_get_supported_hardware(&models, &nmodels);
+
+	/* determine ROM size for default model */
+	for (i = 0; i < nmodels; i++)
+		if (models[i]->model_id == default_model)
+			break;
+
+	g_return_val_if_fail(i < nmodels, 0);
+
+	romsize = models[i]->romsize;
+
+	/* all other models with same ROM size are candidates */
+	noptions = 0;
+	for (i = 0; i < nmodels; i++) {
+		if (models[i]->model_id == default_model)
+			defoption = noptions;
+		if (models[i]->romsize == romsize)
+			noptions++;
+	}
+
+	if (noptions < 2) /* no choice */
+		return default_model;
+
+	dlg = gtk_dialog_new_with_buttons("Select Calculator Type",
+	                                  GTK_WINDOW(parent_window),
+	                                  GTK_DIALOG_MODAL,
+	                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                                  GTK_STOCK_OK, GTK_RESPONSE_OK,
+	                                  NULL);
+	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
+	                                        GTK_RESPONSE_OK,
+	                                        GTK_RESPONSE_CANCEL,
+	                                        -1);
+	gtk_dialog_set_default_response(GTK_DIALOG(dlg),
+	                                GTK_RESPONSE_OK);
+
+	vbox = gtk_vbox_new(TRUE, 0);
+
+	/* create radio buttons */
+
+	btns = g_new(GtkToggleButton*, noptions);
+	ids = g_new(char, noptions);
+	btn = NULL;
+	for (i = j = 0; i < nmodels; i++) {
+		if (models[i]->romsize == romsize) {
+			btn = gtk_radio_button_new_with_label_from_widget
+				(GTK_RADIO_BUTTON(btn), models[i]->desc);
+			btns[j] = GTK_TOGGLE_BUTTON(btn);
+			ids[j] = models[i]->model_id;
+			gtk_box_pack_start(GTK_BOX(vbox), btn, TRUE, TRUE, 3);
+			j++;
+		}
+	}
+
+	gtk_toggle_button_set_active(btns[defoption], TRUE);
+
+	fn = g_filename_display_basename(filename);
+	msg = g_strdup_printf("Calculator type for %s:", fn);
+	frame = new_frame(msg, vbox);
+	g_free(fn);
+	g_free(msg);
+
+	gtk_container_set_border_width(GTK_CONTAINER(frame), 6);
+	gtk_widget_show_all(frame);
+
+	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
+	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
+
+	response = gtk_dialog_run(GTK_DIALOG(dlg));
+
+	if (response == GTK_RESPONSE_OK) {
+		for (i = 0; i < noptions; i++) {
+			if (gtk_toggle_button_get_active(btns[i])) {
+				id = ids[i];
+				break;
+			}
+		}
+	}
+	else {
+		id = 0;
+	}
+
+	gtk_widget_destroy(dlg);
+	g_free(btns);
+	g_free(ids);
+
+	return id;
+}
+
+/* Convert UTF-8 to filename encoding.  Use ASCII digits in place of
+   subscripts if necessary.  If conversion fails utterly, fall back to
+   the UTF-8 name, which is broken but better than nothing. */
+char * utf8_to_filename(const char *utf8str)
+{
+	gchar *result, *ibuf, *obuf, *p;
+	gsize icount, ocount;
+	const gchar **charsets;
+	GIConv ic;
+	gunichar c;
+
+	if (g_get_filename_charsets(&charsets))
+		return g_strdup(utf8str);
+
+	ic = g_iconv_open(charsets[0], "UTF-8");
+	if (!ic) {
+		g_warning("utf8_to_filename: unsupported charset %s",
+		          charsets[0]);
+		return g_strdup(utf8str);
+	}
+
+	ibuf = (gchar*) utf8str;
+	icount = strlen(utf8str);
+	ocount = icount * 2; /* be generous */
+	result = obuf = g_new(gchar, ocount + 1);
+
+	while (g_iconv(ic, &ibuf, &icount, &obuf, &ocount) == (gsize) -1) {
+		if (errno != EILSEQ) {
+			g_warning("utf8_to_filename: error in conversion");
+			g_free(result);
+			g_iconv_close(ic);
+			return g_strdup(utf8str);
+		}
+
+		c = g_utf8_get_char(ibuf);
+		if (c >= 0x2080 && c <= 0x2089)
+			*obuf = c - 0x2080 + '0';
+		else
+			*obuf = '_';
+		obuf++;
+		ocount--;
+
+		p = g_utf8_next_char(ibuf);
+		icount -= p - ibuf;
+		ibuf = p;
+	}
+
+	*obuf = 0;
+	g_iconv_close(ic);
+	return result;
+}
+
+/* Convert UTF-8 to a subset of UTF-8 that is compatible with the
+   locale */
+char * utf8_to_restricted_utf8(const char *utf8str)
+{
+	char *p, *q;
+	p = utf8_to_filename(utf8str);
+	q = g_filename_to_utf8(p, -1, NULL, NULL, NULL);
+	g_free(p);
+	if (q)
+		return q;
+	else
+		return g_strdup(utf8str);
+}
+
+/* Generate default filename (UTF-8) for a variable */
+char * get_default_filename(const TilemVarEntry *tve)
+{
+	GString *str = g_string_new("");
+
+	if (tve->slot_str) {
+		g_string_append(str, tve->slot_str);
+		if (tve->name_str && tve->name_str[0]) {
+			g_string_append_c(str, '-');
+			g_string_append(str, tve->name_str);
+		}
+	}
+	else if (tve->name_str && tve->name_str[0]) {
+		g_string_append(str, tve->name_str);
+	}
+	else {
+		g_string_append(str, "untitled");
+	}
+	g_string_append_c(str, '.');
+	g_string_append(str, tve->file_ext);
+	return g_string_free(str, FALSE);
+}
diff --git a/tool/tilem-src/install-sh b/tool/tilem-src/install-sh
new file mode 100755
index 0000000..6781b98
--- /dev/null
+++ b/tool/tilem-src/install-sh
@@ -0,0 +1,520 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2009-04-28.21; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+nl='
+'
+IFS=" ""	$nl"
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit=${DOITPROG-}
+if test -z "$doit"; then
+  doit_exec=exec
+else
+  doit_exec=$doit
+fi
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_glob='?'
+initialize_posix_glob='
+  test "$posix_glob" != "?" || {
+    if (set -f) 2>/dev/null; then
+      posix_glob=
+    else
+      posix_glob=:
+    fi
+  }
+'
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+no_target_directory=
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+   or: $0 [OPTION]... SRCFILES... DIRECTORY
+   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+   or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+     --help     display this help and exit.
+     --version  display version info and exit.
+
+  -c            (ignored)
+  -C            install only if different (preserve the last data modification time)
+  -d            create directories instead of installing files.
+  -g GROUP      $chgrpprog installed files to GROUP.
+  -m MODE       $chmodprog installed files to MODE.
+  -o USER       $chownprog installed files to USER.
+  -s            $stripprog installed files.
+  -t DIRECTORY  install into DIRECTORY.
+  -T            report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+  RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+  case $1 in
+    -c) ;;
+
+    -C) copy_on_change=true;;
+
+    -d) dir_arg=true;;
+
+    -g) chgrpcmd="$chgrpprog $2"
+	shift;;
+
+    --help) echo "$usage"; exit $?;;
+
+    -m) mode=$2
+	case $mode in
+	  *' '* | *'	'* | *'
+'*	  | *'*'* | *'?'* | *'['*)
+	    echo "$0: invalid mode: $mode" >&2
+	    exit 1;;
+	esac
+	shift;;
+
+    -o) chowncmd="$chownprog $2"
+	shift;;
+
+    -s) stripcmd=$stripprog;;
+
+    -t) dst_arg=$2
+	shift;;
+
+    -T) no_target_directory=true;;
+
+    --version) echo "$0 $scriptversion"; exit $?;;
+
+    --)	shift
+	break;;
+
+    -*)	echo "$0: invalid option: $1" >&2
+	exit 1;;
+
+    *)  break;;
+  esac
+  shift
+done
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+  # When -d is used, all remaining arguments are directories to create.
+  # When -t is used, the destination is already specified.
+  # Otherwise, the last argument is the destination.  Remove it from $@.
+  for arg
+  do
+    if test -n "$dst_arg"; then
+      # $@ is not empty: it contains at least $arg.
+      set fnord "$@" "$dst_arg"
+      shift # fnord
+    fi
+    shift # arg
+    dst_arg=$arg
+  done
+fi
+
+if test $# -eq 0; then
+  if test -z "$dir_arg"; then
+    echo "$0: no input file specified." >&2
+    exit 1
+  fi
+  # It's OK to call `install-sh -d' without argument.
+  # This can happen when creating conditional directories.
+  exit 0
+fi
+
+if test -z "$dir_arg"; then
+  trap '(exit $?); exit' 1 2 13 15
+
+  # Set umask so as not to create temps with too-generous modes.
+  # However, 'strip' requires both read and write access to temps.
+  case $mode in
+    # Optimize common cases.
+    *644) cp_umask=133;;
+    *755) cp_umask=22;;
+
+    *[0-7])
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw='% 200'
+      fi
+      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+    *)
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw=,u+rw
+      fi
+      cp_umask=$mode$u_plus_rw;;
+  esac
+fi
+
+for src
+do
+  # Protect names starting with `-'.
+  case $src in
+    -*) src=./$src;;
+  esac
+
+  if test -n "$dir_arg"; then
+    dst=$src
+    dstdir=$dst
+    test -d "$dstdir"
+    dstdir_status=$?
+  else
+
+    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+    # might cause directories to be created, which would be especially bad
+    # if $src (and thus $dsttmp) contains '*'.
+    if test ! -f "$src" && test ! -d "$src"; then
+      echo "$0: $src does not exist." >&2
+      exit 1
+    fi
+
+    if test -z "$dst_arg"; then
+      echo "$0: no destination specified." >&2
+      exit 1
+    fi
+
+    dst=$dst_arg
+    # Protect names starting with `-'.
+    case $dst in
+      -*) dst=./$dst;;
+    esac
+
+    # If destination is a directory, append the input filename; won't work
+    # if double slashes aren't ignored.
+    if test -d "$dst"; then
+      if test -n "$no_target_directory"; then
+	echo "$0: $dst_arg: Is a directory" >&2
+	exit 1
+      fi
+      dstdir=$dst
+      dst=$dstdir/`basename "$src"`
+      dstdir_status=0
+    else
+      # Prefer dirname, but fall back on a substitute if dirname fails.
+      dstdir=`
+	(dirname "$dst") 2>/dev/null ||
+	expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	     X"$dst" : 'X\(//\)[^/]' \| \
+	     X"$dst" : 'X\(//\)$' \| \
+	     X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
+	echo X"$dst" |
+	    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)[^/].*/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\).*/{
+		   s//\1/
+		   q
+		 }
+		 s/.*/./; q'
+      `
+
+      test -d "$dstdir"
+      dstdir_status=$?
+    fi
+  fi
+
+  obsolete_mkdir_used=false
+
+  if test $dstdir_status != 0; then
+    case $posix_mkdir in
+      '')
+	# Create intermediate dirs using mode 755 as modified by the umask.
+	# This is like FreeBSD 'install' as of 1997-10-28.
+	umask=`umask`
+	case $stripcmd.$umask in
+	  # Optimize common cases.
+	  *[2367][2367]) mkdir_umask=$umask;;
+	  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+	  *[0-7])
+	    mkdir_umask=`expr $umask + 22 \
+	      - $umask % 100 % 40 + $umask % 20 \
+	      - $umask % 10 % 4 + $umask % 2
+	    `;;
+	  *) mkdir_umask=$umask,go-w;;
+	esac
+
+	# With -d, create the new directory with the user-specified mode.
+	# Otherwise, rely on $mkdir_umask.
+	if test -n "$dir_arg"; then
+	  mkdir_mode=-m$mode
+	else
+	  mkdir_mode=
+	fi
+
+	posix_mkdir=false
+	case $umask in
+	  *[123567][0-7][0-7])
+	    # POSIX mkdir -p sets u+wx bits regardless of umask, which
+	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+	    ;;
+	  *)
+	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+	    if (umask $mkdir_umask &&
+		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+	    then
+	      if test -z "$dir_arg" || {
+		   # Check for POSIX incompatibilities with -m.
+		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+		   # other-writeable bit of parent directory when it shouldn't.
+		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
+		   case $ls_ld_tmpdir in
+		     d????-?r-*) different_mode=700;;
+		     d????-?--*) different_mode=755;;
+		     *) false;;
+		   esac &&
+		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+		   }
+		 }
+	      then posix_mkdir=:
+	      fi
+	      rmdir "$tmpdir/d" "$tmpdir"
+	    else
+	      # Remove any dirs left behind by ancient mkdir implementations.
+	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+	    fi
+	    trap '' 0;;
+	esac;;
+    esac
+
+    if
+      $posix_mkdir && (
+	umask $mkdir_umask &&
+	$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+      )
+    then :
+    else
+
+      # The umask is ridiculous, or mkdir does not conform to POSIX,
+      # or it failed possibly due to a race condition.  Create the
+      # directory the slow way, step by step, checking for races as we go.
+
+      case $dstdir in
+	/*) prefix='/';;
+	-*) prefix='./';;
+	*)  prefix='';;
+      esac
+
+      eval "$initialize_posix_glob"
+
+      oIFS=$IFS
+      IFS=/
+      $posix_glob set -f
+      set fnord $dstdir
+      shift
+      $posix_glob set +f
+      IFS=$oIFS
+
+      prefixes=
+
+      for d
+      do
+	test -z "$d" && continue
+
+	prefix=$prefix$d
+	if test -d "$prefix"; then
+	  prefixes=
+	else
+	  if $posix_mkdir; then
+	    (umask=$mkdir_umask &&
+	     $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+	    # Don't fail if two instances are running concurrently.
+	    test -d "$prefix" || exit 1
+	  else
+	    case $prefix in
+	      *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+	      *) qprefix=$prefix;;
+	    esac
+	    prefixes="$prefixes '$qprefix'"
+	  fi
+	fi
+	prefix=$prefix/
+      done
+
+      if test -n "$prefixes"; then
+	# Don't fail if two instances are running concurrently.
+	(umask $mkdir_umask &&
+	 eval "\$doit_exec \$mkdirprog $prefixes") ||
+	  test -d "$dstdir" || exit 1
+	obsolete_mkdir_used=true
+      fi
+    fi
+  fi
+
+  if test -n "$dir_arg"; then
+    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+  else
+
+    # Make a couple of temp file names in the proper directory.
+    dsttmp=$dstdir/_inst.$$_
+    rmtmp=$dstdir/_rm.$$_
+
+    # Trap to clean up those temp files at exit.
+    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+    # Copy the file name to the temp name.
+    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+    # and set any options; do chmod last to preserve setuid bits.
+    #
+    # If any of these fail, we abort the whole thing.  If we want to
+    # ignore errors from any of these, just make sure not to ignore
+    # errors from the above "$doit $cpprog $src $dsttmp" command.
+    #
+    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+    # If -C, don't bother to copy if it wouldn't change the file.
+    if $copy_on_change &&
+       old=`LC_ALL=C ls -dlL "$dst"	2>/dev/null` &&
+       new=`LC_ALL=C ls -dlL "$dsttmp"	2>/dev/null` &&
+
+       eval "$initialize_posix_glob" &&
+       $posix_glob set -f &&
+       set X $old && old=:$2:$4:$5:$6 &&
+       set X $new && new=:$2:$4:$5:$6 &&
+       $posix_glob set +f &&
+
+       test "$old" = "$new" &&
+       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+    then
+      rm -f "$dsttmp"
+    else
+      # Rename the file to the real destination.
+      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+      # The rename failed, perhaps because mv can't rename something else
+      # to itself, or perhaps because mv is so ancient that it does not
+      # support -f.
+      {
+	# Now remove or move aside any old file at destination location.
+	# We try this two ways since rm can't unlink itself on some
+	# systems and the destination file might be busy for other
+	# reasons.  In this case, the final cleanup might fail but the new
+	# file should still install successfully.
+	{
+	  test ! -f "$dst" ||
+	  $doit $rmcmd -f "$dst" 2>/dev/null ||
+	  { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+	    { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
+	  } ||
+	  { echo "$0: cannot unlink or rename $dst" >&2
+	    (exit 1); exit 1
+	  }
+	} &&
+
+	# Now rename the file to the real destination.
+	$doit $mvcmd "$dsttmp" "$dst"
+      }
+    fi || exit 1
+
+    trap '' 0
+  fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/tool/tilem-src/installer/win32/COPYING-PIXMAN b/tool/tilem-src/installer/win32/COPYING-PIXMAN
new file mode 100644
index 0000000..42c4912
--- /dev/null
+++ b/tool/tilem-src/installer/win32/COPYING-PIXMAN
@@ -0,0 +1,39 @@
+License for pixman
+------------------
+
+ Copyright 1987, 1988, 1989, 1998  The Open Group
+ Copyright 1987, 1988, 1989 Digital Equipment Corporation
+ Copyright 1999, 2004, 2008 Keith Packard
+ Copyright 2000 SuSE, Inc.
+ Copyright 2000 Keith Packard, member of The XFree86 Project, Inc.
+ Copyright 2004, 2005, 2007, 2008, 2009, 2010 Red Hat, Inc.
+ Copyright 2004 Nicholas Miell
+ Copyright 2005 Lars Knoll & Zack Rusin, Trolltech
+ Copyright 2005 Trolltech AS
+ Copyright 2007 Luca Barbato
+ Copyright 2008 Aaron Plattner, NVIDIA Corporation
+ Copyright 2008 Rodrigo Kumpera
+ Copyright 2008 André Tupinambá
+ Copyright 2008 Mozilla Corporation
+ Copyright 2008 Frederic Plourde
+ Copyright 2009, Oracle and/or its affiliates. All rights reserved.
+ Copyright 2009, 2010 Nokia Corporation
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
diff --git a/tool/tilem-src/installer/win32/COPYING-ZLIB b/tool/tilem-src/installer/win32/COPYING-ZLIB
new file mode 100644
index 0000000..ad5a38c
--- /dev/null
+++ b/tool/tilem-src/installer/win32/COPYING-ZLIB
@@ -0,0 +1,23 @@
+License for zlib
+----------------
+
+ (C) 1995-2010 Jean-loup Gailly and Mark Adler
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Jean-loup Gailly        Mark Adler
+  jloup@gzip.org          madler@alumni.caltech.edu
diff --git a/tool/tilem-src/installer/win32/Makefile.in b/tool/tilem-src/installer/win32/Makefile.in
new file mode 100644
index 0000000..898f1b9
--- /dev/null
+++ b/tool/tilem-src/installer/win32/Makefile.in
@@ -0,0 +1,184 @@
+MAKENSIS = @MAKENSIS@
+STRIP = @STRIP@
+OBJDUMP = @OBJDUMP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+SHELL = @SHELL@
+LN_S = @LN_S@
+
+abs_builddir = @abs_builddir@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+@SET_MAKE@
+
+main_program = tilem2.exe
+
+docs = README KEYS NEWS COPYING CHANGELOG THANKS
+extra_docs = COPYING-PIXMAN COPYING-ZLIB
+
+install_files = $(main_program) *.dll *.txt
+install_subdirs = etc lib share
+
+extra_gtk_files = lib/gtk-2.0/2.10.0/engines/libwimp.dll
+extra_dlls =
+
+GTK_BINDIR = @GTK_BINDIR@
+TICALCS_BINDIR = @TICALCS_BINDIR@
+DLLPATH = @DLLPATH@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+
+system_dlls = advapi32.dll cfgmgr32.dll comctl32.dll comdlg32.dll dnsapi.dll \
+	      gdi32.dll gdiplus.dll imm32.dll kernel32.dll msimg32.dll \
+	      msvcrt.dll ole32.dll setupapi.dll shell32.dll shlwapi.dll \
+	      user32.dll usp10.dll winspool.drv ws2_32.dll wsock32.dll
+
+all: install
+
+#### Build the installer
+
+dist: install installer.nsi
+	rm -f files.nsi rmfiles.nsi rmdirs.nsi
+	set -e ; for file in $(install_files) ; do \
+	  name=`echo "$$file" | sed 's,/,\\\\,g'` ; \
+	  echo "File \"$$name\"" >> files.nsi ; \
+	  echo "Delete \"\$$INSTDIR\\$$name\"" >> rmfiles.nsi ; \
+	done
+	set -e ; for dir in $(install_subdirs) ; do \
+	  if [ -d $$dir ] ; then \
+	    echo "File /r \"$$dir\"" >> files.nsi ; \
+	  fi ; \
+	done
+	set -e ; LC_ALL=C ; export LC_ALL ; \
+	for file in `find -L $(install_subdirs) | sort -r` ; do \
+	  name=`echo "$$file" | sed 's,/,\\\\,g'` ; \
+	  if [ -d $$file ] ; then \
+	    echo "RmDir \"\$$INSTDIR\\$$name\"" >> rmdirs.nsi ; \
+	  else \
+	    echo "Delete \"\$$INSTDIR\\$$name\"" >> rmfiles.nsi ; \
+	  fi ; \
+	done
+	$(MAKENSIS) installer.nsi
+
+installer.nsi: installer.nsi.in $(top_builddir)/config.status
+	cd $(top_builddir) && $(SHELL) ./config.status installer/win32/installer.nsi
+
+#### Install files into this directory
+
+install: install-bin install-data install-config install-extra install-libs
+
+# Install the program itself and data files
+
+install-bin: $(main_program)
+
+$(main_program): $(top_builddir)/gui/$(main_program)
+	cd $(top_builddir)/gui && $(MAKE) install bindir=$(abs_builddir)
+	$(STRIP) $(main_program)
+
+install-data:
+	cd $(top_builddir)/data && $(MAKE) install prefix=$(abs_builddir)
+	rm -rf share/mime
+	rm -rf share/applications
+	rm -rf share/icons
+	set -e ; for file in $(docs) ; do \
+	 sed 's/\r*$$/\r/' < $(top_srcdir)/$$file > $$file.txt ; \
+	done
+	set -e ; for file in $(extra_docs) ; do \
+	 sed 's/\r*$$/\r/' < $(srcdir)/$$file > $$file.txt ; \
+	done
+
+# Install GTK+ configuration
+
+install-config:
+	$(INSTALL) -d -m 755 etc/gtk-2.0
+	$(INSTALL_DATA) $(srcdir)/gtkrc etc/gtk-2.0
+
+# Install additional files
+
+install-extra: install-extra-stamp
+
+install-extra-stamp:
+	set -e ; for file in $(extra_gtk_files) ; do \
+	  rm -f $$file ; \
+	  $(INSTALL) -d -m 755 `dirname $$file` ; \
+	  for dir in `echo $(DLLPATH) | tr $(PATH_SEPARATOR) ' '` ; do \
+	    if [ -d $$dir ] && [ -f $$dir/../$$file ] ; then \
+	      $(LN_S) $$dir/../$$file $$file ; \
+	      break ; \
+	    fi ; \
+	  done ; \
+	  if ! [ -f $$file ] ; then \
+	    echo "** Cannot find $$file **" ; \
+	    echo ; \
+	    echo "  Directories searched: $(DLLPATH)" ; \
+	    echo ; \
+	    exit 1 ; \
+	  fi ; \
+	done
+	touch install-extra-stamp
+
+# Install required libraries
+
+install-libs: install-libs-stamp
+
+install-libs-stamp: $(main_program) install-extra-stamp
+	rm -f dlls missing-dlls
+	echo $(main_program) > binfiles
+	ls $(extra_gtk_files) | grep 'dll$$' >> binfiles
+	echo $(extra_dlls) > dlls
+	set -e ; LC_ALL=C ; export LC_ALL ; \
+	while [ -s binfiles ] ; do \
+	  for bin in `cat binfiles` ; do \
+	    $(OBJDUMP) -p $$bin | \
+	      sed -n '/DLL Name:/{s/.*DLL Name: *\([^ ]*\).*/\1/;p}' | \
+	      tr A-Z a-z >> dlls ; \
+	  done ; \
+	  rm -f binfiles ; \
+	  for lib in `cat dlls` ; do \
+	    if ! [ -f ./$$lib ] && ! echo $(system_dlls) | grep -q $$lib ; then \
+	      for dir in `echo $(DLLPATH) | tr $(PATH_SEPARATOR) ' '` ; do \
+	        if [ -d $$dir ] && [ -f $$dir/$$lib ] ; then \
+	          $(LN_S) $$dir/$$lib . ; \
+	          echo $$lib >> binfiles ; \
+	          break ; \
+	        fi ; \
+	      done ; \
+	      if ! [ -f $$lib ] ; then \
+	        echo $$lib >> missing-dlls ; \
+	      fi ; \
+	    fi ; \
+	  done ; \
+	  rm -f dlls ; \
+	done
+	@if [ -s missing-dlls ] ; then \
+	  echo "** Cannot find the following libraries **" ; \
+	  cat missing-dlls ; \
+	  echo ; \
+	  echo "  If these libraries are part of a standard Windows" ; \
+	  echo "  installation, add them to the list of 'system_dlls'" ; \
+	  echo "  in Makefile.in." ; \
+	  echo ; \
+	  echo "  Directories searched: $(DLLPATH)" ; \
+	  echo ; \
+	  exit 1 ; \
+	fi
+	touch install-libs-stamp
+
+
+clean:
+	rm -f install-libs-stamp install-extra-stamp
+	rm -f $(install_files)
+	rm -rf $(install_subdirs)
+	rm -f dlls missing-dlls binfiles
+	rm -f files.nsi rmfiles.nsi rmdirs.nsi
+
+
+Makefile: Makefile.in $(top_builddir)/config.status
+	cd $(top_builddir) && $(SHELL) ./config.status
+
+$(top_builddir)/config.status: $(top_srcdir)/configure
+	cd $(top_builddir) && $(SHELL) ./config.status --recheck
+
+.PRECIOUS: Makefile $(top_builddir)/config.status
+.PHONY: all clean install install-bin install-data install-config install-libraries install-extra
diff --git a/tool/tilem-src/installer/win32/gtkrc b/tool/tilem-src/installer/win32/gtkrc
new file mode 100644
index 0000000..8942b20
--- /dev/null
+++ b/tool/tilem-src/installer/win32/gtkrc
@@ -0,0 +1,75 @@
+gtk-icon-sizes = "gtk-menu=13,13:gtk-small-toolbar=16,16:gtk-large-toolbar=24,24:gtk-dnd=32,32"
+gtk-toolbar-icon-size = small-toolbar
+gtk-toolbar-style = GTK_TOOLBAR_ICONS
+
+# disable images in buttons. i've only seen ugly delphi apps use this feature.
+gtk-button-images = 0
+
+# enable/disable images in menus. most "stock" microsoft apps don't use these, except sparingly.
+# the office apps use them heavily, though.
+gtk-menu-images = 0
+
+# use the win32 button ordering instead of the GNOME HIG one, where applicable
+gtk-alternative-button-order = 1
+
+# use the win32 sort indicators direction, as in Explorer
+gtk-alternative-sort-arrows = 1
+
+# Windows users don't expect the PC Speaker beeping at them when they backspace in an empty textview and stuff like that
+gtk-error-bell = 0
+
+style "msw-default"
+{
+  GtkWidget::interior-focus = 1
+  GtkOptionMenu::indicator-size = { 9, 5 }
+  GtkOptionMenu::indicator-spacing = { 7, 5, 2, 2 }
+  GtkSpinButton::shadow-type = in
+
+  # Owen and I disagree that these should be themable
+  #GtkUIManager::add-tearoffs = 0
+  #GtkComboBox::add-tearoffs = 0
+  
+  GtkComboBox::appears-as-list = 1
+  GtkComboBox::focus-on-click = 0
+  
+  GOComboBox::add_tearoffs = 0
+
+  GtkTreeView::allow-rules = 0
+  GtkTreeView::expander-size = 12
+
+  GtkExpander::expander-size = 12
+
+  GtkScrolledWindow::scrollbar_spacing = 1
+
+  GtkSeparatorMenuItem::horizontal-padding = 2
+
+  engine "wimp" 
+  {
+  }
+}
+class "*" style "msw-default"
+
+binding "ms-windows-tree-view"
+{
+  bind "Right" { "expand-collapse-cursor-row" (1,1,0) }
+  bind "Left" { "expand-collapse-cursor-row" (1,0,0) }
+}
+
+class "GtkTreeView" binding "ms-windows-tree-view"
+
+style "msw-combobox-thickness" = "msw-default"
+{
+  xthickness = 0 
+  ythickness = 0
+}
+
+widget_class "*TreeView*ComboBox*" style "msw-combobox-thickness"
+widget_class "*ComboBox*GtkFrame*" style "msw-combobox-thickness"
+
+# work around issue with "list-style" cellrenderercombos not emitting
+# the "edited" signal - GNOME bug #317387
+style "menu-style-combo"
+{
+  GtkComboBox::appears-as-list = 0
+}
+widget_class "*.GtkTreeView.GtkComboBox" style "menu-style-combo"
diff --git a/tool/tilem-src/installer/win32/installer.nsi.in b/tool/tilem-src/installer/win32/installer.nsi.in
new file mode 100644
index 0000000..f662691
--- /dev/null
+++ b/tool/tilem-src/installer/win32/installer.nsi.in
@@ -0,0 +1,82 @@
+Name "@PACKAGE_NAME@"
+OutFile "@PACKAGE_TARNAME@-@PACKAGE_VERSION@.exe"
+SetCompressor /solid lzma
+
+!define MULTIUSER_EXECUTIONLEVEL Highest
+!define MULTIUSER_MUI
+!define MULTIUSER_INSTALLMODE_INSTDIR "@PACKAGE_NAME@"
+!include "MultiUser.nsh"
+!include "MUI2.nsh"
+
+Var StartMenuFolder
+
+!define MUI_FINISHPAGE_RUN ""
+!define MUI_FINISHPAGE_RUN_TEXT "Create a desktop shortcut"
+!define MUI_FINISHPAGE_RUN_FUNCTION desktopicon
+
+!insertmacro MUI_PAGE_WELCOME
+!insertmacro MULTIUSER_PAGE_INSTALLMODE
+!insertmacro MUI_PAGE_DIRECTORY
+!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
+!insertmacro MUI_PAGE_INSTFILES
+!insertmacro MUI_PAGE_FINISH
+
+!insertmacro MUI_UNPAGE_WELCOME
+!insertmacro MUI_UNPAGE_INSTFILES
+!insertmacro MUI_UNPAGE_FINISH
+
+!insertmacro MUI_LANGUAGE "English"
+
+Function .onInit
+	!insertmacro MULTIUSER_INIT
+FunctionEnd
+
+Function un.onInit
+	UserInfo::GetAccountType
+	Pop $MultiUser.Privileges
+	ReadINIStr $0 "$INSTDIR\uninstall.ini" "Uninstall" "InstallMode"
+	${if} $0 == "AllUsers"
+		call un.MultiUser.InstallMode.AllUsers
+	${else}
+		call un.MultiUser.InstallMode.CurrentUser
+	${endif}
+FunctionEnd
+
+Function desktopicon
+	SetShellVarContext current
+	CreateShortCut "$DESKTOP\TilEm.lnk" "$INSTDIR\tilem2.exe"
+FunctionEnd
+
+Section
+	SetOutPath "$INSTDIR"
+	!include "files.nsi"
+
+	!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+		CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
+		CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TilEm.lnk" "$INSTDIR\tilem2.exe"
+		CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall TilEm.lnk" "$INSTDIR\uninstall.exe"
+		WriteINIStr "$INSTDIR\uninstall.ini" "Uninstall" "StartMenuFolder" $StartMenuFolder
+	!insertmacro MUI_STARTMENU_WRITE_END
+
+	WriteINIStr "$INSTDIR\uninstall.ini" "Uninstall" "InstallMode" $MultiUser.InstallMode
+	WriteUninstaller "$INSTDIR\uninstall.exe"
+SectionEnd
+
+Section "Uninstall"
+	!include "rmfiles.nsi"
+	!include "rmdirs.nsi"
+
+	ReadINIStr $StartMenuFolder "$INSTDIR\uninstall.ini" "Uninstall" "StartMenuFolder"
+	${if} $StartMenuFolder != ""
+		Delete "$SMPROGRAMS\$StartMenuFolder\TilEm.lnk"
+		Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall TilEm.lnk"
+		RmDir "$SMPROGRAMS\$StartMenuFolder"
+	${endif}
+
+	SetShellVarContext current
+	Delete "$DESKTOP\TilEm.lnk"
+
+	Delete "$INSTDIR\uninstall.ini"
+	Delete "$INSTDIR\uninstall.exe"
+	RmDir "$INSTDIR"
+SectionEnd