From 4f66de65615cdd47b860b4e2606bac5efa3f261d Mon Sep 17 00:00:00 2001
From: "Rob Swindell (on Windows 11)" <rob@synchro.net>
Date: Mon, 7 Apr 2025 20:45:02 -0700
Subject: [PATCH] Modified text and menu files can now be placed in mods/text/*

(e.g. mods/text/menu/main.msg), but only when the mods dir is configured in
SCFG->System->Advanced Options (by default, this set to ../mods/).

While menu-related functions will always check/read the file from the
mods/text dir (if exists), printfile() function requires the new P_MODS flag
to be specified to check/read the file from the mods/text directory.

This resolves feature request/issue #879
---
 src/sbbs3/atcodes.cpp  |  4 ++--
 src/sbbs3/main.cpp     |  4 ++--
 src/sbbs3/prntfile.cpp | 33 +++++++++++++++++++++++++++++++--
 src/sbbs3/putmsg.cpp   |  2 +-
 src/sbbs3/sbbsdefs.h   |  1 +
 src/sbbs3/str.cpp      |  6 ++++++
 6 files changed, 43 insertions(+), 7 deletions(-)

diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index b155a5faef..7a8f296731 100644
--- a/src/sbbs3/atcodes.cpp
+++ b/src/sbbs3/atcodes.cpp
@@ -1610,12 +1610,12 @@ const char* sbbs_t::atcode(const char* sp, char* str, size_t maxlen, int* pmode,
 	}
 
 	if (!strncmp(sp, "TYPE:", 5)) {
-		printfile(cmdstr(sp + 5, nulstr, nulstr, str), 0);
+		printfile(cmdstr(sp + 5, nulstr, nulstr, str), P_MODS);
 		return nulstr;
 	}
 
 	if (!strncmp(sp, "INCLUDE:", 8)) {
-		printfile(cmdstr(sp + 8, nulstr, nulstr, str), P_NOCRLF | P_SAVEATR);
+		printfile(cmdstr(sp + 8, nulstr, nulstr, str), P_NOCRLF | P_SAVEATR | P_MODS);
 		return nulstr;
 	}
 
diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index 6eede662b1..259c458594 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -5770,7 +5770,7 @@ NO_SSH:
 				lprintf(LOG_WARNING, "%04d %s [%s] !No nodes available for login.", client_socket, client.protocol, host_ip);
 				SAFEPRINTF(str, "%snonodes.txt", scfg.text_dir);
 				if (fexist(str))
-					sbbs->printfile(str, P_NOABORT);
+					sbbs->printfile(str, P_NOABORT | P_MODS);
 				else {
 					sbbs->cp437_out("\r\nSorry, all terminal nodes are in use or otherwise unavailable.\r\n");
 					sbbs->cp437_out("Please try again later.\r\n");
@@ -5846,7 +5846,7 @@ NO_SSH:
 				        , client_socket, client.protocol, new_node->cfg.node_num);
 				SAFEPRINTF(str, "%snonodes.txt", scfg.text_dir);
 				if (fexist(str))
-					sbbs->printfile(str, P_NOABORT);
+					sbbs->printfile(str, P_NOABORT | P_MODS);
 				else
 					sbbs->cp437_out("\r\nSorry, initialization failed. Try again later.\r\n");
 				sbbs->flush_output(3000);
diff --git a/src/sbbs3/prntfile.cpp b/src/sbbs3/prntfile.cpp
index 10073394b5..31485a3242 100644
--- a/src/sbbs3/prntfile.cpp
+++ b/src/sbbs3/prntfile.cpp
@@ -35,7 +35,7 @@
 /* for pauses, aborts and ANSI. 'str' is the path of the file to print      */
 /* Called from functions menu and text_sec                                  */
 /****************************************************************************/
-bool sbbs_t::printfile(const char* fname, int mode, int org_cols, JSObject* obj)
+bool sbbs_t::printfile(const char* inpath, int mode, int org_cols, JSObject* obj)
 {
 	char* buf;
 	char  fpath[MAX_PATH + 1];
@@ -45,7 +45,16 @@ bool sbbs_t::printfile(const char* fname, int mode, int org_cols, JSObject* obj)
 	int   l, length, savcon = console;
 	FILE *stream;
 
-	SAFECOPY(fpath, fname);
+	if (FULLPATH(fpath, inpath, sizeof fpath) == NULL)
+		SAFECOPY(fpath, inpath);
+	if ((mode & P_MODS) && cfg.mods_dir[0] != '\0') {
+		if (strncmp(fpath, cfg.text_dir, strlen(cfg.text_dir)) == 0) {
+			char modpath[MAX_PATH + 1];
+			snprintf(modpath, sizeof modpath, "%stext/%s", cfg.mods_dir, fpath + strlen(cfg.text_dir));
+			if(fexistcase(modpath))
+				SAFECOPY(fpath, modpath);
+		}
+	}
 	(void)fexistcase(fpath);
 	p = getfext(fpath);
 	if (p != NULL) {
@@ -308,6 +317,10 @@ bool sbbs_t::menu(const char *code, int mode, JSObject* obj)
 	return printfile(path, mode, /* org_cols: */ 0, obj);
 }
 
+//****************************************************************************
+// Check (return true) if a menu file exists with specified type/extension
+// 'path' buffer must be at least (MAX_PATH + 1) bytes in size
+//****************************************************************************
 bool sbbs_t::menu_exists(const char *code, const char* ext, char* path)
 {
 	char pathbuf[MAX_PATH + 1];
@@ -337,6 +350,16 @@ bool sbbs_t::menu_exists(const char *code, const char* ext, char* path)
 		SAFEPRINTF3(prefix, "%smenu/%s%s", cfg.text_dir, subdir, code);
 		FULLPATH(path, prefix, MAX_PATH);
 		SAFECOPY(prefix, path);
+		if (cfg.mods_dir[0] != '\0') {
+			char modprefix[MAX_PATH + 1];
+			char modpath[MAX_PATH + 1];
+			snprintf(modprefix, sizeof modprefix, "%stext/menu/%s%s", cfg.mods_dir, subdir, code);
+			snprintf(modpath, sizeof modpath, "%s.%s", modprefix, ext);
+			if (fexist(modpath)) {
+				FULLPATH(path, modprefix, MAX_PATH);
+				SAFECOPY(prefix, path);
+			}
+		}
 	}
 	// Display specified EXACT width file
 	safe_snprintf(path, MAX_PATH, "%s.%ucol.%s", prefix, term->cols, ext);
@@ -379,6 +402,12 @@ bool sbbs_t::random_menu(const char *name, int mode, JSObject* obj)
 	str_list_t names = NULL;
 
 	SAFEPRINTF2(path, "%smenu/%s", cfg.text_dir, name);
+	if (cfg.mods_dir[0] != '\0') {
+		char modpath[MAX_PATH + 1];
+		SAFEPRINTF2(modpath, "%stext/menu/%s", cfg.mods_dir, name);
+		if (fexist(modpath))
+			SAFECOPY(path, modpath);
+	}
 	if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &g) != 0) {
 		return false;
 	}
diff --git a/src/sbbs3/putmsg.cpp b/src/sbbs3/putmsg.cpp
index 05003f0883..756219f47a 100644
--- a/src/sbbs3/putmsg.cpp
+++ b/src/sbbs3/putmsg.cpp
@@ -212,7 +212,7 @@ char sbbs_t::putmsgfrag(const char* buf, int& mode, unsigned org_cols, JSObject*
 					tmp2[i] = 0;
 					sys_status |= SS_NEST_PF;     /* keep it only one message deep! */
 					SAFEPRINTF2(path, "%s%s", cfg.text_dir, tmp2);
-					printfile(path, 0);
+					printfile(path, P_MODS);
 					sys_status &= ~SS_NEST_PF;
 				}
 			}
diff --git a/src/sbbs3/sbbsdefs.h b/src/sbbs3/sbbsdefs.h
index 593387d156..ac6f272d7f 100644
--- a/src/sbbs3/sbbsdefs.h
+++ b/src/sbbs3/sbbsdefs.h
@@ -699,6 +699,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_MODS      (1 << 21)     // Display from mods/text dir, if file is there
 
 #define P_XATTR_SHIFT 20
 #define P_WILDCAT   (SM_WILDCAT  << P_XATTR_SHIFT)
diff --git a/src/sbbs3/str.cpp b/src/sbbs3/str.cpp
index b5bd65dc35..a77ac323f1 100644
--- a/src/sbbs3/str.cpp
+++ b/src/sbbs3/str.cpp
@@ -990,6 +990,12 @@ bool sbbs_t::trashcan(const char *insearchof, const char *name, struct trash* tr
 	result = ::trashcan2(&cfg, insearchof, NULL, name, trash);
 	if (result) {
 		snprintf(str, sizeof str, "%sbad%s.msg", cfg.text_dir, name);
+		if (cfg.mods_dir[0] != '\0') {
+			char modpath[MAX_PATH + 1];
+			snprintf(modpath, sizeof modpath, "%stext/bad%s.msg", cfg.mods_dir, name);
+			if (fexistcase(modpath))
+				SAFECOPY(str, modpath);
+	}
 		if (fexistcase(str)) {
 			printfile(str, 0);
 			flush_output(500); // give time for tx buffer to clear before disconnect
-- 
GitLab