From bd5139a78165bf7172b70f7efb41ab7475062c66 Mon Sep 17 00:00:00 2001 From: Rick Pontefract Date: Mon, 14 Oct 2024 17:08:57 +1300 Subject: [PATCH 01/12] Fix the 68000 CoPro IRQ and NMI code. Decode the 68000 flags for the debugger. Display the 68000 register names in the debugger. Correct the spacing in the debugger output. --- src/debugger.c | 4 +++- src/mc68000tube.c | 31 ++++++++++++++++++++++++++++++- src/tube.c | 6 ++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/debugger.c b/src/debugger.c index 0e2de0a2..bb81d2a9 100644 --- a/src/debugger.c +++ b/src/debugger.c @@ -1751,7 +1751,9 @@ 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 = ' '; diff --git a/src/mc68000tube.c b/src/mc68000tube.c index f40e032c..7c3c6093 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, "%s:%08X", reg_names[which], value); } static void dbg_reg_parse(int which, const char *strval) 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); From 8d830bf3d0eae834f965ffb03de425954d4d476f Mon Sep 17 00:00:00 2001 From: Rick Pontefract Date: Mon, 14 Oct 2024 17:12:08 +1300 Subject: [PATCH 02/12] Update to version 2.03 of the CiscOS ROM with fixes for NMI handling. --- roms/tube/CiscOS.rom | Bin 32768 -> 32768 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/roms/tube/CiscOS.rom b/roms/tube/CiscOS.rom index ce2bd80ed96acdaaee82d051c0a801edb51bbf60..97b9eaf2b9a9c5e1f6b25a38e86cf166d7d99067 100644 GIT binary patch literal 32768 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 literal 32768 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 BzNi2I From ed768fdc8e2ff5eeddcdfeb3810a123e15361f5b Mon Sep 17 00:00:00 2001 From: Rick Pontefract Date: Mon, 14 Oct 2024 19:43:03 +1300 Subject: [PATCH 03/12] Don't print the register name in dbg_reg_print() as they are already displayed by the regs command. Instead, add them to the trace output. Remove the padding from the sprow disassembly output as it is added in debugger.c. --- src/debugger.c | 8 +++++--- src/mc68000tube.c | 2 +- src/sprow.c | 10 +++++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/debugger.c b/src/debugger.c index bb81d2a9..70cde59c 100644 --- a/src/debugger.c +++ b/src/debugger.c @@ -1755,14 +1755,16 @@ static void debug_trace_write(cpu_debug_t *cpu, uint32_t addr, FILE *fp) 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/mc68000tube.c b/src/mc68000tube.c index 7c3c6093..38499a36 100644 --- a/src/mc68000tube.c +++ b/src/mc68000tube.c @@ -291,7 +291,7 @@ static size_t dbg_reg_print(int which, char *buf, size_t bufsize) if (which == 17) // Status register return dbg_decode_flags(value, buf, bufsize); - return snprintf(buf, bufsize, "%s:%08X", reg_names[which], value); + return snprintf(buf, bufsize, "%08X", value); } static void dbg_reg_parse(int which, const char *strval) 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; }; From 508600fca273a183cbdebe5fd624e2f4528d5260 Mon Sep 17 00:00:00 2001 From: Rick Pontefract Date: Mon, 14 Oct 2024 20:32:55 +1300 Subject: [PATCH 04/12] Fix the sorting of speeds loaded from the configuration file --- src/b-em.h | 1 + src/main.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) 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 #include #include +#include #include "compat_wrappers.h" 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) From 2523db0e5a739054640286685df774df1f8d8fbb Mon Sep 17 00:00:00 2001 From: Steve Fosdick Date: Wed, 27 Nov 2024 01:18:25 +0000 Subject: [PATCH 05/12] Implement an option to save the screen as text. See: https://www.stardot.org.uk/forums/viewtopic.php?t=30102 --- src/Makefile.am | 1 + src/gui-allegro.c | 20 ++++ src/gui-allegro.h | 1 + src/textsave.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++ src/textsave.h | 1 + src/video.c | 2 +- src/video.h | 3 +- 7 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 src/textsave.c create mode 100644 src/textsave.h 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/gui-allegro.c b/src/gui-allegro.c index c8c43527..ef7b8ef6 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" @@ -124,6 +125,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); @@ -680,6 +682,21 @@ static void file_save_scrshot(ALLEGRO_EVENT *event) } } +static void file_save_scrtext(ALLEGRO_EVENT *event) +{ + ALLEGRO_FILECHOOSER *chooser; + ALLEGRO_DISPLAY *display; + + if ((chooser = al_create_native_file_dialog(savestate_name, "Save screen as text to file", "*.txt", 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) + textsave(al_get_native_file_dialog_path(chooser, 0)); + } + al_destroy_native_file_dialog(chooser); + } +} + static void file_print_close_file(void) { if (fclose(prt_fp)) @@ -1254,6 +1271,9 @@ void gui_allegro_event(ALLEGRO_EVENT *event) case IDM_FILE_SCREEN_SHOT: file_save_scrshot(event); break; + case IDM_FILE_SCREEN_TEXT: + file_save_scrtext(event); + break; case IDM_FILE_PRINT: file_print(event); break; 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/textsave.c b/src/textsave.c new file mode 100644 index 00000000..98ce20d6 --- /dev/null +++ b/src/textsave.c @@ -0,0 +1,235 @@ +#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[] = { + /* SPC */ 0x0000000000000000, /* ! */ 0x1818181818001800, + /* " */ 0x6c6c6c0000000000, /* # */ 0x36367f367f363600, + /* $ */ 0x0c3f683e0b7e1800, /* % */ 0x60660c1830660600, + /* & */ 0x386c6c386d663b00, /* ' */ 0x0c18300000000000, + /* ( */ 0x0c18303030180c00, /* ) */ 0x30180c0c0c183000, + /* * */ 0x00187e3c7e180000, /* + */ 0x0018187e18180000, + /* , */ 0x0000000000181830, /* - */ 0x0000007e00000000, + /* . */ 0x0000000000181800, /* / */ 0x00060c1830600000, + /* 0 */ 0x3c666e7e76663c00, /* 1 */ 0x1838181818187e00, + /* 2 */ 0x3c66060c18307e00, /* 3 */ 0x3c66061c06663c00, + /* 4 */ 0x0c1c3c6c7e0c0c00, /* 5 */ 0x7e607c0606663c00, + /* 6 */ 0x1c30607c66663c00, /* 7 */ 0x7e060c1830303000, + /* 8 */ 0x3c66663c66663c00, /* 9 */ 0x3c66663e060c3800, + /* : */ 0x0000181800181800, /* ; */ 0x0000181800181830, + /* < */ 0x0c18306030180c00, /* = */ 0x00007e007e000000, + /* > */ 0x30180c060c183000, /* ? */ 0x3c660c1818001800, + /* @ */ 0x3c666e6a6e603c00, /* A */ 0x3c66667e66666600, + /* B */ 0x7c66667c66667c00, /* C */ 0x3c66606060663c00, + /* D */ 0x786c6666666c7800, /* E */ 0x7e60607c60607e00, + /* F */ 0x7e60607c60606000, /* G */ 0x3c66606e66663c00, + /* H */ 0x6666667e66666600, /* I */ 0x7e18181818187e00, + /* J */ 0x3e0c0c0c0c6c3800, /* K */ 0x666c7870786c6600, + /* L */ 0x6060606060607e00, /* M */ 0x63777f6b6b636300, + /* N */ 0x6666767e6e666600, /* O */ 0x3c66666666663c00, + /* P */ 0x7c66667c60606000, /* Q */ 0x3c6666666a6c3600, + /* R */ 0x7c66667c6c666600, /* S */ 0x3c66603c06663c00, + /* T */ 0x7e18181818181800, /* U */ 0x6666666666663c00, + /* V */ 0x66666666663c1800, /* W */ 0x63636b6b7f776300, + /* X */ 0x66663c183c666600, /* Y */ 0x6666663c18181800, + /* Z */ 0x7e060c1830607e00, /* [ */ 0x7c60606060607c00, + /* \ */ 0x006030180c060000, /* ] */ 0x3e06060606063e00, + /* ^ */ 0x183c664200000000, /* _ */ 0x00000000000000ff, + /* ` */ 0x1c36307c30307e00, /* a */ 0x00003c063e663e00, + /* b */ 0x60607c6666667c00, /* c */ 0x00003c6660663c00, + /* d */ 0x06063e6666663e00, /* e */ 0x00003c667e603c00, + /* f */ 0x1c30307c30303000, /* g */ 0x00003e66663e063c, + /* h */ 0x60607c6666666600, /* i */ 0x1800381818183c00, + /* j */ 0x1800381818181870, /* k */ 0x6060666c786c6600, + /* l */ 0x3818181818183c00, /* m */ 0x0000367f6b6b6300, + /* n */ 0x00007c6666666600, /* o */ 0x00003c6666663c00, + /* p */ 0x00007c66667c6060, /* q */ 0x00003e66663e0607, + /* r */ 0x00006c7660606000, /* s */ 0x00003e603c067c00, + /* t */ 0x30307c3030301c00, /* u */ 0x0000666666663e00, + /* v */ 0x00006666663c1800, /* w */ 0x0000636b6b7f3600, + /* x */ 0x0000663c183c6600, /* y */ 0x00006666663e063c, + /* z */ 0x00007e0c18307e00, /* { */ 0x0c18187018180c00, + /* | */ 0x1818180018181800, /* } */ 0x3018180e18183000, + /* ~ */ 0x316b460000000000, /* DEL */ 0xffffffffffffffff, +}; + +/* + * 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 }; + +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; +} + +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; +} + +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 = ' '; + for (int i = 0; i < 96; ++i) { + if (chbits == charset[i]) { + ch = i + 32; + break; + } + } + 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); +} + +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/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); From 939ec55b8963a7c97d20a52c0cb21d8e029c8f59 Mon Sep 17 00:00:00 2001 From: Steve Fosdick Date: Wed, 27 Nov 2024 01:34:00 +0000 Subject: [PATCH 06/12] Add comments. --- src/textsave.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/textsave.c b/src/textsave.c index 98ce20d6..fabbcc9c 100644 --- a/src/textsave.c +++ b/src/textsave.c @@ -128,6 +128,12 @@ 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; @@ -138,6 +144,12 @@ static uint64_t textsave_fcbits(uint64_t chbits, uint_least8_t bgmask, uint_leas 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; @@ -146,6 +158,14 @@ static uint64_t textsave_scbits(uint64_t chbits, uint_least8_t bgmask, uint_leas 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); @@ -218,6 +238,12 @@ static void textsave_bitmap(const char *filename, FILE *fp, uint_least16_t mem_a 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"); From 948706cb217dd878f011383df8338e16c063d92d Mon Sep 17 00:00:00 2001 From: Steve Fosdick Date: Wed, 27 Nov 2024 23:37:46 +0000 Subject: [PATCH 07/12] textsave: Add support for the Master's extended characters (>=128). --- src/textsave.c | 339 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 288 insertions(+), 51 deletions(-) diff --git a/src/textsave.c b/src/textsave.c index fabbcc9c..d6e0a307 100644 --- a/src/textsave.c +++ b/src/textsave.c @@ -1,3 +1,4 @@ +#define _DEBUG #include "b-em.h" #include "textsave.h" #include "mem.h" @@ -65,54 +66,275 @@ static void textsave_teletext(const char *filename, FILE *fp, uint_least16_t mem */ static const uint64_t charset[] = { - /* SPC */ 0x0000000000000000, /* ! */ 0x1818181818001800, - /* " */ 0x6c6c6c0000000000, /* # */ 0x36367f367f363600, - /* $ */ 0x0c3f683e0b7e1800, /* % */ 0x60660c1830660600, - /* & */ 0x386c6c386d663b00, /* ' */ 0x0c18300000000000, - /* ( */ 0x0c18303030180c00, /* ) */ 0x30180c0c0c183000, - /* * */ 0x00187e3c7e180000, /* + */ 0x0018187e18180000, - /* , */ 0x0000000000181830, /* - */ 0x0000007e00000000, - /* . */ 0x0000000000181800, /* / */ 0x00060c1830600000, - /* 0 */ 0x3c666e7e76663c00, /* 1 */ 0x1838181818187e00, - /* 2 */ 0x3c66060c18307e00, /* 3 */ 0x3c66061c06663c00, - /* 4 */ 0x0c1c3c6c7e0c0c00, /* 5 */ 0x7e607c0606663c00, - /* 6 */ 0x1c30607c66663c00, /* 7 */ 0x7e060c1830303000, - /* 8 */ 0x3c66663c66663c00, /* 9 */ 0x3c66663e060c3800, - /* : */ 0x0000181800181800, /* ; */ 0x0000181800181830, - /* < */ 0x0c18306030180c00, /* = */ 0x00007e007e000000, - /* > */ 0x30180c060c183000, /* ? */ 0x3c660c1818001800, - /* @ */ 0x3c666e6a6e603c00, /* A */ 0x3c66667e66666600, - /* B */ 0x7c66667c66667c00, /* C */ 0x3c66606060663c00, - /* D */ 0x786c6666666c7800, /* E */ 0x7e60607c60607e00, - /* F */ 0x7e60607c60606000, /* G */ 0x3c66606e66663c00, - /* H */ 0x6666667e66666600, /* I */ 0x7e18181818187e00, - /* J */ 0x3e0c0c0c0c6c3800, /* K */ 0x666c7870786c6600, - /* L */ 0x6060606060607e00, /* M */ 0x63777f6b6b636300, - /* N */ 0x6666767e6e666600, /* O */ 0x3c66666666663c00, - /* P */ 0x7c66667c60606000, /* Q */ 0x3c6666666a6c3600, - /* R */ 0x7c66667c6c666600, /* S */ 0x3c66603c06663c00, - /* T */ 0x7e18181818181800, /* U */ 0x6666666666663c00, - /* V */ 0x66666666663c1800, /* W */ 0x63636b6b7f776300, - /* X */ 0x66663c183c666600, /* Y */ 0x6666663c18181800, - /* Z */ 0x7e060c1830607e00, /* [ */ 0x7c60606060607c00, - /* \ */ 0x006030180c060000, /* ] */ 0x3e06060606063e00, - /* ^ */ 0x183c664200000000, /* _ */ 0x00000000000000ff, - /* ` */ 0x1c36307c30307e00, /* a */ 0x00003c063e663e00, - /* b */ 0x60607c6666667c00, /* c */ 0x00003c6660663c00, - /* d */ 0x06063e6666663e00, /* e */ 0x00003c667e603c00, - /* f */ 0x1c30307c30303000, /* g */ 0x00003e66663e063c, - /* h */ 0x60607c6666666600, /* i */ 0x1800381818183c00, - /* j */ 0x1800381818181870, /* k */ 0x6060666c786c6600, - /* l */ 0x3818181818183c00, /* m */ 0x0000367f6b6b6300, - /* n */ 0x00007c6666666600, /* o */ 0x00003c6666663c00, - /* p */ 0x00007c66667c6060, /* q */ 0x00003e66663e0607, - /* r */ 0x00006c7660606000, /* s */ 0x00003e603c067c00, - /* t */ 0x30307c3030301c00, /* u */ 0x0000666666663e00, - /* v */ 0x00006666663c1800, /* w */ 0x0000636b6b7f3600, - /* x */ 0x0000663c183c6600, /* y */ 0x00006666663e063c, - /* z */ 0x00007e0c18307e00, /* { */ 0x0c18187018180c00, - /* | */ 0x1818180018181800, /* } */ 0x3018180e18183000, - /* ~ */ 0x316b460000000000, /* DEL */ 0xffffffffffffffff, + 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 }; /* @@ -209,13 +431,27 @@ static void textsave_bitmap(const char *filename, FILE *fp, uint_least16_t mem_a } } log_debug("textsave: chbits=%016lx", chbits); - int ch = ' '; - for (int i = 0; i < 96; ++i) { + int ch = 0; + for (int i = 0; i < sizeof(charcodes); ++i) { if (chbits == charset[i]) { - ch = i + 32; + 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; @@ -259,3 +495,4 @@ void textsave(const char *filename) log_error("unable to open file %s: %s\n", filename, strerror(errno)); } } + From a579ccd66956df25136207918782329dac008d77 Mon Sep 17 00:00:00 2001 From: Steve Fosdick Date: Sun, 1 Dec 2024 19:21:47 +0000 Subject: [PATCH 08/12] 6502tube: fix behavior of opcode #02. This was a Klaus Dormann test suite failure. See https://www.stardot.org.uk/forums/viewtopic.php?p=440921#p440921 and https://github.com/stardot/b-em/issues/239 Opcode 02 is HLT (undocumented) on the NMOS 6502 but is a NOP on the 65C02. The Dormann test suite tested that this opcode causes the byte immediately after it to be skipped rather than executed, i.e. it is a two-byte instruction. This commit makes that so and fixes the test failure. --- src/6502tube.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 */ From 4036483a3a5f8d22f98bc955eb2a823e808dd7b5 Mon Sep 17 00:00:00 2001 From: Steve Fosdick Date: Mon, 2 Dec 2024 02:05:54 +0000 Subject: [PATCH 09/12] Issue#239: Apply the same fix for the non-tube CMOS processor. See previous commit. Make the same change for the non-tube CMOS 6502. --- src/6502.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/6502.c b/src/6502.c index f145ac5e..2b9458ff 100644 --- a/src/6502.c +++ b/src/6502.c @@ -4088,8 +4088,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 */ From 8b9c69f22fa735217912e52d5a3f745f888041c3 Mon Sep 17 00:00:00 2001 From: Steve Fosdick Date: Wed, 1 Jan 2025 17:28:02 +0000 Subject: [PATCH 10/12] gui: reduce variable scope. --- src/gui-allegro.c | 264 ++++++++++++++++++++-------------------------- 1 file changed, 115 insertions(+), 149 deletions(-) diff --git a/src/gui-allegro.c b/src/gui-allegro.c index ef7b8ef6..0154843d 100644 --- a/src/gui-allegro.c +++ b/src/gui-allegro.c @@ -93,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); } @@ -109,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); } } @@ -192,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); } @@ -224,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 @@ -247,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); @@ -267,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 @@ -287,11 +274,9 @@ 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++) { + menu_map_t *map = malloc(model_count * sizeof(menu_map_t)); + if (map) { + for (int i = 0; i < model_count; i++) { map[i].label = models[i].name; map[i].itemno = i; } @@ -310,19 +295,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 }; @@ -501,9 +488,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; } @@ -511,9 +496,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; } @@ -570,11 +553,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); @@ -638,9 +619,9 @@ static int radio_event_with_deselect(ALLEGRO_EVENT *event, int current) static void file_load_state(ALLEGRO_EVENT *event) { - 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); 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)); @@ -651,11 +632,9 @@ static void file_load_state(ALLEGRO_EVENT *event) 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); + ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(savestate_name, "Save state to file", "*.snp", ALLEGRO_FILECHOOSER_SAVE); + 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_save(al_get_native_file_dialog_path(chooser, 0)); @@ -666,11 +645,9 @@ static void file_save_state(ALLEGRO_EVENT *event) static void file_save_scrshot(ALLEGRO_EVENT *event) { - 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); + ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(vid_scrshotname, "Save screenshot to file", "*.bmp;*.pcx;*.tga;*.png;*.jpg", ALLEGRO_FILECHOOSER_SAVE); + 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) { strncpy(vid_scrshotname, al_get_native_file_dialog_path(chooser, 0), sizeof vid_scrshotname-1); @@ -684,11 +661,9 @@ static void file_save_scrshot(ALLEGRO_EVENT *event) static void file_save_scrtext(ALLEGRO_EVENT *event) { - ALLEGRO_FILECHOOSER *chooser; - ALLEGRO_DISPLAY *display; - - if ((chooser = al_create_native_file_dialog(savestate_name, "Save screen as text to file", "*.txt", ALLEGRO_FILECHOOSER_SAVE))) { - display = (ALLEGRO_DISPLAY *)(event->user.data2); + ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(savestate_name, "Save screen as text to file", "*.txt", ALLEGRO_FILECHOOSER_SAVE); + 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) textsave(al_get_native_file_dialog_path(chooser, 0)); @@ -716,54 +691,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(); @@ -772,20 +753,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); } } @@ -824,10 +806,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; @@ -920,19 +900,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)); @@ -979,12 +956,10 @@ static void disc_wprot(ALLEGRO_EVENT *event) static void disc_mmb_load(ALLEGRO_EVENT *event) { - 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); + ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(fpath, "Choose an MMB file", "*.mmb", 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 *fn = strdup(al_get_native_file_dialog_path(chooser, 0)); @@ -998,12 +973,10 @@ static void disc_mmb_load(ALLEGRO_EVENT *event) static void disc_mmc_load(ALLEGRO_EVENT *event) { - 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); + ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(fpath, "Choose an MMC card image", "*", 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 *fn = strdup(al_get_native_file_dialog_path(chooser, 0)); @@ -1055,11 +1028,9 @@ static void disc_toggle_scsi(ALLEGRO_EVENT *event) static void disc_vdfs_root(ALLEGRO_EVENT *event) { - 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); + ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(vdfs_get_root(), "Choose a folder to be the VDFS root", "*", ALLEGRO_FILECHOOSER_FOLDER); + 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) { vdfs_set_root(al_get_native_file_dialog_path(chooser, 0)); @@ -1071,14 +1042,12 @@ static void disc_vdfs_root(ALLEGRO_EVENT *event) 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(); @@ -1125,23 +1094,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); @@ -1275,7 +1241,7 @@ void gui_allegro_event(ALLEGRO_EVENT *event) file_save_scrtext(event); break; case IDM_FILE_PRINT: - file_print(event); + file_print_file(event); break; case IDM_FILE_PCMD: file_print_pipe(event); From d087c9201202aff8b706ebb083223fe75a20bc0d Mon Sep 17 00:00:00 2001 From: Steve Fosdick Date: Wed, 1 Jan 2025 18:02:56 +0000 Subject: [PATCH 11/12] gui: reduce the number of very similar file dialog functions. --- src/gui-allegro.c | 113 ++++++++++------------------------------------ 1 file changed, 25 insertions(+), 88 deletions(-) diff --git a/src/gui-allegro.c b/src/gui-allegro.c index 0154843d..0ce7f0f9 100644 --- a/src/gui-allegro.c +++ b/src/gui-allegro.c @@ -617,59 +617,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_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)); + callback(al_get_native_file_dialog_path(chooser, 0)); } al_destroy_native_file_dialog(chooser); } } -static void file_save_state(ALLEGRO_EVENT *event) +static void file_save_scrshot(const char *path) { - ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(savestate_name, "Save state to file", "*.snp", ALLEGRO_FILECHOOSER_SAVE); - 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_save(al_get_native_file_dialog_path(chooser, 0)); - } - al_destroy_native_file_dialog(chooser); - } -} - -static void file_save_scrshot(ALLEGRO_EVENT *event) -{ - ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(vid_scrshotname, "Save screenshot to file", "*.bmp;*.pcx;*.tga;*.png;*.jpg", ALLEGRO_FILECHOOSER_SAVE); - 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) { - 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); - } -} - -static void file_save_scrtext(ALLEGRO_EVENT *event) -{ - ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(savestate_name, "Save screen as text to file", "*.txt", ALLEGRO_FILECHOOSER_SAVE); - 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) - textsave(al_get_native_file_dialog_path(chooser, 0)); - } - 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) @@ -954,38 +919,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) { - const char *fpath = mmb_fn ? mmb_fn : "."; - ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(fpath, "Choose an MMB file", "*.mmb", 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 *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) { - const char *fpath = mmccard_fn ? mmccard_fn : "."; - ALLEGRO_FILECHOOSER *chooser = al_create_native_file_dialog(fpath, "Choose an MMC card image", "*", 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 *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) @@ -1026,18 +971,10 @@ 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 = al_create_native_file_dialog(vdfs_get_root(), "Choose a folder to be the VDFS root", "*", ALLEGRO_FILECHOOSER_FOLDER); - 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) { - 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) @@ -1229,16 +1166,16 @@ 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_save_scrtext(event); + file_chooser_generic(event, savestate_name, "Save screen as text to file", "*.txt", ALLEGRO_FILECHOOSER_SAVE, textsave); break; case IDM_FILE_PRINT: file_print_file(event); @@ -1274,7 +1211,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); @@ -1283,7 +1220,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(); @@ -1331,7 +1268,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); From 4bd48c3a00006fb6b556668f22c24f66b3846dde Mon Sep 17 00:00:00 2001 From: Steve Fosdick Date: Wed, 1 Jan 2025 21:38:50 +0000 Subject: [PATCH 12/12] model: allow group of models for sub-menus. This commit allows models to have a group associated with them. The groups are then used to form sub-menus of the Model menu, making browsing this menu much tidier with a large number of models. --- src/gui-allegro.c | 51 +++++++++++++++++++++++++++++++++++++++++------ src/model.c | 1 + src/model.h | 3 +++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/gui-allegro.c b/src/gui-allegro.c index 0ce7f0f9..0a3e47b1 100644 --- a/src/gui-allegro.c +++ b/src/gui-allegro.c @@ -273,14 +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 = malloc(model_count * sizeof(menu_map_t)); + menu_map_t *map = calloc(model_count * 2, sizeof(menu_map_t)); if (map) { - for (int i = 0; i < model_count; i++) { - map[i].label = models[i].name; - map[i].itemno = i; + 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); + } + 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, model_count, IDM_MODEL, curmodel); + add_sorted_set(menu, map, item_no, IDM_MODEL, curmodel); free(map); return menu; } 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 {