diff --git a/roms/tube/CiscOS.rom b/roms/tube/CiscOS.rom
index ce2bd80e..97b9eaf2 100644
Binary files a/roms/tube/CiscOS.rom and b/roms/tube/CiscOS.rom differ
diff --git a/src/6502.c b/src/6502.c
index 8ace4603..70387fe7 100644
--- a/src/6502.c
+++ b/src/6502.c
@@ -4102,8 +4102,8 @@ void m65c02_exec(int slice)
                 case 0x02:
                         if (dbg_core6502)
                             debug_trap(&core6502_cpu_debug, debug_addr(oldpc), 1);
-                        else
-                            polltime(1);
+                        polltime(2);
+                        (void)readmem(pc++);
                         break;
 
                 case 0x04:      /*TSB zp */
diff --git a/src/6502tube.c b/src/6502tube.c
index 7e324864..bbc4aa4a 100644
--- a/src/6502tube.c
+++ b/src/6502tube.c
@@ -593,8 +593,8 @@ void tube_6502_exec()
                 case 0x02:
                         if (dbg_tube6502)
                             debug_trap(&tube6502_cpu_debug, oldtpc, 1);
-                        else
-                            polltime(1);
+                        polltime(2);
+                        readmem(pc++);
                         break;
 
                 case 0x04:      /*TSB zp */
diff --git a/src/Makefile.am b/src/Makefile.am
index 3cabd488..f1d36e65 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -108,6 +108,7 @@ b_em_SOURCES = \
 	tapenoise.c \
     pdp11/pdp11.c \
     pdp11/pdp11_debug.c \
+    textsave.c \
 	tube.c \
 	uef.c \
 	uservia.c \
diff --git a/src/b-em.h b/src/b-em.h
index 91483956..bcc44232 100644
--- a/src/b-em.h
+++ b/src/b-em.h
@@ -9,6 +9,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <math.h>
 
 #include "compat_wrappers.h"
 
diff --git a/src/debugger.c b/src/debugger.c
index 0e2de0a2..70cde59c 100644
--- a/src/debugger.c
+++ b/src/debugger.c
@@ -1751,16 +1751,20 @@ static void debug_trace_write(cpu_debug_t *cpu, uint32_t addr, FILE *fp)
         *(sym++) = '\0';
     }
 
-    fputs("\t", fp);
+    while(strlen(buf) < 52)
+      strcat(buf, " ");
+
     fputs(buf, fp);
-    *buf = ' ';
 
     const char **np = cpu->reg_names;
     const char *name;
     int r = 0;
     while ((name = *np++)) {
-        size_t len = cpu->reg_print(r++, buf + 1, sizeof buf - 1);
-        fwrite(buf, len + 1, 1, fp);
+        fputs(" ", fp);
+        fputs(name, fp);
+        fputs("=", fp);
+        size_t len = cpu->reg_print(r++, buf, sizeof buf);
+        fwrite(buf, len, 1, fp);
     }
 
     if (sym)
diff --git a/src/gui-allegro.c b/src/gui-allegro.c
index c8c43527..0a3e47b1 100644
--- a/src/gui-allegro.c
+++ b/src/gui-allegro.c
@@ -29,6 +29,7 @@
 #include "sysacia.h"
 #include "tape.h"
 #include "tapecat-allegro.h"
+#include "textsave.h"
 #include "tube.h"
 #include "uservia.h"
 #include "video.h"
@@ -92,10 +93,9 @@ static void add_radio_item(ALLEGRO_MENU *parent, char const *title, uint16_t id,
 
 static void add_radio_set(ALLEGRO_MENU *parent, char const **labels, uint16_t id, int cur_value)
 {
-    int i;
     const char *label;
 
-    for (i = 0; (label = *labels++); i++)
+    for (int i = 0; (label = *labels++); i++)
         add_checkbox_item(parent, label, menu_id_num(id, i), i == cur_value);
 }
 
@@ -108,11 +108,9 @@ static int menu_cmp(const void *va, const void *vb)
 
 static void add_sorted_set(ALLEGRO_MENU *parent, menu_map_t *map, size_t items, uint16_t id, int cur_value)
 {
-    int i, ino;
-
     qsort(map, items, sizeof(menu_map_t), menu_cmp);
-    for (i = 0; i < items; i++) {
-        ino = map[i].itemno;
+    for (int i = 0; i < items; i++) {
+        int ino = map[i].itemno;
         add_checkbox_item(parent, map[i].label, menu_id_num(id, ino), ino == cur_value);
     }
 }
@@ -124,6 +122,7 @@ static ALLEGRO_MENU *create_file_menu(void)
     al_append_menu_item(menu, "Load state...", IDM_FILE_LOAD_STATE, 0, NULL, NULL);
     al_append_menu_item(menu, "Save State...", IDM_FILE_SAVE_STATE, 0, NULL, NULL);
     al_append_menu_item(menu, "Save Screenshot...", IDM_FILE_SCREEN_SHOT, 0, NULL, NULL);
+    al_append_menu_item(menu, "Save Screen as Text...", IDM_FILE_SCREEN_TEXT, 0, NULL, NULL);
     add_checkbox_item(menu, "Print to file", IDM_FILE_PRINT, print_dest == PDEST_FILE);
     add_checkbox_item(menu, "Print to command", IDM_FILE_PCMD, print_dest == PDEST_PIPE);
     add_checkbox_item(menu, "Serial to file", IDM_FILE_SERIAL, sysacia_fp);
@@ -190,9 +189,8 @@ static ALLEGRO_MENU *create_disc_menu(void)
 
 void gui_allegro_set_eject_text(int drive, ALLEGRO_PATH *path)
 {
-    char temp[256];
-
     if (path) {
+        char temp[256];
         snprintf(temp, sizeof temp, "Eject drive %s: %s", drive ? "1/3" : "0/2", al_get_path_filename(path));
         al_set_menu_item_caption(disc_menu, menu_id_num(IDM_DISC_EJECT, drive), temp);
     }
@@ -222,15 +220,11 @@ static ALLEGRO_MENU *create_tape_menu(void)
 
 static void gen_rom_label(int slot, char *dest)
 {
-    int ver;
-    const uint8_t *detail;
-    const char *rr, *name;
-
-    rr = rom_slots[slot].swram ? "RAM" : "ROM";
-    detail = mem_romdetail(slot);
-    name = rom_slots[slot].name;
+    const char *rr = rom_slots[slot].swram ? "RAM" : "ROM";
+    const uint8_t *detail = mem_romdetail(slot);
+    const char *name = rom_slots[slot].name;
     if (detail) {
-        ver = *detail++;
+        int ver = *detail++;
         if (name)
             snprintf(dest, ROM_LABEL_LEN, "%02d %s: %s %02X (%s)", slot, rr, detail, ver, name);
         else
@@ -245,14 +239,11 @@ static void gen_rom_label(int slot, char *dest)
 
 static ALLEGRO_MENU *create_rom_menu(void)
 {
-    ALLEGRO_MENU *menu, *sub;
-    int slot;
-    char label[ROM_LABEL_LEN];
-
-    menu =  al_create_menu();
-    for (slot = ROM_NSLOT-1; slot >= 0; slot--) {
+    ALLEGRO_MENU *menu = al_create_menu();
+    for (int slot = ROM_NSLOT-1; slot >= 0; slot--) {
+        char label[ROM_LABEL_LEN];
         gen_rom_label(slot, label);
-        sub = al_create_menu();
+        ALLEGRO_MENU *sub = al_create_menu();
         al_append_menu_item(sub, "Load...", menu_id_num(IDM_ROMS_LOAD, slot), 0, NULL, NULL);
         al_append_menu_item(sub, "Clear", menu_id_num(IDM_ROMS_CLEAR, slot), 0, NULL, NULL);
         add_checkbox_item(sub, "RAM", menu_id_num(IDM_ROMS_RAM, slot), rom_slots[slot].swram);
@@ -265,16 +256,14 @@ static ALLEGRO_MENU *create_rom_menu(void)
 static void update_rom_menu(void)
 {
     ALLEGRO_MENU *menu = rom_menu;
-    ALLEGRO_MENU *sub;
-    int slot, flags;
-    char label[ROM_LABEL_LEN];
-
-    for (slot = ROM_NSLOT-1; slot >= 0; slot--) {
+    
+    for (int slot = ROM_NSLOT-1; slot >= 0; slot--) {
+        char label[ROM_LABEL_LEN];
         gen_rom_label(slot, label);
         al_set_menu_item_caption(menu, slot-ROM_NSLOT+1, label);
-        sub = al_find_menu(menu, slot+1);
+        ALLEGRO_MENU *sub = al_find_menu(menu, slot+1);
         if (sub) {
-            flags = rom_slots[slot].swram ? ALLEGRO_MENU_ITEM_CHECKBOX|ALLEGRO_MENU_ITEM_CHECKED : ALLEGRO_MENU_ITEM_CHECKBOX;
+            int flags = rom_slots[slot].swram ? ALLEGRO_MENU_ITEM_CHECKBOX|ALLEGRO_MENU_ITEM_CHECKED : ALLEGRO_MENU_ITEM_CHECKBOX;
             al_set_menu_item_flags(sub, menu_id_num(IDM_ROMS_RAM, slot), flags);
         }
         else
@@ -284,16 +273,53 @@ static void update_rom_menu(void)
 
 static ALLEGRO_MENU *create_model_menu(void)
 {
-    ALLEGRO_MENU *menu = al_create_menu();
-    menu_map_t *map;
-    int i;
-
-    if ((map = malloc(model_count * sizeof(menu_map_t)))) {
-        for (i = 0; i < model_count; i++) {
-            map[i].label = models[i].name;
-            map[i].itemno = i;
+    menu_map_t *map = calloc(model_count * 2, sizeof(menu_map_t));
+    if (map) {
+        ALLEGRO_MENU *menu = al_create_menu();
+        menu_map_t *groups = map + model_count;
+        int ngroup = 0;
+        for (int model_no = 0; model_no < model_count; ++model_no) {
+            const char *group = models[model_no].group;
+            if (group) {
+                bool found = false;
+                for (int group_no = 0; group_no < ngroup; ++group_no) {
+                    if (!strcmp(group, groups[group_no].label)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    groups[ngroup].label = group;
+                    groups[ngroup].itemno = ngroup;
+                    ++ngroup;
+                }
+            }
+        }
+        qsort(groups, ngroup, sizeof(menu_map_t), menu_cmp);
+        for (int group_no = 0; group_no < ngroup; ++group_no) {
+            const char *group_label = groups[group_no].label;
+            int item_no = 0;
+            for (int model_no = 0; model_no < model_count; ++model_no) {
+                const char *model_group = models[model_no].group;
+                if (model_group && !strcmp(model_group, group_label)) {
+                    map[item_no].label = models[model_no].name;
+                    map[item_no].itemno = model_no;
+                    ++item_no;
+                }
+            }
+            ALLEGRO_MENU *sub = al_create_menu();
+            add_sorted_set(sub, map, item_no, IDM_MODEL, curmodel);
+            al_append_menu_item(menu, groups[group_no].label, 0, 0, NULL, sub);
         }
-        add_sorted_set(menu, map, model_count, IDM_MODEL, curmodel);
+        int item_no = 0;
+        for (int model_no = 0; model_no < model_count; ++model_no) {
+            if (!models[model_no].group) {
+                map[item_no].label = models[model_no].name;
+                map[item_no].itemno = model_no;
+                ++item_no;
+            }
+        }
+        add_sorted_set(menu, map, item_no, IDM_MODEL, curmodel);
         free(map);
         return menu;
     }
@@ -308,19 +334,21 @@ static ALLEGRO_MENU *create_tube_menu(void)
     ALLEGRO_MENU *menu = al_create_menu();
     ALLEGRO_MENU *sub = al_create_menu();
     menu_map_t *map = malloc(num_tubes * sizeof(menu_map_t));
-    if (!map) {
-        log_error("gui: unable to allocate tube menu");
-        return NULL;
+    if (map) {
+        for (int i = 0; i < num_tubes; ++i) {
+            map[i].label = tubes[i].name;
+            map[i].itemno = i;
+        }
+        add_sorted_set(menu, map, num_tubes, IDM_TUBE, curtube);
+        for (int i = 0; i < NUM_TUBE_SPEEDS; i++)
+            add_radio_item(sub, tube_speeds[i].name, IDM_TUBE_SPEED, i, tube_speed_num);
+        al_append_menu_item(menu, "Tube speed", 0, 0, NULL, sub);
+        return menu;
     }
-    for (int i = 0; i < num_tubes; ++i) {
-        map[i].label = tubes[i].name;
-        map[i].itemno = i;
+    else {
+        log_fatal("gui-allegro: out of memory");
+        exit(1);
     }
-    add_sorted_set(menu, map, num_tubes, IDM_TUBE, curtube);
-    for (int i = 0; i < NUM_TUBE_SPEEDS; i++)
-        add_radio_item(sub, tube_speeds[i].name, IDM_TUBE_SPEED, i, tube_speed_num);
-    al_append_menu_item(menu, "Tube speed", 0, 0, NULL, sub);
-    return menu;
 }
 
 static const char *mode7_font_files[] = { "saa5050", "brandy", "basicsdl", "original", NULL };
@@ -499,9 +527,7 @@ static ALLEGRO_MENU *create_keyboard_menu(void)
 static ALLEGRO_MENU *create_joystick_menu(int joystick)
 {
     ALLEGRO_MENU *menu = al_create_menu();
-    int i;
-
-    for (i = 0; i < joystick_count; i++) if (joystick_names[i])
+    for (int i = 0; i < joystick_count; i++) if (joystick_names[i])
         add_checkbox_item(menu, joystick_names[i], menu_id_num(IDM_JOYSTICK + joystick, i), i == joystick_index[joystick]);
     return menu;
 }
@@ -509,9 +535,7 @@ static ALLEGRO_MENU *create_joystick_menu(int joystick)
 static ALLEGRO_MENU *create_joymap_menu(int joystick)
 {
     ALLEGRO_MENU *menu = al_create_menu();
-    int i;
-
-    for (i = 0; i < joymap_count; i++)
+    for (int i = 0; i < joymap_count; i++)
         add_checkbox_item(menu, joymaps[i].name, menu_id_num(IDM_JOYMAP + joystick, i), i == joymap_index[joystick]);
     return menu;
 }
@@ -568,11 +592,9 @@ static ALLEGRO_MENU *create_settings_menu(void)
 
 static ALLEGRO_MENU *create_speed_menu(void)
 {
-    int i;
-
     ALLEGRO_MENU *menu = al_create_menu();
     add_radio_item(menu, "Paused", IDM_SPEED, EMU_SPEED_PAUSED, emuspeed);
-    for (i = 0; i < num_emu_speeds; i++)
+    for (int i = 0; i < num_emu_speeds; i++)
         add_radio_item(menu, emu_speeds[i].name, IDM_SPEED, i, emuspeed);
     add_radio_item(menu, "Full-speed", IDM_SPEED, EMU_SPEED_FULL, emuspeed);
     add_checkbox_item(menu, "Auto Frameskip", IDM_AUTOSKIP, autoskip);
@@ -634,50 +656,24 @@ static int radio_event_with_deselect(ALLEGRO_EVENT *event, int current)
     return num;
 }
 
-static void file_load_state(ALLEGRO_EVENT *event)
+static void file_chooser_generic(ALLEGRO_EVENT *event, const char *initial_path, const char *title, const char *patterns, int flags, void (*callback)(const char *))
 {
-    ALLEGRO_DISPLAY *display = (ALLEGRO_DISPLAY *)(event->user.data2);
-    ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(savestate_name, "Load state from file", "*.snp", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST);
+    ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(initial_path, title, patterns, flags);
     if (chooser) {
+        ALLEGRO_DISPLAY *display = (ALLEGRO_DISPLAY *)(event->user.data2);
         if (al_show_native_file_dialog(display, chooser)) {
             if (al_get_native_file_dialog_count(chooser) > 0)
-                savestate_load(al_get_native_file_dialog_path(chooser, 0));
-        }
-        al_destroy_native_file_dialog(chooser);
-    }
-}
-
-static void file_save_state(ALLEGRO_EVENT *event)
-{
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
-
-    if ((chooser = al_create_native_file_dialog(savestate_name, "Save state to file", "*.snp", ALLEGRO_FILECHOOSER_SAVE))) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
-        if (al_show_native_file_dialog(display, chooser)) {
-            if (al_get_native_file_dialog_count(chooser) > 0)
-                savestate_save(al_get_native_file_dialog_path(chooser, 0));
+                callback(al_get_native_file_dialog_path(chooser, 0));
         }
         al_destroy_native_file_dialog(chooser);
     }
 }
 
-static void file_save_scrshot(ALLEGRO_EVENT *event)
+static void file_save_scrshot(const char *path)
 {
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
-
-    if ((chooser = al_create_native_file_dialog(savestate_name, "Save screenshot to file", "*.bmp;*.pcx;*.tga;*.png;*.jpg", ALLEGRO_FILECHOOSER_SAVE))) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
-        if (al_show_native_file_dialog(display, chooser)) {
-            if (al_get_native_file_dialog_count(chooser) > 0) {
-                strncpy(vid_scrshotname, al_get_native_file_dialog_path(chooser, 0), sizeof vid_scrshotname-1);
-                vid_scrshotname[sizeof vid_scrshotname-1] = 0;
-                vid_savescrshot = 2;
-            }
-        }
-        al_destroy_native_file_dialog(chooser);
-    }
+    strncpy(vid_scrshotname, path, sizeof vid_scrshotname-1);
+    vid_scrshotname[sizeof vid_scrshotname-1] = 0;
+    vid_savescrshot = 2;
 }
 
 static void file_print_close_file(void)
@@ -699,54 +695,60 @@ static void file_print_close_pipe(void)
     print_dest = PDEST_NONE;
 }
 
-static void file_print(ALLEGRO_EVENT *event)
+static void file_print_open(ALLEGRO_EVENT *event)
 {
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
+    ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(savestate_name, "Print to file", "*.prn", ALLEGRO_FILECHOOSER_SAVE);
+    if (chooser) {
+        ALLEGRO_DISPLAY *display = (ALLEGRO_DISPLAY *)(event->user.data2);
+        while (al_show_native_file_dialog(display, chooser)) {
+            if (al_get_native_file_dialog_count(chooser) <= 0)
+                break;
+            if ((prt_fp = fopen(al_get_native_file_dialog_path(chooser, 0), "wb")))
+                break;
+        }
+        al_destroy_native_file_dialog(chooser);
+    }
+    if (prt_fp)
+        print_dest = PDEST_FILE;
+    else
+        al_set_menu_item_flags((ALLEGRO_MENU *)(event->user.data3), IDM_FILE_PRINT, 0);
+}
 
+static void file_print_file(ALLEGRO_EVENT *event)
+{
     switch(print_dest) {
         case PDEST_PIPE:
             file_print_close_pipe();
             al_set_menu_item_flags((ALLEGRO_MENU *)(event->user.data3), IDM_FILE_PCMD, 0);
             /* FALL THROUGH */
         case PDEST_NONE:
-            if ((chooser = al_create_native_file_dialog(savestate_name, "Print to file", "*.prn", ALLEGRO_FILECHOOSER_SAVE))) {
-                display = (ALLEGRO_DISPLAY *)(event->user.data2);
-                while (al_show_native_file_dialog(display, chooser)) {
-                    if (al_get_native_file_dialog_count(chooser) <= 0)
-                        break;
-                    if ((prt_fp = fopen(al_get_native_file_dialog_path(chooser, 0), "wb")))
-                        break;
-                }
-                al_destroy_native_file_dialog(chooser);
-            }
-            if (prt_fp)
-                print_dest = PDEST_FILE;
-            else
-                al_set_menu_item_flags((ALLEGRO_MENU *)(event->user.data3), IDM_FILE_PRINT, 0);
+            file_print_open(event);
             break;
         case PDEST_FILE:
             file_print_close_file();
     }
 }
 
-static void file_print_pipe(ALLEGRO_EVENT *event)
+static void file_print_open_pipe(ALLEGRO_EVENT *event)
 {
-    const char *pcmd;
+    const char *pcmd = get_config_string(NULL, "printcmd", "lp");
+    if ((prt_fp = popen(pcmd, "w")))
+        print_dest = PDEST_PIPE;
+    else {
+        log_error("unable to start print command '%s': %s", pcmd, strerror(errno));
+        al_set_menu_item_flags((ALLEGRO_MENU *)(event->user.data3), IDM_FILE_PCMD, 0);
+    }
+}
 
+static void file_print_pipe(ALLEGRO_EVENT *event)
+{
     switch(print_dest) {
         case PDEST_FILE:
             file_print_close_file();
             al_set_menu_item_flags((ALLEGRO_MENU *)(event->user.data3), IDM_FILE_PRINT, 0);
             /* FALL THROUGH */
         case PDEST_NONE:
-            pcmd = get_config_string(NULL, "printcmd", "lp");
-            if ((prt_fp = popen(pcmd, "w")))
-                print_dest = PDEST_PIPE;
-            else {
-                log_error("unable to start print command '%s': %s", pcmd, strerror(errno));
-                al_set_menu_item_flags((ALLEGRO_MENU *)(event->user.data3), IDM_FILE_PCMD, 0);
-            }
+            file_print_open_pipe(event);
             break;
         case PDEST_PIPE:
             file_print_close_pipe();
@@ -755,20 +757,21 @@ static void file_print_pipe(ALLEGRO_EVENT *event)
 
 static void serial_rec(ALLEGRO_EVENT *event)
 {
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
 
     if (sysacia_fp)
         sysacia_rec_stop();
-    else if ((chooser = al_create_native_file_dialog(savestate_name, "Record serial to file", "*.txt", ALLEGRO_FILECHOOSER_SAVE))) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
-        while (al_show_native_file_dialog(display, chooser)) {
-            if (al_get_native_file_dialog_count(chooser) <= 0)
-                break;
-            if (sysacia_rec_start(al_get_native_file_dialog_path(chooser, 0)))
-                break;
+    else {
+        ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(savestate_name, "Record serial to file", "*.txt", ALLEGRO_FILECHOOSER_SAVE);
+        if (chooser) {
+            ALLEGRO_DISPLAY *display = (ALLEGRO_DISPLAY *)(event->user.data2);
+            while (al_show_native_file_dialog(display, chooser)) {
+                if (al_get_native_file_dialog_count(chooser) <= 0)
+                    break;
+                if (sysacia_rec_start(al_get_native_file_dialog_path(chooser, 0)))
+                    break;
+            }
+            al_destroy_native_file_dialog(chooser);
         }
-        al_destroy_native_file_dialog(chooser);
     }
 }
 
@@ -807,10 +810,8 @@ static void edit_paste_start(ALLEGRO_EVENT *event)
 
 static void edit_print_clip(ALLEGRO_EVENT *event)
 {
-    ALLEGRO_DISPLAY *display;
-
     if (prt_clip_str) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
+        ALLEGRO_DISPLAY *display = (ALLEGRO_DISPLAY *)(event->user.data2);
         al_set_clipboard_text(display, al_cstr(prt_clip_str));
         al_ustr_free(prt_clip_str);
         prt_clip_str = NULL;
@@ -903,19 +904,16 @@ static void disc_choose_new(ALLEGRO_EVENT *event, const char *ext)
 
 static void disc_choose(ALLEGRO_EVENT *event, const char *opname, const char *exts, int flags)
 {
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
+    int drive = menu_get_num(event);
     ALLEGRO_PATH *apath;
-    int drive;
     const char *fpath;
-    char title[70];
-
-    drive = menu_get_num(event);
     if (!(apath = discfns[drive]) || !(fpath = al_path_cstr(apath, ALLEGRO_NATIVE_PATH_SEP)))
         fpath = ".";
+    char title[70];
     snprintf(title, sizeof title, "Choose a disc to %s drive %d/%d", opname, drive, drive+2);
-    if ((chooser = al_create_native_file_dialog(fpath, title, exts, flags))) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
+    ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(fpath, title, exts, flags);
+    if (chooser) {
+        ALLEGRO_DISPLAY *display = (ALLEGRO_DISPLAY *)(event->user.data2);
         if (al_show_native_file_dialog(display, chooser)) {
             if (al_get_native_file_dialog_count(chooser) > 0) {
                 ALLEGRO_PATH *path = al_create_path(al_get_native_file_dialog_path(chooser, 0));
@@ -960,42 +958,18 @@ static void disc_wprot(ALLEGRO_EVENT *event)
     writeprot[drive] = !writeprot[drive];
 }
 
-static void disc_mmb_load(ALLEGRO_EVENT *event)
+static void disc_mmb_load(const char *path)
 {
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
-    const char *fpath = mmb_fn ? mmb_fn : ".";
-
-    if ((chooser = al_create_native_file_dialog(fpath, "Choose an MMB file", "*.mmb", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST))) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
-        if (al_show_native_file_dialog(display, chooser)) {
-            if (al_get_native_file_dialog_count(chooser) > 0) {
-                char *fn = strdup(al_get_native_file_dialog_path(chooser, 0));
-                mmb_eject();
-                mmb_load(fn);
-            }
-        }
-        al_destroy_native_file_dialog(chooser);
-    }
+    char *fn = strdup(path);
+    mmb_eject();
+    mmb_load(fn);
 }
 
-static void disc_mmc_load(ALLEGRO_EVENT *event)
+static void disc_mmc_load(const char *path)
 {
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
-    const char *fpath = mmccard_fn ? mmccard_fn : ".";
-
-    if ((chooser = al_create_native_file_dialog(fpath, "Choose an MMC card image", "*", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST))) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
-        if (al_show_native_file_dialog(display, chooser)) {
-            if (al_get_native_file_dialog_count(chooser) > 0) {
-                char *fn = strdup(al_get_native_file_dialog_path(chooser, 0));
-                mmccard_eject();
-                mmccard_load(fn);
-            }
-        }
-        al_destroy_native_file_dialog(chooser);
-    }
+    char *fn = strdup(path);
+    mmccard_eject();
+    mmccard_load(fn);
 }
 
 static void disc_toggle_ide(ALLEGRO_EVENT *event)
@@ -1036,32 +1010,20 @@ static void disc_toggle_scsi(ALLEGRO_EVENT *event)
     }
 }
 
-static void disc_vdfs_root(ALLEGRO_EVENT *event)
+static void disc_vdfs_root(const char *path)
 {
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
-
-    if ((chooser = al_create_native_file_dialog(vdfs_get_root(), "Choose a folder to be the VDFS root", "*", ALLEGRO_FILECHOOSER_FOLDER))) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
-        if (al_show_native_file_dialog(display, chooser)) {
-            if (al_get_native_file_dialog_count(chooser) > 0) {
-                vdfs_set_root(al_get_native_file_dialog_path(chooser, 0));
-                config_save();
-            }
-        }
-    }
+    vdfs_set_root(path);
+    config_save();
 }
 
 static void tape_load_ui(ALLEGRO_EVENT *event)
 {
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
     const char *fpath;
-
     if (!tape_fn || !(fpath = al_path_cstr(tape_fn, ALLEGRO_NATIVE_PATH_SEP)))
         fpath = ".";
-    if ((chooser = al_create_native_file_dialog(fpath, "Choose a tape to load", "*.uef;*.csw", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST))) {
-        display = (ALLEGRO_DISPLAY *)(event->user.data2);
+    ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(fpath, "Choose a tape to load", "*.uef;*.csw", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST);
+    if (chooser) {
+        ALLEGRO_DISPLAY *display = (ALLEGRO_DISPLAY *)(event->user.data2);
         if (al_show_native_file_dialog(display, chooser)) {
             if (al_get_native_file_dialog_count(chooser) > 0) {
                 tape_close();
@@ -1108,23 +1070,20 @@ static void tape_fast(ALLEGRO_EVENT *event)
 
 static void rom_load(ALLEGRO_EVENT *event)
 {
-    rom_slot_t *slotp;
-    int slot;
-    char tempname[PATH_MAX], label[ROM_LABEL_LEN];
-    ALLEGRO_FILECHOOSER *chooser;
-    ALLEGRO_DISPLAY *display;
-
-    slot = menu_get_num(event);
-    slotp = rom_slots + slot;
+    int slot = menu_get_num(event);
+    rom_slot_t *slotp = rom_slots + slot;
     if (!slotp->locked) {
+        char tempname[PATH_MAX];
         if (slotp->name)
             strncpy(tempname, slotp->name, sizeof tempname-1);
         else
             tempname[0] = 0;
-        if ((chooser = al_create_native_file_dialog(tempname, "Choose a ROM to load", "*.rom", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST))) {
-            display = (ALLEGRO_DISPLAY *)(event->user.data2);
+        ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(tempname, "Choose a ROM to load", "*.rom", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST);
+        if (chooser) {
+            ALLEGRO_DISPLAY *display = (ALLEGRO_DISPLAY *)(event->user.data2);
             if (al_show_native_file_dialog(display, chooser)) {
                 if (al_get_native_file_dialog_count(chooser) > 0) {
+                    char label[ROM_LABEL_LEN];
                     ALLEGRO_PATH *path = al_create_path(al_get_native_file_dialog_path(chooser, 0));
                     mem_clearrom(slot);
                     mem_loadrom(slot, al_get_path_filename(path), al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP), 0);
@@ -1246,16 +1205,19 @@ void gui_allegro_event(ALLEGRO_EVENT *event)
             update_rom_menu();
             break;
         case IDM_FILE_LOAD_STATE:
-            file_load_state(event);
+            file_chooser_generic(event, savestate_name, "Load state from file", "*.snp", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST, savestate_load);
             break;
         case IDM_FILE_SAVE_STATE:
-            file_save_state(event);
+            file_chooser_generic(event, savestate_name, "Save state to file", "*.snp", ALLEGRO_FILECHOOSER_SAVE, savestate_save);
             break;
         case IDM_FILE_SCREEN_SHOT:
-            file_save_scrshot(event);
+            file_chooser_generic(event, vid_scrshotname, "Save screenshot to file", "*.bmp;*.pcx;*.tga;*.png;*.jpg", ALLEGRO_FILECHOOSER_SAVE, file_save_scrshot);
+            break;
+        case IDM_FILE_SCREEN_TEXT:
+            file_chooser_generic(event, savestate_name, "Save screen as text to file", "*.txt", ALLEGRO_FILECHOOSER_SAVE, textsave);
             break;
         case IDM_FILE_PRINT:
-            file_print(event);
+            file_print_file(event);
             break;
         case IDM_FILE_PCMD:
             file_print_pipe(event);
@@ -1288,7 +1250,7 @@ void gui_allegro_event(ALLEGRO_EVENT *event)
             disc_choose(event, "load into", all_dext, ALLEGRO_FILECHOOSER_FILE_MUST_EXIST);
             break;
         case IDM_DISC_MMB_LOAD:
-            disc_mmb_load(event);
+            file_chooser_generic(event, mmb_fn ? mmb_fn : ".", "Choose an MMB file", "*.mmb", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST, disc_mmb_load);
             break;
         case IDM_DISC_EJECT:
             disc_eject(event);
@@ -1297,7 +1259,7 @@ void gui_allegro_event(ALLEGRO_EVENT *event)
             mmb_eject();
             break;
         case IDM_DISC_MMC_LOAD:
-            disc_mmc_load(event);
+            file_chooser_generic(event, mmccard_fn ? mmccard_fn : ".", "Choose an MMC card image", "*", ALLEGRO_FILECHOOSER_FILE_MUST_EXIST, disc_mmc_load);
             break;
         case IDM_DISC_MMC_EJECT:
             mmccard_eject();
@@ -1345,7 +1307,7 @@ void gui_allegro_event(ALLEGRO_EVENT *event)
             vdfs_enabled = !vdfs_enabled;
             break;
         case IDM_DISC_VDFS_ROOT:
-            disc_vdfs_root(event);
+            file_chooser_generic(event, vdfs_get_root(), "Choose a folder to be the VDFS root", "*", ALLEGRO_FILECHOOSER_FOLDER, disc_vdfs_root);
             break;
         case IDM_TAPE_LOAD:
             tape_load_ui(event);
diff --git a/src/gui-allegro.h b/src/gui-allegro.h
index a7957675..9d4cd2bb 100644
--- a/src/gui-allegro.h
+++ b/src/gui-allegro.h
@@ -7,6 +7,7 @@ typedef enum {
     IDM_FILE_LOAD_STATE,
     IDM_FILE_SAVE_STATE,
     IDM_FILE_SCREEN_SHOT,
+    IDM_FILE_SCREEN_TEXT,
     IDM_FILE_PRINT,
     IDM_FILE_PCMD,
     IDM_FILE_SERIAL,
diff --git a/src/main.c b/src/main.c
index 4a58247b..72a89b1c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -172,7 +172,8 @@ static double main_calc_timer(int speed)
 
 static int main_speed_cmp(const void *va, const void *vb)
 {
-    return ((const emu_speed_t *)va)->multiplier - ((const emu_speed_t *)vb)->multiplier;
+    double res = ((const emu_speed_t *)va)->multiplier - ((const emu_speed_t *)vb)->multiplier;
+    return (int)round(res);
 }
 
 static void main_load_speeds(void)
diff --git a/src/mc68000tube.c b/src/mc68000tube.c
index f40e032c..38499a36 100644
--- a/src/mc68000tube.c
+++ b/src/mc68000tube.c
@@ -260,9 +260,38 @@ static void dbg_reg_set(int which, uint32_t value)
     return m68k_set_reg(which, value);
 }
 
+static size_t dbg_decode_flags(uint32_t flags, char *buf, size_t bufsize)
+{
+    if (bufsize >= 16) {
+        buf[0] = flags & 0x8000 ? 'T' : '-';
+        buf[1] = '.';
+        buf[2] = flags & 0x2000 ? 'S' : '-';
+        buf[3] = '.';
+        buf[4] = '.';
+        buf[5] = flags & 0x400 ? '2' : '-';
+        buf[6] = flags & 0x200 ? '1' : '-';
+        buf[7] = flags & 0x100 ? '0' : '-';
+        buf[8] = '.';
+        buf[9] = '.';
+        buf[10] = '.';
+        buf[11] = flags & 0x10 ? 'X' : '-';
+        buf[12] = flags & 0x08 ? 'N' : '-';
+        buf[13] = flags & 0x04 ? 'Z' : '-';
+        buf[14] = flags & 0x02 ? 'V' : '-';
+        buf[15] = flags & 0x01 ? 'C' : '-';
+        return 16;
+    }
+    return 0;
+}
+
 static size_t dbg_reg_print(int which, char *buf, size_t bufsize)
 {
-    return snprintf(buf, bufsize, "%08X", m68k_get_reg(NULL, which));
+  uint32_t value = m68k_get_reg(NULL, which);
+
+  if (which == 17)  // Status register
+      return dbg_decode_flags(value, buf, bufsize);
+
+    return snprintf(buf, bufsize, "%08X", value);
 }
 
 static void dbg_reg_parse(int which, const char *strval)
diff --git a/src/model.c b/src/model.c
index 7cdad318..0dd5b84c 100644
--- a/src/model.c
+++ b/src/model.c
@@ -236,6 +236,7 @@ void model_loadcfg(void)
             MODEL *ptr = models + num;
             ptr->cfgsect = sect;
             ptr->name = get_config_string(sect, "name", sect);
+            ptr->group = al_get_config_value(bem_cfg, sect, "group");
             ptr->fdc_type = model_find_fdc(get_config_string(sect, "fdc", "none"), ptr->name);
             ptr->x65c02  = get_config_bool(sect, "65c02",  false);
             ptr->bplus   = get_config_bool(sect, "b+",   false);
diff --git a/src/model.h b/src/model.h
index 46a7154d..3a92a994 100644
--- a/src/model.h
+++ b/src/model.h
@@ -28,6 +28,7 @@ typedef struct
     const char *name;
     const char *os;
     const char *cmos;
+    const char *group;
     rom_setup_t *romsetup;
     fdc_type_t fdc_type;
     uint8_t x65c02:1;
@@ -43,6 +44,8 @@ typedef struct
 
 extern MODEL *models;
 extern int model_count;
+extern const char **model_groups;
+extern int model_ngroup;
 
 typedef struct
 {
diff --git a/src/sprow.c b/src/sprow.c
index 8c92b464..e8b7d1a3 100644
--- a/src/sprow.c
+++ b/src/sprow.c
@@ -272,11 +272,11 @@ static uint32_t sprow_dbg_disassemble(cpu_debug_t *cpu, uint32_t addr, char *buf
   bufsize -= len;
   strncpy(buf, dest, bufsize);
 
-  if ((len = strlen(buf)) < 40)
-  {
-    memset(buf+len, ' ', 40-len);
-    buf[40] = 0;
-  }
+//if ((len = strlen(buf)) < 40)
+//{
+//  memset(buf+len, ' ', 40-len);
+//  buf[40] = 0;
+//}
   return addr + 4;
 };
 
diff --git a/src/textsave.c b/src/textsave.c
new file mode 100644
index 00000000..d6e0a307
--- /dev/null
+++ b/src/textsave.c
@@ -0,0 +1,498 @@
+#define _DEBUG
+#include "b-em.h"
+#include "textsave.h"
+#include "mem.h"
+#include "via.h"
+#include "sysvia.h"
+#include "video.h"
+
+/*
+ * Save text screen to file.
+ *
+ * This can be called from the GUI to save the contents of the screen
+ * memory to a file on the host.  This works similarly to calling
+ * OSBYTE &87 for each location on the screen and writing the result
+ * to a file, except that trailing spaces and trailing black lines
+ * are omitted.
+ */
+
+/*
+ * Mode 7 (teletext).
+ *
+ * This is easy as the characters are stored in memory as ASCII codes.
+ * The only complexity is the way memory wrap-around works when the
+ * screen is hardware scrolled.
+ */
+
+static void textsave_teletext(const char *filename, FILE *fp, uint_least16_t mem_addr)
+{
+    int cols = crtc[1];
+    int rows = crtc[6];
+    uint_least8_t newlines = 0;
+    for (int row = 0; row < rows; ++row) {
+        uint_least8_t spaces = 0;
+        for (int col = 0; col < cols; ++col) {
+            uint_least32_t ram_addr;
+            if (mem_addr & 0x2000)
+                ram_addr = ttxbank | (mem_addr & 0x3FF) | vidbank;
+            else
+                ram_addr = (mem_addr << 3) & 0x7fff;
+            unsigned ch = ram[ram_addr];
+            if (ch == 0 || ch == ' ')
+                ++spaces;
+            else {
+                while (newlines) {
+                    putc('\n', fp);
+                    --newlines;
+                }
+                while (spaces) {
+                    putc(' ', fp);
+                    --spaces;
+                }
+                putc(ch, fp);
+            }
+            ++mem_addr;
+        }
+        ++newlines;
+    }
+    if (newlines)
+        putc('\n', fp);
+}
+
+/*
+ * Character definitions for working with the bitmap modes copied
+ * from the MOS 1.20 ROM.  As each characters is an 8x8 grid, the
+ * whole of one characters will fit in a 64-bit integer.
+ */
+
+static const uint64_t charset[] = {
+    0x0000000000000000, /*  32 20 ' ' */
+    0x1818181818001800, /*  33 21 '!' */
+    0x6c6c6c0000000000, /*  34 22 '"' */
+    0x36367f367f363600, /*  35 23 '#' */
+    0x0c3f683e0b7e1800, /*  36 24 '$' */
+    0x60660c1830660600, /*  37 25 '%' */
+    0x386c6c386d663b00, /*  38 26 '&' */
+    0x0c18300000000000, /*  39 27 ''' */
+    0x0c18303030180c00, /*  40 28 '(' */
+    0x30180c0c0c183000, /*  41 29 ')' */
+    0x00187e3c7e180000, /*  42 2A '*' */
+    0x0018187e18180000, /*  43 2B '+' */
+    0x0000000000181830, /*  44 2C ',' */
+    0x0000007e00000000, /*  45 2D '-' */
+    0x0000000000181800, /*  46 2E '.' */
+    0x00060c1830600000, /*  47 2F '/' */
+    0x3c666e7e76663c00, /*  48 30 '0' */
+    0x1838181818187e00, /*  49 31 '1' */
+    0x3c66060c18307e00, /*  50 32 '2' */
+    0x3c66061c06663c00, /*  51 33 '3' */
+    0x0c1c3c6c7e0c0c00, /*  52 34 '4' */
+    0x7e607c0606663c00, /*  53 35 '5' */
+    0x1c30607c66663c00, /*  54 36 '6' */
+    0x7e060c1830303000, /*  55 37 '7' */
+    0x3c66663c66663c00, /*  56 38 '8' */
+    0x3c66663e060c3800, /*  57 39 '9' */
+    0x0000181800181800, /*  58 3A ':' */
+    0x0000181800181830, /*  59 3B ';' */
+    0x0c18306030180c00, /*  60 3C '<' */
+    0x00007e007e000000, /*  61 3D '=' */
+    0x30180c060c183000, /*  62 3E '>' */
+    0x3c660c1818001800, /*  63 3F '?' */
+    0x3c666e6a6e603c00, /*  64 40 '@' */
+    0x3c66667e66666600, /*  65 41 'A' */
+    0x7c66667c66667c00, /*  66 42 'B' */
+    0x3c66606060663c00, /*  67 43 'C' */
+    0x786c6666666c7800, /*  68 44 'D' */
+    0x7e60607c60607e00, /*  69 45 'E' */
+    0x7e60607c60606000, /*  70 46 'F' */
+    0x3c66606e66663c00, /*  71 47 'G' */
+    0x6666667e66666600, /*  72 48 'H' */
+    0x7e18181818187e00, /*  73 49 'I' */
+    0x3e0c0c0c0c6c3800, /*  74 4A 'J' */
+    0x666c7870786c6600, /*  75 4B 'K' */
+    0x6060606060607e00, /*  76 4C 'L' */
+    0x63777f6b6b636300, /*  77 4D 'M' */
+    0x6666767e6e666600, /*  78 4E 'N' */
+    0x3c66666666663c00, /*  79 4F 'O' */
+    0x7c66667c60606000, /*  80 50 'P' */
+    0x3c6666666a6c3600, /*  81 51 'Q' */
+    0x7c66667c6c666600, /*  82 52 'R' */
+    0x3c66603c06663c00, /*  83 53 'S' */
+    0x7e18181818181800, /*  84 54 'T' */
+    0x6666666666663c00, /*  85 55 'U' */
+    0x66666666663c1800, /*  86 56 'V' */
+    0x63636b6b7f776300, /*  87 57 'W' */
+    0x66663c183c666600, /*  88 58 'X' */
+    0x6666663c18181800, /*  89 59 'Y' */
+    0x7e060c1830607e00, /*  90 5A 'Z' */
+    0x7c60606060607c00, /*  91 5B '[' */
+    0x006030180c060000, /*  92 5C '\' */
+    0x3e06060606063e00, /*  93 5D ']' */
+    0x183c664200000000, /*  94 5E '^' */
+    0x00000000000000ff, /*  95 5F '_' */
+    0x1c36307c30307e00, /*  96 60 '`' */
+    0x00003c063e663e00, /*  97 61 'a' */
+    0x60607c6666667c00, /*  98 62 'b' */
+    0x00003c6660663c00, /*  99 63 'c' */
+    0x06063e6666663e00, /* 100 64 'd' */
+    0x00003c667e603c00, /* 101 65 'e' */
+    0x1c30307c30303000, /* 102 66 'f' */
+    0x00003e66663e063c, /* 103 67 'g' */
+    0x60607c6666666600, /* 104 68 'h' */
+    0x1800381818183c00, /* 105 69 'i' */
+    0x1800381818181870, /* 106 6A 'j' */
+    0x6060666c786c6600, /* 107 6B 'k' */
+    0x3818181818183c00, /* 108 6C 'l' */
+    0x0000367f6b6b6300, /* 109 6D 'm' */
+    0x00007c6666666600, /* 110 6E 'n' */
+    0x00003c6666663c00, /* 111 6F 'o' */
+    0x00007c66667c6060, /* 112 70 'p' */
+    0x00003e66663e0607, /* 113 71 'q' */
+    0x00006c7660606000, /* 114 72 'r' */
+    0x00003e603c067c00, /* 115 73 's' */
+    0x30307c3030301c00, /* 116 74 't' */
+    0x0000666666663e00, /* 117 75 'u' */
+    0x00006666663c1800, /* 118 76 'v' */
+    0x0000636b6b7f3600, /* 119 77 'w' */
+    0x0000663c183c6600, /* 120 78 'x' */
+    0x00006666663e063c, /* 121 79 'y' */
+    0x00007e0c18307e00, /* 122 7A 'z' */
+    0x0c18187018180c00, /* 123 7B '{' */
+    0x1818180018181800, /* 124 7C '|' */
+    0x3018180e18183000, /* 125 7D '}' */
+    0x316b460000000000, /* 126 7E '~' */
+    0xffffffffffffffff, /* 127 7F     */
+    0x66003c667e666600, /* 128 80     */
+    0x6666003c667e6600, /* 128 80     */
+    0x3c663c667e666600, /* 129 81     */
+    0x3c663c3c667e6600, /* 129 81     */
+    0x3f66667f66666700, /* 130 82     */
+    0x3c66606060663c60, /* 131 83     */
+    0x3c666060663c3060, /* 131 83     */
+    0x0c187e607c607e00, /* 132 84     */
+    0x663c666666663c00, /* 133 85     */
+    0x6600666666663c00, /* 134 86     */
+    0x7ec39db19dc37e00, /* 135 87     */
+    0x3c4299a1a199423c, /* 135 87     */
+    0x0018387f38180000, /* 136 88     */
+    0x00181cfe1c180000, /* 137 89     */
+    0x181818187e3c1800, /* 138 8A     */
+    0x00183c7e18181818, /* 139 8B     */
+    0x30183c063e663e00, /* 140 8C     */
+    0x30183c667e603c00, /* 141 8D     */
+    0x66003c667e603c00, /* 142 8E     */
+    0x3c663c667e603c00, /* 143 8F     */
+    0x66003c063e663e00, /* 144 90     */
+    0x3c663c063e663e00, /* 145 91     */
+    0x00003f0d3f6c3f00, /* 146 92     */
+    0x00003c6660663c60, /* 147 93     */
+    0x0c183c667e603c00, /* 148 94     */
+    0x6600003c66663c00, /* 149 95     */
+    0x6600666666663e00, /* 150 96     */
+    0x6600006666663e00, /* 150 96     */
+    0x3018003818183c00, /* 151 97     */
+    0x3c66003818183c00, /* 152 98     */
+    0x3018003c66663c00, /* 153 99     */
+    0x3c66003c66663c00, /* 154 9A     */
+    0x3018006666663e00, /* 155 9B     */
+    0x3c66006666663e00, /* 156 9C     */
+    0x66006666663e063c, /* 157 9D     */
+    0x00663c66663c6600, /* 158 9E     */
+    0x3c603c663c063c00, /* 159 9F     */
+    0x3c663c0000000000, /* 160 A0     */
+    0x0000001818181818, /* 161 A1     */
+    0x0000001f00000000, /* 162 A2     */
+    0x0000001f18181818, /* 163 A3     */
+    0x000000f800000000, /* 164 A4     */
+    0x000000f818181818, /* 165 A5     */
+    0x000000ff00000000, /* 166 A6     */
+    0x000000ff18181818, /* 167 A7     */
+    0x1818181800000000, /* 168 A8     */
+    0x1818181818181818, /* 169 A9     */
+    0x1818181f00000000, /* 170 AA     */
+    0x1818181f18181818, /* 171 AB     */
+    0x181818f800000000, /* 172 AC     */
+    0x181818f818181818, /* 173 AD     */
+    0x181818ff00000000, /* 174 AE     */
+    0x181818ff18181818, /* 175 AF     */
+    0x000000070c181818, /* 176 B0     */
+    0x000000e030181818, /* 177 B1     */
+    0x18180c0700000000, /* 178 B2     */
+    0x181830e000000000, /* 179 B3     */
+    0x1800181830663c00, /* 180 B4     */
+    0x1800181818181800, /* 181 B5     */
+    0x366c0066766e6600, /* 182 B6     */
+    0x366c007c66666600, /* 183 B7     */
+    0x187e181818181800, /* 184 B8     */
+    0x187e1818187e1800, /* 185 B9     */
+    0x1818180000000000, /* 186 BA     */
+    0x1800000000000000, /* 186 BA     */
+    0x30180c0000000000, /* 187 BB     */
+    0x3018000000000000, /* 187 BB     */
+    0x3f7b7b3b1b1b1f00, /* 188 BC     */
+    0x033e767636363e00, /* 188 BC     */
+    0x0000001818000000, /* 189 BD     */
+    0x03030606761c0c00, /* 190 BE     */
+    0xaa55aa55aa55aa55, /* 191 BF     */
+    0x3e63676b73633e00, /* 192 C0     */
+    0x1c3663637f636300, /* 193 C1     */
+    0x7e33333e33337e00, /* 194 C2     */
+    0x7f63606060606000, /* 195 C3     */
+    0x1c1c363663637f00, /* 196 C4     */
+    0x7f33303e30337f00, /* 197 C5     */
+    0x7e660c1830667e00, /* 198 C6     */
+    0x7733333f33337700, /* 199 C7     */
+    0x3e63637f63633e00, /* 200 C8     */
+    0x3c18181818183c00, /* 201 C9     */
+    0x63666c786c666300, /* 202 CA     */
+    0x1c1c363663636300, /* 203 CB     */
+    0x63777f6b63636300, /* 204 CC     */
+    0x63737b6f67636300, /* 205 CD     */
+    0x7e00003c00007e00, /* 206 CE     */
+    0x3e63636363633e00, /* 207 CF     */
+    0x7f36363636363600, /* 208 D0     */
+    0x7e33333e30307800, /* 209 D1     */
+    0x7f63301830637f00, /* 210 D2     */
+    0x7e5a181818181800, /* 211 D3     */
+    0x6666663c18183c00, /* 212 D4     */
+    0x3e083e6b3e083e00, /* 213 D5     */
+    0x6363361c36636300, /* 214 D6     */
+    0x3e086b6b3e083e00, /* 215 D7     */
+    0x3e63636336366300, /* 216 D8     */
+    0x7f636336361c1c00, /* 217 D9     */
+    0x18187e1818007e00, /* 218 DA     */
+    0x007e0018187e1818, /* 219 DB     */
+    0x1818181818181800, /* 220 DC     */
+    0x3636363636363600, /* 221 DD     */
+    0x0066666666663c00, /* 222 DE     */
+    0x003c666666666600, /* 223 DF     */
+    0x00033e676b733e60, /* 224 E0     */
+    0x00023c6e7666bc00, /* 224 E0     */
+    0x00003b6e666e3b00, /* 225 E1     */
+    0x1e33333e33333e60, /* 226 E2     */
+    0x000066361c183030, /* 227 E3     */
+    0x3c60303c66663c00, /* 228 E4     */
+    0x00001e301c301e00, /* 229 E5     */
+    0x3e0c183060603e06, /* 230 E6     */
+    0x00007c6666660606, /* 231 E7     */
+    0x3c66667e66663c00, /* 232 E8     */
+    0x0000181818180c00, /* 233 E9     */
+    0x0000666c786c6600, /* 234 EA     */
+    0x6030181c36636300, /* 235 EB     */
+    0x0000333333333e60, /* 236 EC     */
+    0x000063331b1e1c00, /* 237 ED     */
+    0x3c60603c60603e06, /* 238 EE     */
+    0x0c3e603c603e060c, /* 238 EE     */
+    0x00003e6363633e00, /* 239 EF     */
+    0x00007f3636363600, /* 240 F0     */
+    0x00003c66667c6060, /* 241 F1     */
+    0x00003f6666663c00, /* 242 F2     */
+    0x00007e1818180c00, /* 243 F3     */
+    0x0000733333331e00, /* 244 F4     */
+    0x00003e6b6b3e1818, /* 245 F5     */
+    0x000066361c1c3633, /* 246 F6     */
+    0x0000636b6b3e1818, /* 247 F7     */
+    0x000036636b7f3600, /* 248 F8     */
+    0x000063636b7f3600, /* 248 F8     */
+    0x380c063e66663c00, /* 249 F9     */
+    0x00316b46007f0000, /* 250 FA     */
+    0x007e007e007e0000, /* 251 FB     */
+    0x071c701c07007f00, /* 252 FC     */
+    0x060c7e187e306000, /* 253 FD     */
+    0x701c071c70007f00, /* 254 FE     */
+    0xffffffffffffffff, /* 255 FF     */
+};
+
+static const unsigned char charcodes[] =
+{
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+    0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+    0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+    0x80, 0x80, 0x81, 0x81, 0x82, 0x83, 0x83, 0x84,
+    0x85, 0x86, 0x87, 0x87, 0x88, 0x89, 0x8a, 0x8b,
+    0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93,
+    0x94, 0x95, 0x96, 0x96, 0x97, 0x98, 0x99, 0x9a,
+    0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2,
+    0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+    0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+    0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
+    0xba, 0xbb, 0xbb, 0xbc, 0xbc, 0xbd, 0xbe, 0xbf,
+    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+    0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+    0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+    0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+    0xe0, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
+    0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee,
+    0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5,
+    0xf6, 0xf7, 0xf8, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc,
+    0xfd, 0xfe, 0xff
+};
+
+/*
+ * Information about the screen modes.  This includes row, columns
+ * and the pixel format.
+ *
+ * 0 = 1-bit  per pixel,  2 colours, modea 0, 3, 4, 6
+ * 1 = 2-bits per pixel,  4 colours, modes 1 and 5
+ * 2 = 4-bits per pixel, 16 colours, modes 2
+ */
+
+static const uint8_t mode_rows[8] = { 32, 32, 32, 25, 32, 32, 25, 25 };
+static const uint8_t mode_cols[8] = { 80, 40, 20, 80, 40, 20, 40, 40 };
+static const uint8_t mode_pfmt[8] = {  0,  1,  2,  0,  0,  1,  0,  0 };
+
+/*
+ * Function to process one byte of memory in a 2bbp, four colour mode
+ * working out the equivalent monochrome pixels and shifting into the
+ * set being accumulated for the whole character.
+ */
+
+static uint64_t textsave_fcbits(uint64_t chbits, uint_least8_t bgmask, uint_least32_t cell_addr)
+{
+    uint_least8_t byte = (ram[(cell_addr & 0x7FFF) | vidbank]) ^ bgmask;
+    chbits = (chbits << 1) | ((byte & 0x88) ? 1 : 0);
+    chbits = (chbits << 1) | ((byte & 0x44) ? 1 : 0);
+    chbits = (chbits << 1) | ((byte & 0x22) ? 1 : 0);
+    chbits = (chbits << 1) | ((byte & 0x11) ? 1 : 0);
+    return chbits;
+}
+
+/*
+ * Function to process one byte of memory in a 4bbp, sixteen colour
+ * mode working out the equivalent monochrome pixels and shifting into
+ * the set being accumulated for the whole character.
+ */
+
+static uint64_t textsave_scbits(uint64_t chbits, uint_least8_t bgmask, uint_least32_t cell_addr)
+{
+    uint_least8_t byte = (ram[(cell_addr & 0x7FFF) | vidbank]) ^ bgmask;
+    chbits = (chbits << 1) | ((byte & 0xaa) ? 1 : 0);
+    chbits = (chbits << 1) | ((byte & 0x55) ? 1 : 0);
+    return chbits;
+}
+
+/* Non-teletext modes.
+ *
+ * This does the same scan over rows and columns as for teletext but
+ * instead of reading the character from the screen memmory a bitmap
+ * is built up representing the character cell on screen which is then
+ * compared with the character set in the table above.
+ */
+
+static void textsave_bitmap(const char *filename, FILE *fp, uint_least16_t mem_addr)
+{
+    uint_least32_t cell_addr = (mem_addr << 3);
+    uint_least8_t bpc = ram[0x34f];
+    uint_least8_t mode = ram[0x355];
+    uint_least8_t bgmask = ram[0x358];
+    uint_least8_t rows = mode_rows[mode];
+    uint_least8_t cols = mode_cols[mode];
+    uint_least8_t pfmt = mode_pfmt[mode];
+    log_debug("textsave: bpc=%d, mode=%d, bgmask=%02X, rows=%d, cols=%d, pfmt=%d\n", bpc, mode, bgmask, rows, cols, pfmt);
+
+    uint_least8_t newlines = 0;
+    for (int row = 0; row < rows; ++row) {
+        uint_least8_t spaces = 0;
+        for (int col = 0; col < cols; ++col) {
+            uint_least32_t line_addr = cell_addr;
+            if (line_addr & 0x8000)
+                line_addr -= screenlen[scrsize];
+            log_debug("textsave: col=%d, call_addr=%08X", col, line_addr);
+            uint64_t chbits = 0;
+            if (pfmt == 0) {
+                for (int line = 0; line < 8; ++line) {
+                    chbits = (chbits << 8) | ((ram[(line_addr & 0x7FFF) | vidbank]) ^ bgmask);
+                    ++line_addr;
+                }
+            }
+            else if (pfmt == 1) {
+                for (int line = 0; line < 8; ++line) {
+                    chbits = textsave_fcbits(chbits, bgmask, line_addr);
+                    chbits = textsave_fcbits(chbits, bgmask, line_addr + 8);
+                    ++line_addr;
+                }
+            }
+            else {
+                for (int line = 0; line < 8; ++line) {
+                    chbits = textsave_scbits(chbits, bgmask, line_addr);
+                    chbits = textsave_scbits(chbits, bgmask, line_addr + 8);
+                    chbits = textsave_scbits(chbits, bgmask, line_addr + 16);
+                    chbits = textsave_scbits(chbits, bgmask, line_addr + 24);
+                    ++line_addr;
+                }
+            }
+            log_debug("textsave: chbits=%016lx", chbits);
+            int ch = 0;
+            for (int i = 0; i < sizeof(charcodes); ++i) {
+                if (chbits == charset[i]) {
+                    ch = charcodes[i];
+                    break;
+                }
+            }
+            if (!ch) {
+                if (chbits == 0x66003c6666663c00) {
+                    /* This bit pattern is includes in both the
+                     * MOS 3.20 and MOS 3.50 character definitions,
+                     * but with a different code in each so we need
+                     * to work out which version of MOS is running.
+                     */
+                    uint_least8_t byte = rom[15*ROM_SIZE+0x3c29];
+                    log_debug("textsave: byte=%02X", byte);
+                    ch = byte ? 0x95 : 0x85;
+                }
+                else
+                    ch = ' ';
+            }
+            log_debug("textsave: ch=%02X '%c'", ch, ch);
+            if (ch == ' ')
+                ++spaces;
+            else {
+                while (newlines) {
+                    putc('\n', fp);
+                    --newlines;
+                }
+                while (spaces) {
+                    putc(' ', fp);
+                    --spaces;
+                }
+                putc(ch, fp);
+            }
+            cell_addr += bpc;
+        }
+        ++newlines;
+    }
+    if (newlines)
+        putc('\n', fp);
+}
+
+/*
+ * Main function.  This opens the file and then calls the
+ * appropriate teletext or non-teletext function based on
+ * the teletext bit in the Video ULA.
+ */
+
+void textsave(const char *filename)
+{
+    FILE *fp = fopen(filename, "w");
+    if (fp) {
+        uint_least16_t mem_addr = crtc[13] | (crtc[12] << 8);            
+        if (ula_ctrl & 2)
+            textsave_teletext(filename, fp, mem_addr);
+        else
+            textsave_bitmap(filename, fp, mem_addr);
+        fclose(fp);
+    }
+    else {
+        log_error("unable to open file %s: %s\n", filename, strerror(errno));
+    }
+}
+
diff --git a/src/textsave.h b/src/textsave.h
new file mode 100644
index 00000000..36147e3d
--- /dev/null
+++ b/src/textsave.h
@@ -0,0 +1 @@
+extern void textsave(const char *filename);
diff --git a/src/tube.c b/src/tube.c
index 4f822cad..0b8aeeab 100644
--- a/src/tube.c
+++ b/src/tube.c
@@ -110,10 +110,16 @@ void tube_updateints()
                 pdp11_interrupt(0x80, 7);
             else if (tube_type == TUBESPROW)
                 sprow_interrupt(2);
+            else if (tube_type == TUBE68000)
+                m68k_set_virq(5, 1);
         }
     }
     else if (tube_irq & 2)
+    {
         log_debug("tube: parasite NMI de-asserted");
+        if (tube_type == TUBE68000)
+            m68k_set_virq(5, 0);
+    }
 
     if (new_irq != tube_irq && tube_type == TUBE6809)
         tube_6809_int(new_irq);
diff --git a/src/video.c b/src/video.c
index 603234a5..60366f06 100644
--- a/src/video.c
+++ b/src/video.c
@@ -764,7 +764,7 @@ static void mode7_render(ALLEGRO_LOCKED_REGION *region, uint8_t dat)
 }
 
 uint16_t vidbank;
-static const int screenlen[4] = { 0x4000, 0x5000, 0x2000, 0x2800 };
+const uint_least16_t screenlen[4] = { 0x4000, 0x5000, 0x2000, 0x2800 };
 
 static int vsynctime;
 static int interline;
diff --git a/src/video.h b/src/video.h
index b919bb4d..c0a30788 100644
--- a/src/video.h
+++ b/src/video.h
@@ -16,7 +16,7 @@ extern uint8_t crtc[32];
 extern int crtc_i;
 
 extern int hc, vc, sc;
-extern uint16_t ma;
+extern uint16_t ma, ttxbank;
 extern uint64_t stopwatch_vblank;
 
 /*Video ULA (VIDPROC)*/
@@ -45,6 +45,7 @@ void video_loadstate(FILE *f);
 void nula_reset(void);
 
 extern uint16_t vidbank;
+extern const uint_least16_t screenlen[4];
 
 void mode7_makechars(void);
 bool mode7_loadchars(const char *fn);