From 2a1bc83e93911d1b17939b68c1f6035e57d8d160 Mon Sep 17 00:00:00 2001
From: Nigel Reed <nigel@nigelreed.net>
Date: Tue, 11 Feb 2025 01:29:44 -0600
Subject: [PATCH 1/4] Initial changes to support MODE7 graphics natively
 Similar to PETSCII it will answer on a dedicated port.

---
 ctrl/sbbs.ini             |  4 +++-
 ctrl/text.dat             |  2 ++
 exec/load/termdesc.js     |  6 ++++++
 exec/load/text.js         |  4 +++-
 exec/load/userdefs.js     |  2 +-
 exec/login.js             |  2 +-
 exec/logon.js             |  6 ++++++
 exec/logonlist.js         |  7 +++++++
 src/sbbs3/answer.cpp      |  3 +++
 src/sbbs3/ars.c           |  4 ++++
 src/sbbs3/ars_defs.h      |  2 ++
 src/sbbs3/chk_ar.cpp      | 14 ++++++++++++++
 src/sbbs3/con_out.cpp     | 12 ++++++++++++
 src/sbbs3/logon.cpp       |  4 ++--
 src/sbbs3/main.cpp        |  7 +++++++
 src/sbbs3/newuser.cpp     |  3 +++
 src/sbbs3/prntfile.cpp    |  5 +++++
 src/sbbs3/sbbs.h          |  2 ++
 src/sbbs3/sbbs_ini.c      |  6 ++++++
 src/sbbs3/sbbsdefs.h      |  6 ++++--
 src/sbbs3/scfg/scfgsrvr.c | 31 +++++++++++++++++++------------
 src/sbbs3/startup.h       |  1 +
 src/sbbs3/text.h          |  2 ++
 src/sbbs3/text_defaults.c |  2 ++
 src/sbbs3/text_id.c       |  2 ++
 src/sbbs3/userdat.c       |  6 ++++++
 src/sbbs3/useredit.cpp    |  2 +-
 src/sbbs3/xtrn.cpp        |  9 +++++++++
 28 files changed, 135 insertions(+), 21 deletions(-)

diff --git a/ctrl/sbbs.ini b/ctrl/sbbs.ini
index df19c8ae08..c47ddf1caa 100644
--- a/ctrl/sbbs.ini
+++ b/ctrl/sbbs.ini
@@ -77,7 +77,9 @@
 	Pet40Port = 64
 	; TCP port for 80-column PETSCII connections (any terminal protocol):
 	Pet80Port = 128
-	; Note on PETSCII support: you must add the same port(s) to one of your
+	; TCP port for 40-column MODE7 connections (any termnial protocol):
+	Mode7Port = 5050
+	; Note on PETSCII and MODE7 support: you must add the same port(s) to one of your
 	; *Interface= values above to open/listen/accept connections on that port.
 	; Example:
 	;   TelnetInterface = 71.95.196.34,71.95.196.34:64,71.95.196.34:128
diff --git a/ctrl/text.dat b/ctrl/text.dat
index 7a9f5d4c73..424225c7e1 100644
--- a/ctrl/text.dat
+++ b/ctrl/text.dat
@@ -1124,3 +1124,5 @@
 " %c \1g%.10s\1n %c %.127s%c"							   935 QWKTagLineFmt
 "\1n\r\n\1b\1hQWK Control [\1c%s\1b]: \1g%s\r\n"		   936 QWKControlCommand
 "Unrecognized Control Command!\1n\r\n"					   937 QWKBadControlCommand
+"Mode 7 terminal detected"								   938 Mode7TerminalDetected
+"Are you using a Mode 7 terminal"						   939 Mode7TerminalQ
diff --git a/exec/load/termdesc.js b/exec/load/termdesc.js
index c514c06524..ed0135707a 100644
--- a/exec/load/termdesc.js
+++ b/exec/load/termdesc.js
@@ -22,6 +22,8 @@ function charset(term)
 		term = console.term_supports();
 	if(term & USER_PETSCII)
 		return "CBM-ASCII";
+	if(term & USER_MODE7)
+		return "MODE7";
 	if(term & USER_UTF8)
 		return "UTF-8";
 	if(term & USER_NO_EXASCII)
@@ -62,6 +64,8 @@ function type(verbose, usr)
 	var type = "DUMB";
 	if(term & USER_PETSCII)
 		type = "PETSCII";
+	if(term & USER_MODE7)
+		type = "MODE7";
 	if(term & USER_RIP)
 		type = "RIP";
 	if(term & USER_ANSI)
@@ -72,6 +76,8 @@ function type(verbose, usr)
 	// Verbose
 	if(term & USER_PETSCII)
 		return ((usr.settings & USER_AUTOTERM) ? bbs.text(TerminalAutoDetect) : "") + "CBM/PETSCII";
+	if(term & USER_MODE7)
+		return ((usr.settings & USER_AUTOTERM) ? bbs.text(TerminalAutoDetect) : "") + "MODE7";
 	return format("%s%s / %s %s%s%s"
 		,(usr.settings & USER_AUTOTERM) ? bbs.text(TerminalAutoDetect) : ""
 		,this.charset(term)
diff --git a/exec/load/text.js b/exec/load/text.js
index ab3fc80346..a780a76474 100644
--- a/exec/load/text.js
+++ b/exec/load/text.js
@@ -944,7 +944,9 @@ var QWKEndOfMessage=934;
 var QWKTagLineFmt=935;
 var QWKControlCommand=936;
 var QWKBadControlCommand=937;
+var Mode7TerminalDetected=938;
+var Mode7TerminalQ=939;
 
-var TOTAL_TEXT=938;
+var TOTAL_TEXT=940;
 
 this;
diff --git a/exec/load/userdefs.js b/exec/load/userdefs.js
index 4f7aad07d7..d09319efa7 100644
--- a/exec/load/userdefs.js
+++ b/exec/load/userdefs.js
@@ -24,7 +24,7 @@ const USER_AUTOTERM	    = (1<<17);	// Autodetect terminal type
 const USER_COLDKEYS	    = (1<<18);	// No hot-keys								
 const USER_EXTDESC 	    = (1<<19);	// Extended file descriptions				
 const USER_AUTOHANG	    = (1<<20);	// Auto-hang-up after transfer				
-const USER_WIP 		    = (1<<21);	// Supports WIP terminal emulation			
+const USER_MODE7	    = (1<<21);	// BBC Micro Mode 7 terminal support
 const USER_AUTOLOGON    = (1<<22);	// AutoLogon via IP							
 const USER_HTML		    = (1<<23);	// Using Deuce's HTML terminal (*cough*)	
 const USER_NOPAUSESPIN  = (1<<24);	// No spinning cursor at pause prompt		
diff --git a/exec/login.js b/exec/login.js
index a4c7324916..347eb97e9e 100644
--- a/exec/login.js
+++ b/exec/login.js
@@ -27,7 +27,7 @@ if(!bbs.online)
 	exit();
 var inactive_hangup = parseInt(options.inactive_hangup, 10);
 if(inactive_hangup && inactive_hangup < console.max_socket_inactivity
-	&& !(console.autoterm&(USER_ANSI | USER_PETSCII | USER_UTF8))) {
+	&& !(console.autoterm&(USER_ANSI | USER_PETSCII | USER_UTF8 | USER_MODE7))) {
 	console.max_socket_inactivity = inactive_hangup;
 	log(LOG_NOTICE, "terminal not detected, reducing inactivity hang-up timeout to " + console.max_socket_inactivity + " seconds");
 }
diff --git a/exec/logon.js b/exec/logon.js
index 6094ea5dda..5be63f0299 100644
--- a/exec/logon.js
+++ b/exec/logon.js
@@ -11,6 +11,12 @@
 
 require("sbbsdefs.js", 'SS_RLOGIN');
 require("nodedefs.js", 'NODE_QUIET');
+
+if(console.term_supports(USER_MODE7)) {
+	log(LOG_DEBUG, "Setting users language to m7");
+	user.lang = "m7";
+}
+
 if(!bbs.mods.avatar_lib)
 	bbs.mods.avatar_lib = load({}, 'avatar_lib.js');
 if(!bbs.mods.logonlist_lib)
diff --git a/exec/logonlist.js b/exec/logonlist.js
index da7f527347..63b174aa0b 100644
--- a/exec/logonlist.js
+++ b/exec/logonlist.js
@@ -45,6 +45,13 @@ if(!js.global.bbs) {
 if(!bbs.mods.logonlist_lib)
 	bbs.mods.logonlist_lib = load({}, 'logonlist_lib.js');
 var options = load("modopts.js", "logonlist");
+function dump_objs(obj) {
+        Object.keys(obj).forEach(function (e) {
+                writeln(e + ': ' + JSON.stringify(obj[e]));
+        });
+}
+dump_objs(options);
+writeln("lang: " + user.lang);
 if(!options)
 	options = {};
 if(options.last_few_callers === undefined)
diff --git a/src/sbbs3/answer.cpp b/src/sbbs3/answer.cpp
index 2c0e2921b3..62a45ca9c9 100644
--- a/src/sbbs3/answer.cpp
+++ b/src/sbbs3/answer.cpp
@@ -553,6 +553,9 @@ bool sbbs_t::answer()
 			SAFECOPY(terminal, "PETSCII");
 			outchar(FF);
 			center(str);
+		} else if (autoterm & MODE7) {
+			SAFECOPY(terminal, "MODE7");
+			center(str);
 		} else {    /* ANSI+ terminal detection */
 			putcom( "\r\n"      /* locate cursor at column 1 */
 			        "\x1b[s"    /* save cursor position (necessary for HyperTerm auto-ANSI) */
diff --git a/src/sbbs3/ars.c b/src/sbbs3/ars.c
index 9f1a995d16..2719f0a12b 100644
--- a/src/sbbs3/ars.c
+++ b/src/sbbs3/ars.c
@@ -337,6 +337,10 @@ uchar* arstr(ushort* count, const char* str, scfg_t* cfg, uchar* ar_buf)
 				artype = AR_PETSCII;
 				i += 6;
 			}
+			else if (!strnicmp(str + i, "MODE7", 5)) {
+				artype = AR_MODE7;
+				i += 4;
+			}
 			else if (!strnicmp(str + i, "ASCII", 5)) {
 				artype = AR_ASCII;
 				i += 4;
diff --git a/src/sbbs3/ars_defs.h b/src/sbbs3/ars_defs.h
index 9d4c003bd5..fa5de50605 100644
--- a/src/sbbs3/ars_defs.h
+++ b/src/sbbs3/ars_defs.h
@@ -116,6 +116,7 @@ enum {                              /* Access requirement binaries */
 	, AR_UTF8
 	, AR_CP437
 	, AR_USERNAME
+	, AR_MODE7
 };
 
 enum ar_type {
@@ -148,6 +149,7 @@ static inline enum ar_type ar_type(int artype)
 		case AR_GUEST:
 		case AR_QNODE:
 		case AR_QUIET:
+		case AR_MODE7:
 			return AR_BOOL;
 		case AR_SUBCODE:
 		case AR_DIRCODE:
diff --git a/src/sbbs3/chk_ar.cpp b/src/sbbs3/chk_ar.cpp
index 731c0796b7..ca0e4e7189 100644
--- a/src/sbbs3/chk_ar.cpp
+++ b/src/sbbs3/chk_ar.cpp
@@ -128,6 +128,20 @@ bool sbbs_t::ar_exp(const uchar **ptrptr, user_t* user, client_t* client)
 					noaccess_val = PETSCII;
 				}
 				break;
+			case AR_MODE7:
+				{
+					int val = term_supports();
+					val &= CHARSET_FLAGS;
+					if (val != CHARSET_MODE7)
+						result = _not;
+					else
+						result = !_not;
+					if (!result) {
+						noaccess_str = text[NoAccessTerminal];
+						noaccess_val = MODE7;
+					}
+				}
+				break;
 			case AR_ASCII:
 				if ((term_supports() & CHARSET_FLAGS) != CHARSET_ASCII)
 					result = _not;
diff --git a/src/sbbs3/con_out.cpp b/src/sbbs3/con_out.cpp
index eda850dfd5..c1b99074af 100644
--- a/src/sbbs3/con_out.cpp
+++ b/src/sbbs3/con_out.cpp
@@ -245,6 +245,11 @@ unsigned char cp437_to_petscii(unsigned char ch)
 	return ch;
 }
 
+unsigned char cp437_to_mode7(unsigned char ch)
+{
+	return ch;
+}
+
 /* Perform PETSCII conversion to ANSI-BBS/CP437 */
 int sbbs_t::petscii_to_ansibbs(unsigned char ch)
 {
@@ -539,6 +544,9 @@ char* sbbs_t::term_type(user_t* user, int term, char* str, size_t size)
 	if (term & PETSCII)
 		safe_snprintf(str, size, "%sCBM/PETSCII"
 		              , (user->misc & AUTOTERM) ? text[TerminalAutoDetect] : nulstr);
+	else if (term & MODE7)
+		safe_snprintf(str, size, "%s/MODE7"
+		              , (user->misc & AUTOTERM) ? text[TerminalAutoDetect] : nulstr);
 	else
 		safe_snprintf(str, size, "%s%s / %s %s%s%s"
 		              , (user->misc & AUTOTERM) ? text[TerminalAutoDetect] : nulstr
@@ -560,6 +568,8 @@ const char* sbbs_t::term_type(int term)
 		term = term_supports();
 	if (term & PETSCII)
 		return "PETSCII";
+	if (term & MODE7)
+		return "MODE7";
 	if (term & RIP)
 		return "RIP";
 	if (term & ANSI)
@@ -576,6 +586,8 @@ const char* sbbs_t::term_charset(int term)
 		term = term_supports();
 	if (term & PETSCII)
 		return "CBM-ASCII";
+	if (term & MODE7)
+		return "MODE7";
 	if (term & UTF8)
 		return "UTF-8";
 	if (term & NO_EXASCII)
diff --git a/src/sbbs3/logon.cpp b/src/sbbs3/logon.cpp
index 5c3022c0ca..f9231b0929 100644
--- a/src/sbbs3/logon.cpp
+++ b/src/sbbs3/logon.cpp
@@ -182,8 +182,8 @@ bool sbbs_t::logon()
 	}
 
 	if ((useron.misc & AUTOTERM)
-	    // User manually-enabled PETSCII, but they're logging in with an ANSI (auto-detected) terminal
-	    || ((useron.misc & PETSCII) && (autoterm & ANSI))) {
+	    // User manually-enabled PETSCII or MODE7, but they're logging in with an ANSI (auto-detected) terminal
+	    || (((useron.misc & PETSCII) || (useron.misc & MODE7)) && (autoterm & ANSI))) {
 		useron.misc &= ~(ANSI | RIP | CHARSET_FLAGS);
 		useron.misc |= (AUTOTERM | autoterm);
 	}
diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index 20bea4fad6..2b6feda0e5 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -24,6 +24,7 @@
 #include "telnet.h"
 #include "netwrap.h"
 #include "petdefs.h"
+#include "mode7defs.h"
 #include "filedat.h"
 #include "js_rtpool.h"
 #include "js_request.h"
@@ -5663,6 +5664,12 @@ NO_SSH:
 				sbbs->outcom(PETSCII_UPPERLOWER);
 			}
 
+			if (inet_addrport(&local_addr) == startup->mode7_port) {
+				sbbs->autoterm = MODE7;
+				sbbs->cols = 40;
+				sbbs->rows = 25;
+			}
+
 			sbbs->bprintf("\r\n%s\r\n", VERSION_NOTICE);
 			sbbs->bprintf("%s connection from: %s\r\n", client.protocol, host_ip);
 
diff --git a/src/sbbs3/newuser.cpp b/src/sbbs3/newuser.cpp
index 7775414cbc..e46c3b31bb 100644
--- a/src/sbbs3/newuser.cpp
+++ b/src/sbbs3/newuser.cpp
@@ -153,6 +153,9 @@ bool sbbs_t::newuser()
 			autoterm |= PETSCII;
 			outcom(PETSCII_UPPERLOWER);
 			bputs(text[PetTerminalDetected]);
+		} else if (useron.misc & MODE7) {
+			autoterm |= MODE7;
+			bputs(text[Mode7TerminalDetected]);
 		} else {
 			if (!yesno(text[ExAsciiTerminalQ]))
 				useron.misc |= NO_EXASCII;
diff --git a/src/sbbs3/prntfile.cpp b/src/sbbs3/prntfile.cpp
index caf470b4d5..71fc770859 100644
--- a/src/sbbs3/prntfile.cpp
+++ b/src/sbbs3/prntfile.cpp
@@ -22,6 +22,7 @@
 #include "sbbs.h"
 #include "utf8.h"
 #include "petdefs.h"
+#include "mode7defs.h"
 
 #ifndef PRINTFILE_MAX_LINE_LEN
 #define PRINTFILE_MAX_LINE_LEN (8 * 1024)
@@ -54,6 +55,8 @@ bool sbbs_t::printfile(const char* fname, int mode, int org_cols, JSObject* obj)
 			mode |= P_NOPAUSE;
 		} else if (stricmp(p, ".seq") == 0) {
 			mode |= P_PETSCII;
+		} else if (stricmp(p, ".m7") == 0) {
+			mode |= P_MODE7;
 		} else if (stricmp(p, ".utf8") == 0) {
 			mode |= P_UTF8;
 		}
@@ -283,6 +286,8 @@ bool sbbs_t::menu(const char *code, int mode, JSObject* obj)
 				break;
 			if ((term & PETSCII) && menu_exists(code, "seq", path))
 				break;
+			if ((term & MODE7) && menu_exists(code, "m7", path))
+				break;
 			if (term & NO_EXASCII) {
 				next = "asc";
 				last = "msg";
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 5723daed8b..411a1cfe29 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -981,6 +981,7 @@ public:
 	bool	saveline(void);
 	bool	restoreline(void);
 	int		petscii_to_ansibbs(unsigned char);
+	int		mode7_to_ansibbs(unsigned char);
 	size_t	print_utf8_as_cp437(const char*, size_t);
 	int		attr(int);				/* Change text color/attributes */
 	void	ctrl_a(char);			/* Performs Ctrl-Ax attribute changes */
@@ -1431,6 +1432,7 @@ extern "C" {
 
 	/* con_out.cpp */
 	unsigned char		cp437_to_petscii(unsigned char);
+	unsigned char		cp437_to_mode7(unsigned char);
 
 	/* xtrn.cpp */
 	bool				native_executable(scfg_t*, const char* cmdline, int mode);
diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c
index 6ba8123a24..a550ea306b 100644
--- a/src/sbbs3/sbbs_ini.c
+++ b/src/sbbs3/sbbs_ini.c
@@ -448,6 +448,9 @@ void sbbs_read_ini(
 		bbs->pet80_port
 		    = iniGetShortInt(list, section, "Pet80Port", 128);
 
+		bbs->mode7_port
+			= iniGetShortInt(list, section, "Mode7Port", 5050);
+
 		bbs->ssh_port
 		    = iniGetShortInt(list, section, "SSHPort", 22);
 		bbs->ssh_connect_timeout
@@ -936,6 +939,9 @@ bool sbbs_write_ini(
 			if (!iniSetUInt16(lp, section, "Pet80Port", bbs->pet80_port, &style))
 				break;
 
+			if (!iniSetUInt16(lp, section, "Mode7Port", bbs->mode7_port, &style))
+				break;
+
 			if (strListCmp(bbs->ssh_interfaces, global->interfaces) == 0)
 				iniRemoveValue(lp, section, "SSHInterface");
 			else if (!iniSetStringList(lp, section, "SSHInterface", ",", bbs->ssh_interfaces, &style))
diff --git a/src/sbbs3/sbbsdefs.h b/src/sbbs3/sbbsdefs.h
index b4c285ca30..d1ac52f537 100644
--- a/src/sbbs3/sbbsdefs.h
+++ b/src/sbbs3/sbbsdefs.h
@@ -568,7 +568,7 @@ typedef enum {                      /* Values for xtrn_t.event				*/
 #define COLDKEYS    (1 << 18)     /* No hot-keys							*/
 #define EXTDESC     (1 << 19)     /* Extended file descriptions			*/
 #define AUTOHANG    (1 << 20)     /* Auto-hang-up after transfer			*/
-#define WIP_UNUSED  (1 << 21)     /* Supports WIP terminal emulation		*/
+#define MODE7		(1 << 21)     /* BBC Micro Mode 7 terminal support	*/
 #define AUTOLOGON   (1 << 22)     /* AutoLogon via IP						*/
 #define HTML_UNUSED (1 << 23)     /* Using Zuul/HTML terminal				*/
 #define NOPAUSESPIN (1 << 24)     /* No spinning cursor at pause prompt	*/
@@ -579,9 +579,10 @@ typedef enum {                      /* Values for xtrn_t.event				*/
 #define MOUSE       (1U << 31)    /* Mouse supported terminal				*/
 
 #define TERM_FLAGS      (ANSI | COLOR | RIP | SWAP_DELETE | ICE_COLOR | MOUSE | CHARSET_FLAGS)
-#define CHARSET_FLAGS   (NO_EXASCII | PETSCII | UTF8)
+#define CHARSET_FLAGS   (NO_EXASCII | PETSCII | UTF8 | MODE7)
 #define CHARSET_ASCII   NO_EXASCII  // US-ASCII
 #define CHARSET_PETSCII PETSCII     // CBM-ASCII
+#define CHARSET_MODE7	MODE7		// MODE 7
 #define CHARSET_UTF8    UTF8
 #define CHARSET_CP437   0
 
@@ -694,6 +695,7 @@ typedef enum {                      /* Values for xtrn_t.event				*/
 #define P_REMOTE    (1 << 18)     /* Only print when online == ON_REMOTE		*/
 #define P_INDENT    (1 << 19)     /* Indent lines to current cursor column	*/
 #define P_ATCODES   (1 << 20)     /* Trusted @-codes in formatted string		*/
+#define P_MODE7		(1 << 21)     /* Message is native Mode 7					*/
 
 /* Bits in 'mode' for listfiles             */
 #define FL_ULTIME   (1 << 0)      /* List files by upload time                */
diff --git a/src/sbbs3/scfg/scfgsrvr.c b/src/sbbs3/scfg/scfgsrvr.c
index 4e870ce2c3..b52ce44848 100644
--- a/src/sbbs3/scfg/scfgsrvr.c
+++ b/src/sbbs3/scfg/scfgsrvr.c
@@ -557,6 +557,8 @@ static void termsrvr_cfg(void)
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "40 Column PETSCII Support", startup.pet40_port ? str : strDisabled );
 		snprintf(str, sizeof str, "Port %u", startup.pet80_port);
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "80 Column PETSCII Support", startup.pet80_port  ? str : strDisabled);
+		snprintf(str, sizeof str, "Port %u", startup.mode7_port);
+		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "BBC Mode 7 Support", startup.mode7_port  ? str : strDisabled);
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "DOS Program Support", startup.options & BBS_OPT_NO_DOS ? "No" : "Yes");
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Max Concurrent Connections", maximum(startup.max_concurrent_connections));
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Max Login Inactivity", vduration(startup.max_login_inactivity));
@@ -621,9 +623,14 @@ static void termsrvr_cfg(void)
 					startup.pet80_port = atoi(str);
 				break;
 			case 8:
-				startup.options ^= BBS_OPT_NO_DOS;
+				SAFEPRINTF(str, "%u", startup.mode7_port);
+				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "BBC Mode 7 Port", str, 5, K_NUMBER | K_EDIT) > 0)
+					startup.mode7_port = atoi(str);
 				break;
 			case 9:
+				startup.options ^= BBS_OPT_NO_DOS;
+				break;
+			case 10:
 				SAFECOPY(str, maximum(startup.max_concurrent_connections));
 				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Maximum Concurrent (Unauthenticated) Connections", str, 10, K_EDIT) > 0)
 					startup.max_concurrent_connections = atoi(str);
@@ -637,7 +644,7 @@ static void termsrvr_cfg(void)
 		"`Maximum User Inactivity` setting in `System->Advanced Options`.\n" \
 		"Normally, if enabled, this socket inactivity duration should be `longer`\n" \
 		"than the `Maximum User Inactivity` setting in `System->Advanced Options`.\n"
-			case 10:
+			case 11:
 				uifc.helpbuf =
 					"`Maximum Socket Inactivity at Login:`\n"
 					"\n"
@@ -651,7 +658,7 @@ static void termsrvr_cfg(void)
 				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Maximum Socket Inactivity at Login", str, 10, K_EDIT) > 0)
 					startup.max_login_inactivity = (uint16_t)parse_duration(str);
 				break;
-			case 11:
+			case 12:
 				uifc.helpbuf =
 					"`Maximum Socket Inactivity at New User Registration:`\n"
 					"\n"
@@ -665,7 +672,7 @@ static void termsrvr_cfg(void)
 				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Maximum Socket Inactivity at New User Registration", str, 10, K_EDIT) > 0)
 					startup.max_newuser_inactivity = (uint16_t)parse_duration(str);
 				break;
-			case 12:
+			case 13:
 				uifc.helpbuf =
 					"`Maximum Socket Inactivity during User Session:`\n"
 					"\n"
@@ -681,34 +688,34 @@ static void termsrvr_cfg(void)
 				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Maximum Socket Inactivity during User Session", str, 10, K_EDIT) > 0)
 					startup.max_session_inactivity = (uint16_t)parse_duration(str);
 				break;
-			case 13:
+			case 14:
 				SAFEPRINTF(str, "%u", startup.outbuf_drain_timeout);
 				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Output Buffer Drain Timeout (milliseconds)", str, 5, K_NUMBER | K_EDIT) > 0)
 					startup.outbuf_drain_timeout = atoi(str);
 				break;
-			case 14:
+			case 15:
 				startup.options ^= BBS_OPT_NO_EVENTS;
 				break;
-			case 15:
+			case 16:
 				if (startup.options & BBS_OPT_NO_EVENTS)
 					break;
 				startup.options ^= BBS_OPT_NO_QWK_EVENTS;
 				break;
-			case 16:
+			case 17:
 				if (startup.options & BBS_OPT_NO_EVENTS)
 					break;
 				uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &startup.event_log_level, 0, "Event Log Level", iniLogLevelStringList());
 				break;
-			case 17:
+			case 18:
 				startup.options ^= BBS_OPT_NO_HOST_LOOKUP;
 				break;
-			case 18:
+			case 19:
 				getar("Terminal Server Login", startup.login_ars);
 				break;
-			case 19:
+			case 20:
 				js_startup_cfg(&startup.js);
 				break;
-			case 20:
+			case 21:
 				login_attempt_cfg(&startup.login_attempt);
 				break;
 			default:
diff --git a/src/sbbs3/startup.h b/src/sbbs3/startup.h
index bdefe555fa..58d70b865b 100644
--- a/src/sbbs3/startup.h
+++ b/src/sbbs3/startup.h
@@ -129,6 +129,7 @@ typedef struct {
 	uint16_t rlogin_port;
 	uint16_t pet40_port;            // 40-column PETSCII terminal server
 	uint16_t pet80_port;            // 80-column PETSCII terminal server
+	uint16_t mode7_port;			// 40-column MODE7 terminal server
 	uint16_t ssh_port;
 	uint16_t ssh_connect_timeout;
 	int ssh_error_level;
diff --git a/src/sbbs3/text.h b/src/sbbs3/text.h
index b7097f503e..548c2785b4 100644
--- a/src/sbbs3/text.h
+++ b/src/sbbs3/text.h
@@ -954,6 +954,8 @@ enum text {
 	,QWKTagLineFmt
 	,QWKControlCommand
 	,QWKBadControlCommand
+	,Mode7TerminalDetected
+	,Mode7TerminalQ
 
 	,TOTAL_TEXT
 };
diff --git a/src/sbbs3/text_defaults.c b/src/sbbs3/text_defaults.c
index 6c568a1ec9..4929b181ae 100644
--- a/src/sbbs3/text_defaults.c
+++ b/src/sbbs3/text_defaults.c
@@ -1507,4 +1507,6 @@ const char * const text_defaults[TOTAL_TEXT]={
 		"\x25\x73\x0d\x0a" // 936 QWKControlCommand
 	,"\x55\x6e\x72\x65\x63\x6f\x67\x6e\x69\x7a\x65\x64\x20\x43\x6f\x6e\x74\x72\x6f\x6c\x20\x43\x6f\x6d\x6d\x61\x6e\x64\x21\x01\x6e\x0d"
 		"\x0a" // 937 QWKBadControlCommand
+	,"\x4d\x6f\x64\x65\x20\x37\x20\x74\x65\x72\x6d\x69\x6e\x61\x6c\x20\x64\x65\x74\x65\x63\x74\x65\x64" // 938 Mode7TerminalDetected
+	,"\x41\x72\x65\x20\x79\x6f\x75\x20\x75\x73\x69\x6e\x67\x20\x61\x20\x4d\x6f\x64\x65\x20\x37\x20\x74\x65\x72\x6d\x69\x6e\x61\x6c" // 939 Mode7TerminalQ
 };
diff --git a/src/sbbs3/text_id.c b/src/sbbs3/text_id.c
index abf977c944..213aaf1491 100644
--- a/src/sbbs3/text_id.c
+++ b/src/sbbs3/text_id.c
@@ -938,4 +938,6 @@ const char* const text_id[]={
 	,"QWKTagLineFmt"
 	,"QWKControlCommand"
 	,"QWKBadControlCommand"
+	,"Mode7TerminalDetected"
+	,"Mode7TerminalQ"
 };
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index 5d28ebe2d6..9049296eb1 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -2177,6 +2177,12 @@ static bool ar_exp(scfg_t* cfg, uchar **ptrptr, user_t* user, client_t* client)
 				else
 					result = !not;
 				break;
+			case AR_MODE7:
+				if (user == NULL || (user->misc & CHARSET_FLAGS) != CHARSET_MODE7)
+					result = not;
+				else
+					result = !not;
+				break;
 			case AR_ASCII:
 				if (user == NULL || (user->misc & CHARSET_FLAGS) != CHARSET_ASCII)
 					result = not;
diff --git a/src/sbbs3/useredit.cpp b/src/sbbs3/useredit.cpp
index eddc505f0c..77d9366f6c 100644
--- a/src/sbbs3/useredit.cpp
+++ b/src/sbbs3/useredit.cpp
@@ -880,7 +880,7 @@ void sbbs_t::user_config(user_t* user)
 			case 'T':
 				if (yesno(text[AutoTerminalQ])) {
 					user->misc |= AUTOTERM;
-					user->misc &= ~(ANSI | RIP | PETSCII | UTF8);
+					user->misc &= ~(ANSI | RIP | PETSCII | UTF8 | MODE7);
 					if (user == &useron)
 						user->misc |= autoterm;
 				}
diff --git a/src/sbbs3/xtrn.cpp b/src/sbbs3/xtrn.cpp
index 69b8ef8cd4..152d43e00f 100644
--- a/src/sbbs3/xtrn.cpp
+++ b/src/sbbs3/xtrn.cpp
@@ -214,6 +214,13 @@ static void petscii_convert(BYTE* buf, uint len)
 	}
 }
 
+static void mode7_convert(BYTE* buf, uint len)
+{
+	for (uint i = 0; i < len; i++) {
+		buf[i] = cp437_to_mode7(buf[i]);
+	}
+}
+
 static BYTE* cp437_to_utf8(BYTE* input, size_t& len, BYTE* outbuf, size_t maxlen)
 {
 	size_t outlen = 0;
@@ -1961,6 +1968,8 @@ int sbbs_t::external(const char* cmdline, int mode, const char* startup_dir)
 				}
 				if (term_supports(PETSCII))
 					petscii_convert(bp, output_len);
+				else if (term_supports(MODE7))
+					mode7_convert(bp, output_len);
 				else if (term_supports(UTF8))
 					bp = cp437_to_utf8(bp, output_len, utf8_buf, sizeof utf8_buf);
 			}
-- 
GitLab


From ecfd1e1d0cf9d4f19d8f4f91c9b2eae93b8d0386 Mon Sep 17 00:00:00 2001
From: Nigel Reed <nigel@nigelreed.net>
Date: Tue, 11 Feb 2025 17:28:16 -0600
Subject: [PATCH 2/4] More changes for MODE7 Updates for Mode 7 graphics

---
 exec/bullseye.js  |  3 ++-
 exec/login.js     |  5 +++--
 exec/logon.js     |  4 ----
 exec/logonlist.js |  7 -------
 exec/noyesbar.js  | 15 +++++++++++----
 exec/yesnobar.js  | 17 ++++++++++++-----
 6 files changed, 28 insertions(+), 23 deletions(-)

diff --git a/exec/bullseye.js b/exec/bullseye.js
index a5f3e65dd7..c3de0da67e 100644
--- a/exec/bullseye.js
+++ b/exec/bullseye.js
@@ -7,6 +7,7 @@
 // @format.tab-size 4, @format.use-tabs true
 
 require("sbbsdefs.js", "P_NOERROR");
+require("gettext.js", 'gettext');
 
 "use strict";
 
@@ -48,7 +49,7 @@ if(bull.length < 1) {
 
 while(bbs.online && !js.terminated) {
 	if(bbs.menu("../bullseye", P_NOERROR)) {
-		console.mnemonics("\r\nEnter number of bulletin or [~Quit]: ");
+		console.mnemonics(gettext("\r\nEnter number of bulletin or [~Quit]: "));
 		b = console.getnum(bull.length);
 	} else {
 		for(i = 0; i < bull.length; ++i)
diff --git a/exec/login.js b/exec/login.js
index 347eb97e9e..e97e2ce6ea 100644
--- a/exec/login.js
+++ b/exec/login.js
@@ -67,11 +67,12 @@ for(var c=0; c < options.login_prompts; c++) {
 	if(!str.length) // blank
 		continue;
 
+
 	// New user application?
 	if(str.toUpperCase()=="NEW") {
 	   if(bbs.newuser()) {
-		   bbs.logon();
-		   exit();
+		bbs.logon();
+		exit();
 	   }
 	   continue;
 	}
diff --git a/exec/logon.js b/exec/logon.js
index 5be63f0299..0b7feadc38 100644
--- a/exec/logon.js
+++ b/exec/logon.js
@@ -12,10 +12,6 @@
 require("sbbsdefs.js", 'SS_RLOGIN');
 require("nodedefs.js", 'NODE_QUIET');
 
-if(console.term_supports(USER_MODE7)) {
-	log(LOG_DEBUG, "Setting users language to m7");
-	user.lang = "m7";
-}
 
 if(!bbs.mods.avatar_lib)
 	bbs.mods.avatar_lib = load({}, 'avatar_lib.js');
diff --git a/exec/logonlist.js b/exec/logonlist.js
index 63b174aa0b..da7f527347 100644
--- a/exec/logonlist.js
+++ b/exec/logonlist.js
@@ -45,13 +45,6 @@ if(!js.global.bbs) {
 if(!bbs.mods.logonlist_lib)
 	bbs.mods.logonlist_lib = load({}, 'logonlist_lib.js');
 var options = load("modopts.js", "logonlist");
-function dump_objs(obj) {
-        Object.keys(obj).forEach(function (e) {
-                writeln(e + ': ' + JSON.stringify(obj[e]));
-        });
-}
-dump_objs(options);
-writeln("lang: " + user.lang);
 if(!options)
 	options = {};
 if(options.last_few_callers === undefined)
diff --git a/exec/noyesbar.js b/exec/noyesbar.js
index 7855eb7c43..bd97f40539 100644
--- a/exec/noyesbar.js
+++ b/exec/noyesbar.js
@@ -8,6 +8,10 @@ const yes_key = yes_str[0];
 const no_str = bbs.text(bbs.text.No);
 const no_key = no_str[0];
 
+var options = load("modopts.js", "noyesbar");
+if(!options)
+	options = {};
+
 while(console.question.substring(0, 2) == "\r\n") {
 	console.crlf();
 	console.question = console.question.substring(2);
@@ -18,18 +22,21 @@ if(console.question.substring(0, 2) == "\x01\?") {
 	console.question = console.question.substring(2);
 }
 
-console.putmsg("\x01n\x01b\x01h[\x01c@CHECKMARK@\x01b] \x01y@QUESTION->@? @CLEAR_HOT@", P_NOABORT);
+console.putmsg(options.noyes_question || "\x01n\x01b\x01h[\x01c@CHECKMARK@\x01b] \x01y@QUESTION->@? @CLEAR_HOT@", P_NOABORT);
 var affirm = false;
 while(bbs.online && !js.terminated) {
 	var str;
 	if(affirm)
-		str = format("\x01n\x01b\x01h \x01~%s \x014\x01w\x01e[\x01~%s]", no_str, yes_str);
+		str = format(options.highlight_yes_fmt || "\x01n\x01b\x01h \x01~%s \x014\x01w\x01e[\x01~%s]", no_str, yes_str);
 	else
-		str = format("\x01h\x014\x01w\x01e[\x01~%s]\x01n\x01b\x01h \x01~%s ", no_str, yes_str);
+		str = format(options.highlight_no_fmt || "\x01h\x014\x01w\x01e[\x01~%s]\x01n\x01b\x01h \x01~%s ", no_str, yes_str);
 	console.print(str);
 	var key = console.getkey(0).toUpperCase();
 	console.backspace(console.strlen(str));
-	console.print("\x01n\x01h\x01>");
+	if(console.term_supports(USER_MODE7))
+		console.print(" ");
+	else
+		console.print("\x01n\x01h\x01>");
 	if(console.aborted)
 		break;
 	if(key == '\r')
diff --git a/exec/yesnobar.js b/exec/yesnobar.js
index 6b64c4a4ec..1b7f454ced 100644
--- a/exec/yesnobar.js
+++ b/exec/yesnobar.js
@@ -1,5 +1,4 @@
 // JS version of yesnobar.src
-
 require("sbbsdefs.js", "P_NOABORT");
 
 "use strict";
@@ -9,6 +8,10 @@ const yes_key = yes_str[0];
 const no_str = bbs.text(bbs.text.No);
 const no_key = no_str[0];
 
+var options = load("modopts.js", "yesnobar");
+if(!options)
+	options = {};
+
 while(console.question.substring(0, 2) == "\r\n") {
 	console.crlf();
 	console.question = console.question.substring(2);
@@ -19,18 +22,22 @@ if(console.question.substring(0, 2) == "\x01\?") {
 	console.question = console.question.substring(2);
 }
 
-console.putmsg("\x01n\x01b\x01h[\x01c@CHECKMARK@\x01b] \x01y@QUESTION->@? @CLEAR_HOT@", P_NOABORT);
+console.putmsg(options.yesno_question || "\x01n\x01b\x01h[\x01c@CHECKMARK@\x01b] \x01y@QUESTION->@? @CLEAR_HOT@", P_NOABORT);
 var affirm = true;
 while(bbs.online && !js.terminated) {
 	var str;
 	if(affirm)
-		str = format("\x01h\x014\x01w\x01e[\x01~%s]\x01n\x01b\x01h \x01~%s ", yes_str, no_str);
+		str = format(options.highlight_yes_fmt || "\x01n\x01b\x01h \x01~%s \x014\x01w\x01e[\x01~%s]", no_str, yes_str);
 	else
-		str = format("\x01n\x01b\x01h \x01~%s \x014\x01w\x01e[\x01~%s]", yes_str, no_str);
+		str = format(options.highlight_no_fmt || "\x01h\x014\x01w\x01e[\x01~%s]\x01n\x01b\x01h \x01~%s ", no_str, yes_str);
 	console.print(str);
 	var key = console.getkey(0).toUpperCase();
 	console.backspace(console.strlen(str));
-	console.print("\x01n\x01h\x01>");
+	if(console.term_supports(USER_MODE7))
+		console.print(" ");
+	else
+		console.print("\x01n\x01h\x01>");
+	
 	if(console.aborted)
 		break;
 	if(key == '\r')
-- 
GitLab


From 6f6fefd041701deed1aa0a636c9ec0f1329bdd20 Mon Sep 17 00:00:00 2001
From: Nigel Reed <nigel@nigelreed.net>
Date: Tue, 11 Feb 2025 18:11:22 -0600
Subject: [PATCH 3/4] New files for Mode 7 support This includes menu scripts
 and files, and some other .m7 files needed to get it to run. Keep in mind a
 lot of this is incomplete and still being developed.

---
 exec/mode7.js                   | 124 ++++++++++++++++++++++++
 exec/mode7/mode7_chat.js        | 142 +++++++++++++++++++++++++++
 exec/mode7/mode7_email.js       |  88 +++++++++++++++++
 exec/mode7/mode7_forums.js      | 167 ++++++++++++++++++++++++++++++++
 src/xpdev/mode7defs.h           |  72 ++++++++++++++
 text/answer.m7                  |  15 +++
 text/bullseye.m7                |   6 ++
 text/menu/mode7/mode7_chat.m7   |   4 +
 text/menu/mode7/mode7_email.m7  |   4 +
 text/menu/mode7/mode7_forums.m7 |  20 ++++
 text/menu/mode7/mode7_main.m7   |  23 +++++
 text/menu/msghdr.m7             |   6 ++
 text/system.m7                  |  16 +++
 13 files changed, 687 insertions(+)
 create mode 100644 exec/mode7.js
 create mode 100644 exec/mode7/mode7_chat.js
 create mode 100644 exec/mode7/mode7_email.js
 create mode 100755 exec/mode7/mode7_forums.js
 create mode 100644 src/xpdev/mode7defs.h
 create mode 100644 text/answer.m7
 create mode 100755 text/bullseye.m7
 create mode 100755 text/menu/mode7/mode7_chat.m7
 create mode 100755 text/menu/mode7/mode7_email.m7
 create mode 100755 text/menu/mode7/mode7_forums.m7
 create mode 100755 text/menu/mode7/mode7_main.m7
 create mode 100644 text/menu/msghdr.m7
 create mode 100644 text/system.m7

diff --git a/exec/mode7.js b/exec/mode7.js
new file mode 100644
index 0000000000..d930a277c2
--- /dev/null
+++ b/exec/mode7.js
@@ -0,0 +1,124 @@
+// Default/Classic Synchronet Command Shell
+// replaces default.src/bin
+
+// @format.tab-size 4
+
+"use strict";
+
+require("sbbsdefs.js", "K_UPPER");
+require("userdefs.js", "UFLAG_T");
+require("nodedefs.js", "NODE_MAIN");
+require("key_defs.js", "KEY_UP");
+require("gettext.js", "gettext");
+load("termsetup.js");
+var shell = load({}, "shell_lib.js");
+
+system.settings &= ~SYS_RA_EMU; // Use (R)e-read and (A)uto-reply keys
+
+const help_key = '?';
+// If user has unlimited time, display time-used rather than time-remaining
+const time_code = user.security.exemptions & UFLAG_T ? "\x86Time Used: @TUSED@" : "\x86Time Left: @TLEFT@";
+
+const main_menu = {
+	file: "mode7/mode7_main",
+	eval: 'bbs.main_cmds++',
+	node_action: NODE_MAIN,
+	prompt: time_code
+		+ gettext("\x87->\x83Main\x87-> "),
+	command: {
+	 'C': { exec: 'mode7/mode7_chat.js' },
+	 'D': { exec: 'eotl_xtrn.js' },
+	 'E': { exec: 'mode7/mode7_email.js' },
+	 'F': { exec: 'eotl_xfer.js' },
+	 'G': { eval: 'shell.logoff(/* fast: */false)' },
+	 'N': { exec: 'mode7/mode7_forums.js' },
+	'/G': { eval: 'shell.logoff(/* fast: */true)' },
+	'/O': { eval: 'shell.logoff(/* fast: */true)' },
+	 'S': { exec: 'eotl_settings.js' },
+	 'T': { exec: '../xtrn/ansiview/ansiview.js' },
+	 'X': { exec: 'eotl_sysop.js'
+			,ars: 'SYSOP' },
+	 '!': { eval: 'bbs.menu("sysmain")'
+			,ars: 'SYSOP' },
+	},
+	nav: {
+	'\r': { },
+	},
+};
+
+var menu = main_menu;
+var last_str_cmd = "";
+
+// The menu-display/command-prompt loop
+while(bbs.online && !js.terminated) {
+	if(!(user.settings & USER_EXPERT)) {
+		console.clear();
+		bbs.menu(menu.file);
+	}
+	bbs.node_action = menu.node_action;
+	bbs.nodesync();
+	eval(menu.eval);
+	console.newline();
+	console.aborted = false;
+	console.putmsg(menu.prompt, P_SAVEATR);
+	var cmd = console.getkey(K_UPPER);
+	if(cmd > ' ')
+		console.print(cmd);
+	if(cmd == ';') {
+		cmd = console.getstr();
+		if(cmd == '!')
+			cmd = last_str_cmd;
+//		var script = system.mods_dir + "str_cmds.js";
+//		if(!file_exists(script))
+//			script = system.exec_dir + "str_cmds.js";
+//		js.exec(script, {}, cmd);
+		load({}, "str_cmds.js", cmd);
+		last_str_cmd = cmd;
+		continue;
+	}
+	if(cmd == '/') {
+		cmd = console.getkey(K_UPPER);
+		console.print(cmd);
+		cmd = '/' + cmd;
+	}
+	if(cmd > ' ') {
+		bbs.log_key(cmd, /* comma: */true);
+	}
+	console.newline();
+	console.line_counter = 0;
+	if(cmd == help_key) {
+		if(user.settings & USER_EXPERT)
+			bbs.menu(menu.file);
+		continue;
+	}
+	var menu_cmd = menu.command[cmd];
+	if(!menu_cmd) {
+		console.print("\r\n\x81" + gettext("Unrecognized command."));
+		if(user.settings & USER_EXPERT)
+			console.print(" " + gettext("Hit") + "ƒ'" + help_key + "'†" + gettext("for a menu."));
+		console.newline();
+		continue;
+	}
+	if(!bbs.compare_ars(menu_cmd.ars))
+		console.print(menu_cmd.err);
+	else {
+		if(menu_cmd.msg)
+			console.print(menu_cmd.msg);
+		if(menu_cmd.eval)
+			eval(menu_cmd.eval);
+		if(menu_cmd.exec) {
+			var script = system.mods_dir + menu_cmd.exec;
+			if(!file_exists(script))
+				script = system.exec_dir + menu_cmd.exec;
+			if(menu_cmd.args)
+				js.exec.apply(null, [script, {}].concat(menu_cmd.args));
+			else
+				js.exec(script, {});
+		}
+	}
+}
+// Can't do these statically through initialization:
+main_menu.nav[KEY_UP] = { eval: 'shell.sub_up()' };
+main_menu.nav[KEY_DOWN] = { eval: 'shell.sub_down()' };
+main_menu.nav[KEY_RIGHT] = { eval: 'shell.grp_up()' };
+main_menu.nav[KEY_LEFT] = { eval: 'shell.grp_down()' };
diff --git a/exec/mode7/mode7_chat.js b/exec/mode7/mode7_chat.js
new file mode 100644
index 0000000000..5be62f3afd
--- /dev/null
+++ b/exec/mode7/mode7_chat.js
@@ -0,0 +1,142 @@
+// $Id: chat_sec.js,v 1.15 2020/01/05 23:50:35 rswindell Exp $
+
+// Chat Section for any/all Synchronet command shells
+
+"use strict";
+
+require("sbbsdefs.js", 'USER_EXPERT');
+require("nodedefs.js", 'NODE_CHAT');
+require("text.js", 'R_Chat');
+
+// Over-ride these default values by creating/modifying the [chat] section in your ctrl/modopts.ini file
+var options = load("modopts.js", "chat");
+if (!options)
+	options = load("modopts.js", "chat_sec");
+if (!options)
+	options = {};
+if (options.irc === undefined)
+	options.irc = true;
+if (options.irc_server === undefined)
+	options.irc_server = "irc.synchro.net 6667";
+if (options.irc_channel === undefined)
+	options.irc_channel = "#Synchronet";
+if (options.irc_seclevel === undefined)
+	options.irc_seclevel = 90;
+if (options.finger === undefined)
+	options.finger = true;
+if (options.imsg === undefined)
+	options.imsg = true;
+
+if(user.security.restrictions & UFLAG_C) {
+    write(bbs.text(R_Chat));
+	exit(0);
+}
+
+// Set continue point for main menu commands
+menu:
+while(1) {
+	var str="";
+	// Display TEXT\MENU\CHAT.* if not in expert mode
+	bbs.nodesync();
+	if(!(user.settings & USER_EXPERT)) {
+		bbs.menu("mode7/mode7_chat");
+				}
+
+	// Update node status
+	bbs.node_action = NODE_CHAT;
+	bbs.nodesync();
+	if(user.compare_ars("exempt T"))
+        	console.putmsg ("\x86Time Used: @TUSED@");
+	else
+        	console.putmsg ("\x86Time Left: @TLEFT@");
+		
+	// Display main Prompt
+	console.putmsg ("\x87->\x83Chat Menu\x87-> ");
+
+	var keys = "ACEMNPQSTYWX?$%";
+	if(options.imsg)
+		keys += "I";
+	if(options.irc)
+		keys += "R";
+	if(options.finger)
+		keys += "F";
+	switch(console.getkeys(keys, K_UPPER)) {
+		case "S":
+			var val = user.chat_settings ^= CHAT_SPLITP;
+			writeln("");
+			break;
+		case "A":
+			var val = user.chat_settings ^= CHAT_NOACT;
+			writeln("");
+			system.node_list[bbs.node_num-1].misc ^= NODE_AOFF;
+			break;
+		case 'P':
+			var val = user.chat_settings ^= CHAT_NOPAGE;
+			writeln("");
+			system.node_list[bbs.node_num-1].misc ^= NODE_POFF;
+			break;
+		case 'E':
+			if(user.compare_ars("SYSOP")) {
+				writeln("");
+				system.operator_available = !system.operator_available;
+			}
+			break;
+		case 'F':
+			writeln("");
+			load("finger.js");
+			break;
+		case 'I':
+			writeln("");
+			load({}, "sbbsimsg.js");
+			break;
+		case 'R':
+		{
+			var server=options.irc_server;
+			if(user.security.level >= options.irc_seclevel || user.security.exemptions&UFLAG_C) {
+				write("\r\n\x01n\x01y\x01hIRC Server: ");
+				server=console.getstr(options.irc_server, 40, K_EDIT|K_LINE|K_AUTODEL);
+				if(console.aborted || server.length < 4)
+					break;
+			}
+			if(server.indexOf(' ') < 0)
+				server += " 6667";
+			write("\r\n\x01n\x01y\x01hIRC Channel: ");
+			var channel=console.getstr(options.irc_channel, 40, K_EDIT|K_LINE|K_AUTODEL);
+			if(!console.aborted && channel.length) {
+				log("IRC to " + server + " " + channel);
+				bbs.replace_text(NodeActionCustom,"in Internet Relay Chat via " + client.protocol); 
+				bbs.node_action = NODE_CUSTOM;
+				bbs.nodesync();
+				bbs.exec("?irc -a " + server + " " + channel); // can't be load()ed because it calls exit()
+				bbs.revert_text(NodeActionCustom);
+			}
+			break;
+		}
+		case 'C':
+			bbs.multinode_chat();
+			break;
+		case 'N':
+			bbs.private_chat();
+			break;
+		case 'Y':
+			if(!bbs.page_sysop())
+				bbs.page_guru();
+			break;
+		case 'T':
+			bbs.page_guru();
+			break;
+		case 'M':
+			bbs.exec_xtrn("MULTIRELAYCHAT");
+			break;
+		case '?':
+			if(user.settings & USER_EXPERT)
+				bbs.menu("mode7/mode7_chat");
+			break;
+                case 'Q':
+			break menu;
+
+		default:
+			console.clear();
+			break;
+	}
+}
diff --git a/exec/mode7/mode7_email.js b/exec/mode7/mode7_email.js
new file mode 100644
index 0000000000..653d248173
--- /dev/null
+++ b/exec/mode7/mode7_email.js
@@ -0,0 +1,88 @@
+// E-mail Section
+
+// $Id: email_sec.js,v 1.10 2020/04/24 08:05:39 rswindell Exp $
+
+// Note: this module replaces the old ### E-mail section ### Baja code in exec/*.src
+// replace "call E-mail" with "exec_bin email_sec"
+
+require("sbbsdefs.js", "WM_NONE");
+require("userdefs.js", "USER_EXPERT");
+var text = bbs.mods.text;
+if(!text)
+	text = load(bbs.mods.text = {}, "text.js");
+var userprops = bbs.mods.userprops;
+if(!userprops)
+	userprops = load(bbs.mods.userprops = {}, "userprops.js");
+const ini_section = "netmail sent";
+
+const NetmailAddressHistoryLength = 10;
+
+while(bbs.online) {
+	if(!(user.settings & USER_EXPERT))
+		bbs.menu("mode7/mode7_email");
+
+	bbs.replace_text(720,'at email menu');
+        bbs.node_action = NODE_CUSTOM;
+	bbs.nodesync();
+	bbs.revert_text(720);
+
+	writeln();
+        if(user.compare_ars("exempt T"))
+                console.putmsg ("\x86Time Used: @TUSED@");
+        else
+                console.putmsg ("\x86Time Left: @TLEFT@");
+
+        // Display main Prompt
+        console.putmsg ("\x87->\x83Email Menu\x87-> ");
+
+	var wm_mode = WM_NONE;
+	var cmdkeys = "EFKNRSUQ?\r";
+	switch(console.getkeys(cmdkeys,K_UPPER)) {
+		case 'R':	// Read your mail
+			if(user.compare_ars("GUEST")) {
+				console.putmsg("Guests are not permitted to read emails\r\n");
+				break;
+			}
+			bbs.read_mail(MAIL_YOUR, user.number);
+			break;
+		case 'U':	// Read your un-read mail
+			if(user.compare_ars("GUEST")) {
+				console.putmsg("Guests are not permitted to read emails\r\n");
+				break;
+			}
+			bbs.read_mail(MAIL_YOUR, user.number, LM_UNREAD|LM_REVERSE);
+			break;
+		case 'K':	// Read/Kill sent mail
+			if(user.compare_ars("GUEST")) {
+				console.putmsg("Guests are not permitted to read emails\r\n");
+				break;
+			}
+			bbs.read_mail(MAIL_SENT, user.number, LM_REVERSE);
+			break;
+		case 'E':	// Send Feedback
+			bbs.email(/* user # */1, bbs.text(text.ReFeedback));
+			break;
+		case 'F':
+			if(user.compare_ars("GUEST")) {
+				console.putmsg("Guests are not permitted to search emails\r\n");
+				break;
+			}
+			bbs.exec("?../xtrn/DDMsgReader/DDMsgReader.js -search=keyword_search -subBoard=mail -startMode=list");
+			break;
+		case 'S':	// Send Mail
+		case 'N':	// Send Mail
+			if(user.compare_ars("GUEST")) {
+				console.putmsg("You are not permitted to send mail as a guest\r\n");
+				break;
+			}
+			bbs.exec("?/sbbs/xtrn/addressbook/addressbook.js",null,"/sbbs/xtrn/addressbook/");
+			break;
+		case 'Q':	// Quit
+		case '\r':
+			exit(0);
+		case '?':	// Display menu
+			if(user.settings & USER_EXPERT)
+				bbs.menu("mode7/mode7_email.js");
+			break;
+	}
+}
diff --git a/exec/mode7/mode7_forums.js b/exec/mode7/mode7_forums.js
new file mode 100755
index 0000000000..1739b279a7
--- /dev/null
+++ b/exec/mode7/mode7_forums.js
@@ -0,0 +1,167 @@
+// Forums Section
+
+'use strict';
+require("sbbsdefs.js", "K_UPPER");
+require("userdefs.js", "USER_EXPERT");
+require("nodedefs.js", "NODE_MAIN");
+require("gettext.js", "gettext");
+
+var shell = load({}, "shell_lib.js");
+var text = bbs.mods.text;
+var last_str_cmd = "";
+if(!text)
+	text = load(bbs.mods.text = {}, "text.js");
+
+main:
+while(bbs.online && !console.aborted) {
+	console.aborted = false;
+	if(!(user.settings & USER_EXPERT)) {
+		console.home();
+		bbs.menu("mode7/mode7_forums");
+	}
+
+	bbs.replace_text(720,'at forums menu via ' + client.protocol.toLowerCase());
+        bbs.node_action = NODE_CUSTOM;
+	bbs.nodesync();
+	bbs.revert_text(720);
+
+	console.crlf();
+        console.putmsg ("\x83@GRP@\x86<@GN@>\x83@SUB@\x86<@SN@>");
+	console.crlf();
+	console.crlf();
+        if(user.compare_ars("exempt T"))
+                console.putmsg ("\x86Time Used: @TUSED@");
+        else
+                console.putmsg ("\x86Time Left: @TLEFT@");
+
+        // Display main Prompt
+        console.putmsg ("\x87->\x83Forums\x87-> ");
+
+	var cmd = console.getkey(K_UPPER);
+	console.print(cmd);
+	switch(cmd) {
+		case 'N':	writeln('\r\n\x86' + gettext("New Message Scan"));
+				bbs.scan_subs(SCAN_NEW);
+				break;
+		case 'R':	bbs.scan_msgs();
+				break;
+		case 'L':	bbs.list_msgs()
+				break;
+		case 'P':	bbs.post_msg();
+				break;
+		case 'C':	writeln('\r\n\x86' + gettext("Continuous New Message Scan") + '\r\n');
+				bbs.scan_subs(SCAN_CONT);
+				break;
+		case 'C':	bbs.exec("?filescancfg.js");
+				break;
+		case 'M':	console.newline();
+				var res = bbs.exec("?postpoll.js");
+				if(console.aborted) {
+					console.aborted = false;
+				}
+				if(res > 1)
+					console.pause();
+				break;
+		case 'V':	bbs.exec("?scanpolls.js");
+				break;
+		case 'T':	console.home();
+				bbs.qwk_sec()
+				break;
+		case 'J':	shell.select_msg_area()
+				break;
+		case 'F':	writeln('\r\n\x86' + gettext("Find Text in Messages") + '\r\n');
+				bbs.scan_subs(SCAN_FIND);
+				break;
+		case 'S':	writeln('\r\n\x86' + gettext("Scan for Messages Posted to You") + '\r\n');
+				bbs.scan_subs(SCAN_TOYOU)
+				break;
+		case '&':	console.home()
+				bbs.exec("?eotl_msgscanconf.js");
+				break;
+		case '!':	if (user.compare_ars("SYSOP"))
+					bbs.menu("sysmain");
+				break;
+		case '*':	shell.show_subs(bbs.curgrp)
+				break;
+		case '#':	writeln('\r\n\x86' + gettext("Type the actual number, not the symbol."));
+				console.pause();
+				break;
+		case KEY_RIGHT:
+		case '>':
+		case '}':
+		case ')':
+		case '+':
+				shell.sub_up();
+				break;
+		case KEY_LEFT:
+		case '<':
+		case '{':
+		case '(':
+		case '-':	shell.sub_down();
+				break;
+		case KEY_UP:
+		case ']': 	shell.grp_up();
+				break;
+		case KEY_DOWN:
+		case '[': 	shell.grp_down();
+				break;
+		case '?':	// Display menu
+			if(user.settings & USER_EXPERT)
+				bbs.menu("mode7/mode7_forums");
+				break;
+		case 'Q':	exit();
+				break;
+	}
+	if(cmd >= '1' && cmd <= '9') {
+		shell.get_sub_num(cmd);
+		continue;
+	}
+	if(cmd == ';' && user.compare_ars("SYSOP")) {
+		cmd = console.getstr();
+		if(cmd == '!')
+			cmd = last_str_cmd;
+		var script = system.mods_dir + "str_cmds.js";
+		if(!file_exists(script))
+			script = system.exec_dir + "str_cmds.js";
+		js.exec(script, {}, cmd);
+		last_str_cmd = cmd;
+		continue;
+	}
+
+	if(cmd == '/') {
+		cmd = console.getkey(K_UPPER);
+		console.print(cmd);
+		switch(cmd) {
+			case 'D':	if (user.compare_ars("REST NOT D")) {
+						writeln('\r\n\x86' + gettext("Download File(s) from User(s)"));
+						shell.download_user_files();
+					}
+					continue main;
+			case 'F':	writeln('\r\n\x86' + gettext("Find Text in Messages"));
+					bbs.scan_subs(SCAN_FIND);
+					continue main;
+			case 'N':	bbs.scan_subs(SCAN_NEW, /* all */true);
+					continue main;
+			case 'U':	writeln('\r\n\x86' + gettext("Upload File to User"));
+					shell.upload_user_file();
+					continue main;
+			case 'O':	shell.logoff(/* fast: */true);
+					continue main;
+			case 'S':	bbs.scan_subs(SCAN_TOYOU, /* all */true)
+					continue main;
+			case '*':	shell.show_grps();
+					continue main;
+			case '#':	writeln('\r\n\x86' + gettext("Type the actual number, not the symbol."));
+					console.pause();
+					continue main;
+		}
+			
+		if(cmd >= '1' && cmd <= '9') {
+			shell.get_grp_num(cmd);
+			continue;
+		}
+
+        }
+	console.home();
+	console.aborted = false;
+}
diff --git a/src/xpdev/mode7defs.h b/src/xpdev/mode7defs.h
new file mode 100644
index 0000000000..2f3b279bcb
--- /dev/null
+++ b/src/xpdev/mode7defs.h
@@ -0,0 +1,72 @@
+/* Commodore/PET definitions */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
+ *																			*
+ * This library is free software; you can redistribute it and/or			*
+ * modify it under the terms of the GNU Lesser General Public License		*
+ * as published by the Free Software Foundation; either version 2			*
+ * of the License, or (at your option) any later version.					*
+ * See the GNU Lesser General Public License for more details: lgpl.txt or	*
+ * http://www.fsf.org/copyleft/lesser.html									*
+ *																			*
+ * For Synchronet coding style and modification guidelines, see				*
+ * http://www.synchro.net/source.html										*
+ *																			*
+ * Note: If this box doesn't appear square, then you need to fix your tabs.	*
+ ****************************************************************************/
+#ifndef _MODE7DFS_H_
+#define _MODE7DEFS_H_
+
+enum mode7_char {
+	/* Colors */
+	MODE7_ALPHA_RED			= 129,
+	MODE7_ALPHA_GREEN		= 130,
+	MODE7_ALPHA_YELLOW		= 131,
+	MODE7_ALPHA_BLUE		= 132,
+	MODE7_ALPHA_MAGENTA		= 133,
+	MODE7_ALPHA_CYAN		= 134,
+	MODE7_ALPHA_WHITE		= 135,
+	MODE7_FLASH				= 136,
+	MODE7_STEADY			= 137,
+	MODE7_NORMAL_HEIGHT		= 140,
+	MODE7_DOUBLE_HEIGHT 	= 141,
+	MODE7_GRAPHIC_RED		= 145,
+	MODE7_GRAPHIC_GREEN		= 146,
+	MODE7_GRAPHIC_YELLOW 	= 147,
+	MODE7_GRAPHIC_BLUE		= 148,
+	MODE7_GRAPHIC_MAGENTA 	= 149,
+	MODE7_GRAPHIC_CYAN		= 150,
+	MODE7_GRAPHIC_WHITE		= 151,
+	MODE7_CONCEAL			= 152,
+	MODE7_CONTIGUOUS_GFX 	= 153,
+	MODE7_SEPARATED_GFX		= 154,
+	MODE7_BLACK_BG			= 156,
+	MODE7_NEW_BG			= 157,
+	MODE7_HOLD_GFX			= 158,
+	MODE7_RELEASE_GFX		= 159,
+	
+	/* Cursor movement */
+	MODE7_LEFT        = 8,
+	MODE7_RIGHT       = 9,
+	MODE7_DOWN        = 10,
+	MODE7_UP          = 11,
+	MODE7_HOME        = 30,
+	MODE7_CLEAR       = 12,
+	/* Symbols (which don't align with ASCII) */
+	MODE7_BRITPOUND		= 96,
+	MODE7_QUARTER		= 123,
+	MODE7_HALF			= 92,
+	MODE7_THREE_QUARTER	= 195,
+	MODE7_DIVIDE		= 196
+	/* Replacement chars (missing ASCII chars) */
+//	PETSCII_BACKSLASH   = '/',  // the 109 graphics char is an 'M' in shifted/text mode :-(
+//	PETSCII_BACKTICK    = 0xAD, // a graphics char actually
+//	PETSCII_TILDE       = 0xA8, // a graphics char actually
+//	PETSCII_UNDERSCORE  = 0xA4, // a graphics char actually
+};
+
+#endif /* Don't add anything after this line */
diff --git a/text/answer.m7 b/text/answer.m7
new file mode 100644
index 0000000000..e491c9f7c4
--- /dev/null
+++ b/text/answer.m7
@@ -0,0 +1,15 @@
+��     �|,$|h4xl0|,h<th<|_<th<(|$       ���     s{5s{5�j5�pj7}ju�j5�jw �        �///////,,.,,.,-.,,-.,-,,-.,-,/,//////// 
+CLIENT
+�CONN: @CONN@
+�ADDR: @HOST@
+�IP  : @IP@
+SERVER
+�NAME:�@BBS@
+�ADDR: @HOSTNAME@
+�NODE: @NODE@ (of @TNODE@)
+�TIME: @DATETIME@ @TIMEZONE@
+�ADMN: @SYSOP@
+
+�If you are a new user to the system,
+�type�"New"�now. Otherwise enter your
+�username or number now.
diff --git a/text/bullseye.m7 b/text/bullseye.m7
new file mode 100755
index 0000000000..343c8a2e31
--- /dev/null
+++ b/text/bullseye.m7
@@ -0,0 +1,6 @@
+@CLS@���|l4|h4| | |$l<h4x|0|,�Last updated: �����{4oz%�0�0�1j5j5�j5s��Feb 5 2025    ��//,,.-,/,.,.,.-.-.,-.,,///////////////                                         �7````````````````````````````````````k �5���1 - Statistics for             ��j �5���@BBS-L........................@��j
+�5                                    j
+�5���2 - New user instructions      ��j �5                                    j �5���3 - About Synchronet           ��j �5                                    j �5                                    j �5                                    j �5                                    j �5                                    j �``````````````````````````````````````
+
+@EOF@
+https://zxnet.co.uk/teletext/editor/#0:QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECACdL_NjT5oafEHxB8SbHmhp4-MPiwHMw8-iDrwyYemXI6QHAJ0v_9tN_pL_Yf2H9jqa6mv_U15_wcbLiQNUDJgyaoECBAcIr16xYuWrF6xcsXLFy1ctXLFq5YsXr169evXr169evXr16BAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQICbdGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0aPWgJtQh0GxQLUFPph6aefTTj5oM2_kgQIECBAgQIECBAgOE9SAm1CHQcCFCprZi5cuXLly5cuXLly5cuXLly5cuXLoCA4T1ICbVAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECDUgJtQh0GyQLUE7L3QdeeXkg07ufTl1x9NO_dzQIECBAgOE9SAm1QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAg1ICbUIdBs0C1BBxb-vRBT87sejlv3ZeiBAgQIECBAgQIDhPUgJtUCBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQINSAm1QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAg1ICbVAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECDUgJtUCBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQINSAm1QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAg1ICaNGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0aNGgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECA:PS=0:RE=0:zx=Jm00
diff --git a/text/menu/mode7/mode7_chat.m7 b/text/menu/mode7/mode7_chat.m7
new file mode 100755
index 0000000000..ac048cba1e
--- /dev/null
+++ b/text/menu/mode7/mode7_chat.m7
@@ -0,0 +1,4 @@
+@CLS@���|,h4h4xl0l<   �Please use chat      �����pj7k5�k5j5   �responsibly.         ��//,,-.-.,-.-./////////////////////////                                         ���(C)hat multinode                    ����(N)ode to Node Chat                 ����(Y)ell for the Sysop                ����(T)alk with the AI Guru             ����(F)inger Remote Query               ����(R)elay Chat (IRC)                  ����(I)nter BBS Instant Messaging       ����(M)ulti Relay Chat                  ����                                    ���                                     ����(A)ctivity Alerts   : @ALERTS|L3@           ����(P)aging Allowed    : @PAGER|L3@           ����(S)plit Screen Chat : @SPLITP|L3@           �@SHOW:!SYSOP@���The Sysop is: @SYSAVAIL|L18@    �@SHOW@@SHOW:SYSOP@���(E)nable Paging     : @SYSAVAILYN|L3@           ���                                     �@SHOW@���        (Q)uit to main menu         �
+
+@EOF@
+https://zxnet.co.uk/teletext/editor/#0:QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECACdL_FmhpoaeNjDY8QIEAOhsy4eeVB155UGPRh6IECBAgQHAJ0v_4am-tr_1tdTVAgQA-WXnw37uenFs8rkCBAgQIECBAcIr16xYtXLVyxauWrl69evXr169evXr169evXr169evXr16BAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIAZ0IohqdGHog29dnTTu35MqBAgQIECBAgQIECBAgQIECBAcBnQiicp35MqDpvQTt-TKgh6MPRAgQIECBAgQIECBAgQIEBwGdCKLKnLs2IM2_kg6aMqCn557-CBAgQIECBAgQIECBAgQHAZ0IoqKcOzWg76emhB00ZUEGSgj9eXVAgQIECBAgQIECBAcBnQiiMp07s-XkgpZdu_plQUeuXl5QIECBAgQIECBAgQIEBwGdCKKSnLsw-UEPRh6IFEmlDUoECBAgQIECBAgQIECBAgQHAZ0Iokqd3TLyQQoVNBJ3c-mHd0QTcvPnhz6d2dAgQIECBAcBnQiiap67OmlBSy7MPlBD0YeiBAgQIECBAgQIECBAgQIEBwGdCIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQHAh0GogqcfTT209PKCDsy8unNAgQOkECDMi0qlP5MZwECBAcCHQaigpw59O7Ogg7Nm_vlyIECBA6QQKEGPFpfJjOAgQIEBwIdBqKanhs09EFPHyy5dyCHow9EDpBAp0JkmpQ-TGcBAgQHAh0NApyJ9dAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAcBnQiBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIEBwYgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIBiBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECA:PS=0:RE=0:zx=XN00
diff --git a/text/menu/mode7/mode7_email.m7 b/text/menu/mode7/mode7_email.m7
new file mode 100755
index 0000000000..2b94aef1be
--- /dev/null
+++ b/text/menu/mode7/mode7_email.m7
@@ -0,0 +1,4 @@
+@CLS@���|,_<<t_<th4|  �Sending of spam is   �����sj55�j7�j5�p �strictly prohibited. ��//,,-..,-.,-.,,///////////////////////                                         �7````````````````````````````````````k �5                            �pppppp�j �5                            �54<4 j�j �5                            �55`5wj�j �5                            �-,,,,.�j �5                                    j �5�(S) Send Local, QWK, or Email     �j �5�(R) Read email sent to you        �j �5�(U) Read your unread messages     �j �5�(E) Email the Sysop               �j �5�(F) Find text in emails           �j �5�(K) Kill/Read sent mail           �j �5                                    j �5 �       (Q)uit to main menu       �j �5                                    j �``````````````````````````````````````
+
+@EOF@
+https://zxnet.co.uk/teletext/editor/`0:QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECACdL_FmB486YHnTQ0-IEAOnl3ZNO7Og35kHPhh2oNPNAgQHAJ0v_56mrX_qb_9TX_wQA-fTlpx9NnlBw5b9GnFp6Zci5AcIr16xYtXLli1csWrlixevXr169evXr169evXr169evXr16BAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQICjdGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0aPWgKNUCBAgQIECBAgQIECBAgQIECBAgQIECBAgJ8OHDhw4FNSAo1QIECBAgQIECBAgQIECBAgQIECBAgQIECAm1aPGiDUU1ICjVAgQIECBAgQIECBAgQIECBAgQIECBAgQICbVqja99RTUgKNUCBAgQIECBAgQIECBAgQIECBAgQIECBAgJrVixYsXFNSAo1QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAg1ICjUGopqUFPLuyIJm_Hh2LEFGvLWIN_JBF24dOxAgQIEBTUgKNQaikpQUsuHIgy7cOnYg55d3RB03oPO_qgQIECBAgQFNSAo1BqKqlBSy4ciDzv68kHXdyy4ciDbl588OfLzQIECBAU1ICjUGoiqUEXbh07EHTRlQU_PPfwQIECBAgQIECBAgQIEBTUgKNQaiMpQRtO7Ig6ZfHRBp3IMu3Dp2c0CBAgQIECBAgQFNSAo1BqJalBL07Ni-llw5EHPLu6INuHTsQIECBAgQIECBAU1ICjVAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECDUgKNUANAgQIECBAooqeunog6b0G3Dp3INuXd1QIECBAgQFNSAo1QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAg1ICiNGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0aNGgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECA:PS=0:RE=0:zx=EA00
diff --git a/text/menu/mode7/mode7_forums.m7 b/text/menu/mode7/mode7_forums.m7
new file mode 100755
index 0000000000..76f89fa467
--- /dev/null
+++ b/text/menu/mode7/mode7_forums.m7
@@ -0,0 +1,20 @@
+���|,h<|h<th4|_<<th<$�Please use
+����`ju�j7}*u?j55�b{5�responsibly.
+�//,/-,,-.,/,.-..,-,.///////////////////���(N)ew Message Scan
+���(R)ead Messages
+���(L)ist Messages
+���(P)ost New Message
+���(C)ontinuous New Scan
+���(M)ake a Poll
+���(V)iew/Vote in Poll
+���(T)ransfer QWK Packet
+���(F)ind Text in Messages
+���(S)can For Messages to You
+���(J)ump to New Area
+���< > _  Select Subboard
+���[ ] /_ Select Group
+���(&) Message scan config
+
+���(Q)uit to Main Menu
+@EOF@
+https://zxnet.co.uk/teletext/editor/#0:QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECACdL_Fmh580POmhp8wPHnTQ8SA6GzLh55UHXnlQIECBAgQIAJ0v_R6uv_U3-qur_U1a_8XtqD5ZefDfu56cWzyuQIECBAgIr16xetWLFq5YvWLlq5csWrFy9evXr169evXr169evXr168GdCKJynL3QTcvPnhz5UFPHh3IECBAgQIECBAgQIECBAgQIAZ0IopKcuHIgm5efPDny80CBAgQIECBAgQIECBAgQIECBAgBnQiiYp08-iCbl588OfLzQIECBAgQIECBAgQIECBAgQIECAGdCKKCnfz6IJ2Xugm5efPDnyoECBAgQIECBAgQIECBAgQIAZ0Iohqd-7pp3dd_XmgnZe6Cnjw7kCBAgQIECBAgQIECBAgBnQiiapw68qDCgob9mxAgQIECBAgQIECBAgQIECBAgQIECAGdCKKynTl7r62_plQadyChv2bECBAgQIECBAgQIECBAgQIAZ0IoqKeWHdzzZeSCjXloKGHHry9ECBAgQIECBAgQIECBAgAnQ6iMp07siCpl8dEGncgm5efPDny80CBAgQIECBAgQIECACdDqKanHh3II2_kgm5efPDny80HTegs7-qBAgQIECBAgQIAh0GolKeu3gg6b0E7L3QQeWXCgQIECBAgQIECBAgQIECBAgCHQbxA-QX0CCnl2ZcfRBT64sW_DyyIECBAgQIECBAgQIECAIdB20F1AvvoKeXZlx9EEflv68ECBAgQIECBAgQIECBAgQIAh0GoTKUE3Lz54c-VBzx4dyDHv3ZtOdAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECAodBqKKnrp6IOm9BNw6dyCbl3dUCBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECA:PS=0:RE=0:zx=TD00
diff --git a/text/menu/mode7/mode7_main.m7 b/text/menu/mode7/mode7_main.m7
new file mode 100755
index 0000000000..7936030474
--- /dev/null
+++ b/text/menu/mode7/mode7_main.m7
@@ -0,0 +1,23 @@
+���|,$|h4xl0|,h4h4|,th<|_<th<(|$�
+���s{5s{5�j5�pj7k5�`}ju�j5�jw ��
+�//,,.,,.,-.,,-.-.,/,-,,-.,-,/,/////////���@DATETIMEZONE|C40@
+���(N)etworked Forums           � �5
+��                              � �5
+���(E)mail and Netmail          � �5
+��                              � �5
+���(F)ile Transfers             � �5
+��                              � �5
+���(C)hat and InterBBS Comms    � �5
+��                              � �5
+���(D)oors and External Programs� �5
+��                              � �5
+���(S)ettings                   � �5
+��                              � �5
+���(G)oodbye - Logoff           � �5
+� ppppppppppppppppppppppppppppppppp5
+���CTRL-C�Abort Text Display
+���CTRL-P�Send Private Messages
+���CTRL-U�Display Who's Online
+@EOF@
+
+https://zxnet.co.uk/teletext/editor/#0:QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECACdL_FiT5oaeNjD4s0NNDT4s6aHnzA86aHij4kQIECBAgQIAJ0vz9teftr_1Nf_DU31tf6P7q6_9TX_q7oP6BAgQIECBAgIr16xYuWLFyxauWLFq5auWL1i1YsWrli1YvWL169evXr169AgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIAh0OonKcvTvv5a8uRBG38uu3mgQIECBAgQIECAogONUCBAgCHUCBAgQIECBAgQIECBAgQIECBAgQIECBAgQICiA41QIECAIdDqIqnbh07EGHdkQTsvTbh07ECBAgQIECBAgKIDjVAgQIAh1AgQIECBAgQIECBAgQIECBAgQIECBAgQIECAogONUCBAgCHQ6iMp07MqCpyw7uebLy5oECBAgQIECBAgQICiA41QIECAIdQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgKIDjVAgQIAh0OohqdGHogw7siCTu6ZeUKFTQQ9-3bzQIECAogONUCBAgCHUCBAgQIECBAgQIECBAgQIECBAgQIECBAgQICiA41QIECAIdDqIinfv5c0GHdkQRfHTLy3YdiChy35-WHbzKIDjVAgQIAh1AgQIECBAgQIECBAgQIECBAgQIECBAgQIECAogONUCBAgCHQ6impy9Omndn5oECBAgQIECBAgQIECBAgQICiA41QIECAIdQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgKIDjVAgQIAh0Oojqd-_Ji85UC1BM359-bMgQIECBAgQIECAogONUCBAgKIOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDhw4cOHDg1QIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIAJ0HDqUpi2GHg4t_LogqZfHRBE08-GzD5QIECBAgQIECBAgAnQcOpSmLaAenl3ZEFDlp7YemVBNy8-eHPl5oECBAgQIECACdBw6lKYtqh4mnnw2YfKCvo3p-aCfu2ad2VAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECBAgQIECA:PS=0:RE=0:zx=BU00
diff --git a/text/menu/msghdr.m7 b/text/menu/msghdr.m7
new file mode 100644
index 0000000000..8155870f61
--- /dev/null
+++ b/text/menu/msghdr.m7
@@ -0,0 +1,6 @@
+���Subj:�@MSG_SUBJECT-L................@
+���Attr:�@MSG_ATTR-L...................@
+���To  :�@MSG_TO-L.....................@
+���From:�@MSG_FROM-L...................@
+���Date:�@MSG_DATE-L..........@@MSG_TIMEZONE-L8@
+
diff --git a/text/system.m7 b/text/system.m7
new file mode 100644
index 0000000000..4cb8c6f521
--- /dev/null
+++ b/text/system.m7
@@ -0,0 +1,16 @@
+@CLS@‚@BBS@‡located in‚@LOCATION@‡
+with your sysop,‚@SYSOP@‡here to serve you.
+
+‚@HOSTNAME@‡is running‚@OS_VER@‡
+and has been online continuously for‚@UPTIME@‡hours.
+
+‚System Totals:
+
+Users:    …@TUSER|L.......@‡today:…@STATS.NUSERS@
+Logons:   …@STATS.LOGONS|L@‡today:…@STATS.LTODAY@
+Timeused: …@STATS.TIMEON|L@‡today:…@STATS.TTODAY@
+Messages: …@TMSG|L........@‡today:…@STATS.PTODAY@
+E-mail:   …@MAILW:0|L.....@‡today:…@STATS.ETODAY@
+Feedback: …@MAILW:1|L.....@‡today:…@STATS.FTODAY@
+Files:    …@TFILE|L.......@‡today:…@STATS.ULS@
+               Downloaded today: …@STATS.DLS@ (@STATS.DLB@ bytes)
-- 
GitLab


From e5b2ee64087fd4f95fb4e5277ac0b33236ca30e3 Mon Sep 17 00:00:00 2001
From: Nigel Reed <nigel@nigelreed.net>
Date: Tue, 11 Feb 2025 19:08:42 -0600
Subject: [PATCH 4/4] Modified with Deuce's recommandation

---
 src/xpdev/mode7defs.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/xpdev/mode7defs.h b/src/xpdev/mode7defs.h
index 2f3b279bcb..4dd59ca0b3 100644
--- a/src/xpdev/mode7defs.h
+++ b/src/xpdev/mode7defs.h
@@ -60,8 +60,8 @@ enum mode7_char {
 	MODE7_BRITPOUND		= 96,
 	MODE7_QUARTER		= 123,
 	MODE7_HALF			= 92,
-	MODE7_THREE_QUARTER	= 195,
-	MODE7_DIVIDE		= 196
+	MODE7_THREE_QUARTER	= 125,
+	MODE7_DIVIDE		= 126
 	/* Replacement chars (missing ASCII chars) */
 //	PETSCII_BACKSLASH   = '/',  // the 109 graphics char is an 'M' in shifted/text mode :-(
 //	PETSCII_BACKTICK    = 0xAD, // a graphics char actually
-- 
GitLab