From 40280934edd931d5d3516b7e18b57b8f93f733a3 Mon Sep 17 00:00:00 2001
From: "Rob Swindell (on Debian Linux)" <rob@synchro.net>
Date: Fri, 26 Apr 2024 17:09:53 -0700
Subject: [PATCH] Convert CP437 to UTF-8 (when necessary) for *nix stdio doors
 (e.g. DOSEMU)

... as requested by "ben" in #synchronet at irc.synchro.net

Currently, this assumes all stdio doors aren't "UTF-8 native", which might be
an incorrect assumption.
---
 src/sbbs3/xtrn.cpp | 44 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/src/sbbs3/xtrn.cpp b/src/sbbs3/xtrn.cpp
index ba7f9a62d1..44a2fe7e23 100644
--- a/src/sbbs3/xtrn.cpp
+++ b/src/sbbs3/xtrn.cpp
@@ -22,6 +22,8 @@
 #include "sbbs.h"
 #include "cmdshell.h"
 #include "telnet.h"
+#include "unicode.h"
+#include "utf8.h"
 
 #include <signal.h>			// kill()
 
@@ -212,6 +214,42 @@ static void petscii_convert(BYTE* buf, uint len)
 	}
 }
 
+static BYTE* cp437_to_utf8(BYTE* input, size_t& len, BYTE* outbuf, size_t maxlen)
+{
+	size_t outlen = 0;
+	for (size_t i = 0; i < len; ++i) {
+		if (outlen >= maxlen) {
+			break;
+		}
+		BYTE ch = input[i];
+		enum unicode_codepoint codepoint = UNICODE_UNDEFINED;
+		switch (ch) {
+			case '\a':
+			case '\b':
+			case '\e':
+			case '\f':
+			case '\n':
+			case '\r':
+			case '\t':
+				// Don't convert these control characters UNICODE
+				break;
+			default:
+				codepoint = cp437_unicode_tbl[ch];
+		}
+		if (codepoint != UNICODE_UNDEFINED) {
+			int ulen = utf8_putc((char*)outbuf + outlen, maxlen - outlen, codepoint);
+			if (ulen < 1)
+				break;
+			outlen += ulen;
+		} else {
+			*(outbuf + outlen) = ch;
+			outlen++;
+		}
+	}
+	len = outlen;
+	return outbuf;
+}
+
 bool native_executable(scfg_t* cfg, const char* cmdline, int mode)
 {
 	char*	p;
@@ -1079,7 +1117,7 @@ int sbbs_t::external(const char* cmdline, int mode, const char* startup_dir)
 	BYTE	buf[XTRN_IO_BUF_LEN];
     BYTE 	output_buf[XTRN_IO_BUF_LEN*2];
 	uint	avail;
-    ulong	output_len;
+    size_t	output_len;
 	bool	native=false;			// DOS program by default
 	bool	rio_abortable_save=rio_abortable;
 	int		i;
@@ -1092,6 +1130,7 @@ int sbbs_t::external(const char* cmdline, int mode, const char* startup_dir)
 	int		out_pipe[2];
 	int		err_pipe[2];
     BYTE 	wwiv_buf[XTRN_IO_BUF_LEN*2];
+	BYTE	utf8_buf[XTRN_IO_BUF_LEN*4];
     bool	wwiv_flag=false;
  	char* p;
 #ifdef PREFER_POLL
@@ -1826,6 +1865,7 @@ int sbbs_t::external(const char* cmdline, int mode, const char* startup_dir)
 				}
 			}
 
+			/* Output */
 			bp=buf;
 			i=0;
 			if(mode&EX_NOLOG)
@@ -1910,6 +1950,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(UTF8))
+					bp = cp437_to_utf8(bp, output_len, utf8_buf, sizeof utf8_buf);
 			}
 			/* Did expansion overrun the output buffer? */
 			if(output_len>sizeof(output_buf)) {
-- 
GitLab