diff --git a/exec/SlyEdit.js b/exec/SlyEdit.js
index 85ab58a4bd8739073751cd3bcb5f17ddb9562ea1..f92b4d2fb8aefa216f8f8a14671c2153aba5c6cb 100644
--- a/exec/SlyEdit.js
+++ b/exec/SlyEdit.js
@@ -54,13 +54,6 @@
  2 (argv[1]): Editor mode ("DCT", "ICE", or "RANDOM")
 */
 
-// Determine the location of this script (its startup directory).
-// The code for figuring this out is a trick that was created by Deuce,
-// suggested by Rob Swindell.  I've shortened the code a little.
-var gStartupPath = '.';
-try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; }
-gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
-
 // EDITOR_STYLE: Can be changed to mimic the look of DCT Edit or IceEdit.
 // The following are supported:
 //  "DCT": DCT Edit style
@@ -87,14 +80,14 @@ if (requireFnExists)
 	require("sbbsdefs.js", "K_NOCRLF");
 	require("dd_lightbar_menu.js", "DDLightbarMenu");
 	require("attr_conv.js", "syncAttrCodesToANSI");
-	require(gStartupPath + "SlyEdit_Misc.js", "gUserSettingsFilename");
+	require(js.exec_dir + "SlyEdit_Misc.js", "gUserSettingsFilename");
 }
 else
 {
 	load("sbbsdefs.js");
 	load("dd_lightbar_menu.js");
 	load("attr_conv.js");
-	load(gStartupPath + "SlyEdit_Misc.js");
+	load(js.exec_dir + "SlyEdit_Misc.js");
 }
 
 // Load program settings from SlyEdit.cfg, and load the user configuratio nsettings
@@ -314,9 +307,9 @@ var gSubjScreenLen = 0;
 if (EDITOR_STYLE == "DCT")
 {
 	if (requireFnExists)
-		require(gStartupPath + "SlyEdit_DCTStuff.js", "DrawQuoteWindowTopBorder_DCTStyle");
+		require(js.exec_dir + "SlyEdit_DCTStuff.js", "DrawQuoteWindowTopBorder_DCTStyle");
 	else
-		load(gStartupPath + "SlyEdit_DCTStuff.js");
+		load(js.exec_dir + "SlyEdit_DCTStuff.js");
 	gEditTop = 6;
 	gQuoteWinTextColor = gConfigSettings.DCTColors.QuoteWinText;
 	gQuoteLineHighlightColor = gConfigSettings.DCTColors.QuoteLineHighlightColor;
@@ -341,9 +334,9 @@ if (EDITOR_STYLE == "DCT")
 else if (EDITOR_STYLE == "ICE")
 {
 	if (requireFnExists)
-		require(gStartupPath + "SlyEdit_IceStuff.js", "DrawQuoteWindowTopBorder_IceStyle");
+		require(js.exec_dir + "SlyEdit_IceStuff.js", "DrawQuoteWindowTopBorder_IceStyle");
 	else
-		load(gStartupPath + "SlyEdit_IceStuff.js");
+		load(js.exec_dir + "SlyEdit_IceStuff.js");
 	gEditTop = 5;
 	gQuoteWinTextColor = gConfigSettings.iceColors.QuoteWinText;
 	gQuoteLineHighlightColor = gConfigSettings.iceColors.QuoteLineHighlightColor;
@@ -5708,7 +5701,7 @@ function doUserSettings(pCurpos, pReturnCursorToOriginalPos)
 
 	// Load the dictionary filenames - If there are more than 1, then we'll add
 	// an option for the user to choose a dictionary.
-	var dictionaryFilenames = getDictionaryFilenames(gStartupPath);
+	var dictionaryFilenames = getDictionaryFilenames(js.exec_dir);
 
 	// Create the user settings box
 	var optBoxTitle = "Setting                                      Enabled";
diff --git a/exec/SlyEdit_DCTStuff.js b/exec/SlyEdit_DCTStuff.js
index a6e2a552d48774b9b1f1ef78278200d2fe4823f1..b6194f3ef8875766c692e0aa0ac759b8bf7aea27 100644
--- a/exec/SlyEdit_DCTStuff.js
+++ b/exec/SlyEdit_DCTStuff.js
@@ -21,12 +21,12 @@
 if (typeof(require) === "function")
 {
 	require("sbbsdefs.js", "K_NOCRLF");
-	require(getScriptDir() + "SlyEdit_Misc.js", "CTRL_A");
+	require(js.exec_dir + "SlyEdit_Misc.js", "CTRL_A");
 }
 else
 {
 	load("sbbsdefs.js");
-	load(getScriptDir() + "SlyEdit_Misc.js");
+	load(js.exec_dir + "SlyEdit_Misc.js");
 }
 
 // DCTEdit menu item return values
@@ -1548,14 +1548,3 @@ function DCTMenu_RemoveAllItems()
    this.hotkeyRetvals = [];
    this.exitLoopKeys = [];
 }
-
-// Returns the the script's execution directory.
-// The code in this function is a trick that was created by Deuce, suggested
-// by Rob Swindell as a way to detect which directory the script was executed
-// in.  I've shortened the code a little.
-function getScriptDir()
-{
-   var startup_path = '.';
-   try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; }
-   return(backslash(startup_path.replace(/[\/\\][^\/\\]*$/,'')));
-}
\ No newline at end of file
diff --git a/exec/SlyEdit_IceStuff.js b/exec/SlyEdit_IceStuff.js
index ef489d6a9ccfd37c78d757eba927a7c3b75b5e91..fedea197a193124d548d939b8fc3c9357b44aa8d 100644
--- a/exec/SlyEdit_IceStuff.js
+++ b/exec/SlyEdit_IceStuff.js
@@ -21,12 +21,12 @@
 if (typeof(require) === "function")
 {
 	require("sbbsdefs.js", "K_NOCRLF");
-	require(getScriptDir() + "SlyEdit_Misc.js", "UPPER_LEFT_SINGLE");
+	require(js.exec_dir + "SlyEdit_Misc.js", "UPPER_LEFT_SINGLE");
 }
 else
 {
 	load("sbbsdefs.js");
-	load(getScriptDir() + "SlyEdit_Misc.js");
+	load(js.exec_dir + "SlyEdit_Misc.js");
 }
 
 // Read the color configuration file
@@ -958,14 +958,3 @@ function displayIceYesNoText(pYesSelected)
       }
    }
 }
-
-// Returns the the script's execution directory.
-// The code in this function is a trick that was created by Deuce, suggested
-// by Rob Swindell as a way to detect which directory the script was executed
-// in.  I've shortened the code a little.
-function getScriptDir()
-{
-   var startup_path = '.';
-   try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; }
-   return(backslash(startup_path.replace(/[\/\\][^\/\\]*$/,'')));
-}
\ No newline at end of file
diff --git a/exec/SlyEdit_Misc.js b/exec/SlyEdit_Misc.js
index cee8ab5031108cfdab4dec87a608b4e1b746b339..9f0ea0f1835af1eb1c72b43edfd1f3a9944f655b 100644
--- a/exec/SlyEdit_Misc.js
+++ b/exec/SlyEdit_Misc.js
@@ -152,7 +152,7 @@ var COPYRIGHT_YEAR = 2022;
 // Lister, since it will be used more than once.
 var gDDML_DROP_FILE_NAME = system.node_dir + "DDML_SyncSMBInfo.txt";
 
-var gUserSettingsFilename = backslash(system.data_dir + "user") + format("%04d", user.number) + ".SlyEdit_Settings";
+var gUserSettingsFilename = system.data_dir + "user/" + format("%04d", user.number) + ".SlyEdit_Settings";
 
 ///////////////////////////////////////////////////////////////////////////////////
 // Object/class stuff
@@ -2106,7 +2106,7 @@ function ReadSlyEditConfigFile()
 		enableTextReplacements: false,
 		textReplacementsUseRegex: false,
 		enableTaglines: false,
-		tagLineFilename: genFullPathCfgFilename("SlyEdit_Taglines.txt", gStartupPath),
+		tagLineFilename: genFullPathCfgFilename("SlyEdit_Taglines.txt", js.exec_dir),
 		taglinePrefix: "... ",
 		quoteTaglines: false,
 		shuffleTaglines: false,
@@ -2145,7 +2145,7 @@ function ReadSlyEditConfigFile()
 		iceColors: {
 			menuOptClassicColors: true,
 			// Ice color theme file
-			ThemeFilename: genFullPathCfgFilename("SlyIceColors_BlueIce.cfg", gStartupPath),
+			ThemeFilename: genFullPathCfgFilename("SlyIceColors_BlueIce.cfg", js.exec_dir),
 			// Quote line color
 			QuoteLineColor: "\x01n\x01c",
 			// Ice colors for the quote window
@@ -2175,7 +2175,7 @@ function ReadSlyEditConfigFile()
 		// Default DCT-style colors
 		DCTColors: {
 			// DCT color theme file
-			ThemeFilename: genFullPathCfgFilename("SlyDCTColors_Default.cfg", gStartupPath),
+			ThemeFilename: genFullPathCfgFilename("SlyDCTColors_Default.cfg", js.exec_dir),
 			// Quote line color
 			QuoteLineColor: "\x01n\x01c",
 			// DCT colors for the border stuff
@@ -2229,7 +2229,7 @@ function ReadSlyEditConfigFile()
 	};
 
 	// Open the SlyEdit configuration file
-	var slyEdCfgFileName = genFullPathCfgFilename("SlyEdit.cfg", gStartupPath);
+	var slyEdCfgFileName = genFullPathCfgFilename("SlyEdit.cfg", js.exec_dir);
 	var cfgFile = new File(slyEdCfgFileName);
 	if (cfgFile.open("r"))
 	{
@@ -2268,11 +2268,11 @@ function ReadSlyEditConfigFile()
 				cfgObj.enableTextReplacements = true;
 		}
 		if (behaviorSettings.hasOwnProperty("tagLineFilename") && typeof(behaviorSettings.tagLineFilename) === "string")
-			cfgObj.tagLineFilename = genFullPathCfgFilename(behaviorSettings.tagLineFilename, gStartupPath);
+			cfgObj.tagLineFilename = genFullPathCfgFilename(behaviorSettings.tagLineFilename, js.exec_dir);
 		if (behaviorSettings.hasOwnProperty("taglinePrefix") && typeof(behaviorSettings.taglinePrefix) === "string")
 			cfgObj.taglinePrefix = behaviorSettings.taglinePrefix;
 		if (behaviorSettings.hasOwnProperty("dictionaryFilenames") && typeof(behaviorSettings.dictionaryFilenames) === "string")
-			cfgObj.dictionaryFilenames = parseDictionaryConfig(behaviorSettings.dictionaryFilenames, gStartupPath);
+			cfgObj.dictionaryFilenames = parseDictionaryConfig(behaviorSettings.dictionaryFilenames, js.exec_dir);
 		// Color settings
 		var iceColorSettings = cfgFile.iniGetObject("ICE_COLORS");
 		var DCTColorSettings = cfgFile.iniGetObject("DCT_COLORS");
@@ -2281,11 +2281,11 @@ function ReadSlyEditConfigFile()
 		if (typeof(cfgObj.DCTColors) !== "object")
 			cfgObj.DCTColors = {};
 		if (iceColorSettings.hasOwnProperty("ThemeFilename") && typeof(iceColorSettings.ThemeFilename) === "string")
-			cfgObj.iceColors.ThemeFilename = genFullPathCfgFilename(iceColorSettings.ThemeFilename, gStartupPath);
+			cfgObj.iceColors.ThemeFilename = genFullPathCfgFilename(iceColorSettings.ThemeFilename, js.exec_dir);
 		if (iceColorSettings.hasOwnProperty("menuOptClassicColors") && typeof(iceColorSettings.menuOptClassicColors) === "boolean")
 			cfgObj.iceColors.menuOptClassicColors = iceColorSettings.menuOptClassicColors; // This is a boolean
 		if (DCTColorSettings.hasOwnProperty("ThemeFilename") && typeof(DCTColorSettings.ThemeFilename) === "string")
-			cfgObj.DCTColors.ThemeFilename = genFullPathCfgFilename(DCTColorSettings.ThemeFilename, gStartupPath);
+			cfgObj.DCTColors.ThemeFilename = genFullPathCfgFilename(DCTColorSettings.ThemeFilename, js.exec_dir);
 
 		cfgFile.close();
 
@@ -2297,7 +2297,7 @@ function ReadSlyEditConfigFile()
 		// set all available dictionary files in the configuration.
 		if (cfgObj.dictionaryFilenames.length == 0)
 		{
-			var dictFilenames = getDictionaryFilenames(gStartupPath);
+			var dictFilenames = getDictionaryFilenames(js.exec_dir);
 			for (var i = 0; i < dictFilenames.length; ++i)
 				cfgObj.dictionaryFilenames.push(dictFilenames[i]);
 		}
@@ -3465,7 +3465,7 @@ function readUserSigFile()
 
 	// The user signature files are located in sbbs/data/user, and the filename
 	// is the user number (zero-padded up to 4 digits) + .sig
-	var userSigFilename = backslash(system.data_dir + "user") + format("%04d.sig", user.number);
+	var userSigFilename = system.data_dir + "user/" + format("%04d.sig", user.number);
 	retObj.sigFileExists = file_exists(userSigFilename);
 	if (retObj.sigFileExists)
 	{
@@ -3545,7 +3545,7 @@ function populateTxtReplacements(pReplacementsObj, pRegex, pAllowColors)
 
 	// Note: Limited to words without spaces.
 	// Open the word replacements configuration file
-	var wordReplacementsFilename = genFullPathCfgFilename("SlyEdit_TextReplacements.cfg", gStartupPath);
+	var wordReplacementsFilename = genFullPathCfgFilename("SlyEdit_TextReplacements.cfg", js.exec_dir);
 	var arrayPopulated = false;
 	var wordFile = new File(wordReplacementsFilename);
 	if (wordFile.open("r"))
@@ -3717,7 +3717,9 @@ function genFullPathCfgFilename(pFilename, pDefaultPath)
 		if (typeof(pDefaultPath) == "string")
 		{
 			// Make sure the default path has a trailing path separator
-			var defaultPath = backslash(pDefaultPath);
+			var defaultPath = pDefaultPath;
+			if (defaultPath.length > 0 && defaultPath.charAt(defaultPath.length-1) != "/" && defaultPath.charAt(defaultPath.length-1) != "\\")
+				defaultPath += "/";
 			fullyPathedFilename = defaultPath + pFilename;
 		}
 		else
@@ -4134,7 +4136,7 @@ function ReadUserSettingsFile(pSlyEdCfgObj)
 					else if (settingUpper == "AUTOSIGNEMAILSREALNAME")
 						userSettingsObj.autoSignEmailsRealName = (valueUpper == "TRUE");
 					else if (settingUpper == "DICTIONARYFILENAMES")
-						userSettingsObj.dictionaryFilenames = parseDictionaryConfig(value, gStartupPath);
+						userSettingsObj.dictionaryFilenames = parseDictionaryConfig(value, js.exec_dir);
 				}
 			}
 		}
@@ -4369,15 +4371,19 @@ function getDictionaryFilenames(pDefaultPath)
 {
 	var filenameWildcard = "dictionary_*.txt";
 	var dictionaryFilenames = [];
-	var dirEntries = directory(backslash(system.mods_dir) + filenameWildcard);
+	var dirEntries = directory(system.mods_dir + filenameWildcard);
 	for (var i = 0; i < dirEntries.length; ++i)
 		dictionaryFilenames.push(dirEntries[i]);
-	dirEntries = directory(backslash(system.ctrl_dir) + filenameWildcard);
+	dirEntries = directory(system.ctrl_dir + filenameWildcard);
 	for (var i = 0; i < dirEntries.length; ++i)
 		dictionaryFilenames.push(dirEntries[i]);
 	if (typeof(pDefaultPath) == "string")
 	{
-		dirEntries = directory(backslash(pDefaultPath) + filenameWildcard);
+		// Make sure the default path has a trailing path separator
+		var defaultPath = pDefaultPath;
+		if (defaultPath.length > 0 && defaultPath.charAt(defaultPath.length-1) != "/" && defaultPath.charAt(defaultPath.length-1) != "\\")
+			defaultPath += "/";
+		dirEntries = directory(defaultPath + filenameWildcard);
 		for (var i = 0; i < dirEntries.length; ++i)
 			dictionaryFilenames.push(dirEntries[i]);
 	}
@@ -4399,7 +4405,7 @@ function readDictionaryFile(pFilename, pFullyPathed)
 	if (pFullyPathed)
 		dictFilename = pFilename;
 	else
-		dictFilename = genFullPathCfgFilename(pFilename, gStartupPath);
+		dictFilename = genFullPathCfgFilename(pFilename, js.exec_dir);
 
 	// Read the lines from the dictionary; skip Aspell copyright header lines
 	// if they exist, and lower-case all words for case-insensitive matching.
diff --git a/xtrn/DDAreaChoosers/DDFileAreaChooser.js b/xtrn/DDAreaChoosers/DDFileAreaChooser.js
index 5d01f717994e8f446970727eeb621884f09bd009..a4642b7880357fc2b9df49ae753b581db8b7cfba 100644
--- a/xtrn/DDAreaChoosers/DDFileAreaChooser.js
+++ b/xtrn/DDAreaChoosers/DDFileAreaChooser.js
@@ -141,14 +141,6 @@ var HORIZONTAL_SINGLE = "\xC4";
 var ERROR_WAIT_MS = 1500;
 var SEARCH_TIMEOUT_MS = 10000;
 
-// Determine the script's startup directory.
-// This code is a trick that was created by Deuce, suggested by Rob Swindell
-// as a way to detect which directory the script was executed in.  I've
-// shortened the code a little.
-var gStartupPath = '.';
-try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; }
-gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
-
 // 1st command-line argument: Whether or not to choose a file library first (if
 // false, then only choose a directory within the user's current library).  This
 // can be true or false.
@@ -1794,7 +1786,7 @@ function DDFileAreaChooser_writeKeyHelpLine()
 function DDFileAreaChooser_ReadConfigFile()
 {
 	// Open the configuration file
-	var cfgFile = new File(gStartupPath + "DDFileAreaChooser.cfg");
+	var cfgFile = new File(js.exec_dir + "DDFileAreaChooser.cfg");
 	if (cfgFile.open("r"))
 	{
 		var behaviorSettings = cfgFile.iniGetObject("BEHAVIOR");
@@ -2596,7 +2588,7 @@ function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
 	// width (areaChgHeader-<width>.ans/asc).  If not, then just go with
 	// msgHeader.ans/asc.
 	var txtFileExists = true;
-	var txtFilenameFullPath = gStartupPath + pFilenameBase;
+	var txtFilenameFullPath = js.exec_dir + pFilenameBase;
 	var txtFileFilename = "";
 	if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".ans"))
 		txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".ans";
@@ -2624,7 +2616,7 @@ function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
 					var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
 								+ syncConvertedHdrFilename + "\"";
 					// Note: Both system.exec(cmdLine) and
-					// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
+					// bbs.exec(cmdLine, EX_NATIVE, js.exec_dir) could be used to
 					// execute the command, but system.exec() seems noticeably faster.
 					system.exec(cmdLine);
 				}
@@ -2645,7 +2637,7 @@ function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
 				var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
 				            + syncConvertedHdrFilename + "\"";
 				// Note: Both system.exec(cmdLine) and
-				// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
+				// bbs.exec(cmdLine, EX_NATIVE, js.exec_dir) could be used to
 				// execute the command, but system.exec() seems noticeably faster.
 				system.exec(cmdLine);
 			}
diff --git a/xtrn/DDAreaChoosers/DDMsgAreaChooser.js b/xtrn/DDAreaChoosers/DDMsgAreaChooser.js
index 9de8356494af6b8f276963feb5b136690ab93883..029d95e837a023b9bc23f69c920d427de29c7f85 100644
--- a/xtrn/DDAreaChoosers/DDMsgAreaChooser.js
+++ b/xtrn/DDAreaChoosers/DDMsgAreaChooser.js
@@ -152,14 +152,6 @@ var HORIZONTAL_SINGLE = "\xC4";
 var ERROR_WAIT_MS = 1500;
 var SEARCH_TIMEOUT_MS = 10000;
 
-// Determine the script's startup directory.
-// This code is a trick that was created by Deuce, suggested by Rob Swindell
-// as a way to detect which directory the script was executed in.  I've
-// shortened the code a little.
-var gStartupPath = '.';
-try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; }
-gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
-
 // gIsSysop stores whether or not the user is a sysop.
 var gIsSysop = user.compare_ars("SYSOP"); // Whether or not the user is a sysop
 
@@ -2194,16 +2186,8 @@ function DDMsgAreaChooser_GetMsgSubBrdLine(pGrpIndex, pSubIndex, pHighlight)
 // For the DDMsgAreaChooser class: Reads the configuration file.
 function DDMsgAreaChooser_ReadConfigFile()
 {
-	// Determine the script's startup directory.
-	// This code is a trick that was created by Deuce, suggested by Rob Swindell
-	// as a way to detect which directory the script was executed in.  I've
-	// shortened the code a little.
-	var startup_path = '.';
-	try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; }
-	startup_path = backslash(startup_path.replace(/[\/\\][^\/\\]*$/,''));
-
 	// Open the configuration file
-	var cfgFile = new File(startup_path + "DDMsgAreaChooser.cfg");
+	var cfgFile = new File(js.exec_dir + "DDMsgAreaChooser.cfg");
 	if (cfgFile.open("r"))
 	{
 		var behaviorSettings = cfgFile.iniGetObject("BEHAVIOR");
@@ -2828,7 +2812,7 @@ function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
 	// width (areaChgHeader-<width>.ans/asc).  If not, then just go with
 	// msgHeader.ans/asc.
 	var txtFileExists = true;
-	var txtFilenameFullPath = gStartupPath + pFilenameBase;
+	var txtFilenameFullPath = js.exec_dir + pFilenameBase;
 	var txtFileFilename = "";
 	if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".ans"))
 		txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".ans";
@@ -2856,7 +2840,7 @@ function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
 					var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
 								+ syncConvertedHdrFilename + "\"";
 					// Note: Both system.exec(cmdLine) and
-					// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
+					// bbs.exec(cmdLine, EX_NATIVE, js.exec_dir) could be used to
 					// execute the command, but system.exec() seems noticeably faster.
 					system.exec(cmdLine);
 				}
@@ -2877,7 +2861,7 @@ function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
 				var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
 				            + syncConvertedHdrFilename + "\"";
 				// Note: Both system.exec(cmdLine) and
-				// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
+				// bbs.exec(cmdLine, EX_NATIVE, js.exec_dir) could be used to
 				// execute the command, but system.exec() seems noticeably faster.
 				system.exec(cmdLine);
 			}
diff --git a/xtrn/ddfilelister/ddfilelister.js b/xtrn/ddfilelister/ddfilelister.js
index d6135a4777b5fab6631d86ad0d3cd65531eb2478..354c8be6663900b17c66f7f3ced75b77fbd1ab68 100644
--- a/xtrn/ddfilelister/ddfilelister.js
+++ b/xtrn/ddfilelister/ddfilelister.js
@@ -1386,7 +1386,7 @@ function addSelectedFilesToBatchDLQueue(pFileMetadata, pFileList)
 	{
 		var batchDLQueueStats = getUserDLQueueStats();
 		var filenamesFailed = []; // To store filenames that failed to get added to the queue
-		var batchDLFilename = backslash(system.data_dir + "user") + format("%04d", user.number) + ".dnload";
+		var batchDLFilename = system.data_dir + "user/" + format("%04d", user.number) + ".dnload";
 		var batchDLFile = new File(batchDLFilename);
 		if (batchDLFile.open(batchDLFile.exists ? "r+" : "w+"))
 		{
@@ -1613,7 +1613,7 @@ function getUserDLQueueStats()
 		filenames: []
 	};
 
-	var batchDLFilename = backslash(system.data_dir + "user") + format("%04d", user.number) + ".dnload";
+	var batchDLFilename = system.data_dir + "user/" + format("%04d", user.number) + ".dnload";
 	var batchDLFile = new File(batchDLFilename);
 	if (batchDLFile.open(batchDLFile.exists ? "r+" : "w+"))
 	{
@@ -1673,7 +1673,7 @@ function letUserDownloadSelectedFile(pFileMetadata)
 	{
 		console.print("\x01cDownloading \x01h" + pFileMetadata.name + "\x01n");
 		console.crlf();
-		var selectedFilanmeFullPath = backslash(file_area.dir[pFileMetadata.dirCode].path) + pFileMetadata.name;
+		var selectedFilanmeFullPath = file_area.dir[pFileMetadata.dirCode].path + pFileMetadata.name;
 		bbs.send_file(selectedFilanmeFullPath);
 	}
 
@@ -3736,14 +3736,6 @@ function readConfigFile()
 {
 	var themeFilename = ""; // In case a theme filename is specified
 
-	// Determine the script's startup directory.
-	// This code is a trick that was created by Deuce, suggested by Rob Swindell
-	// as a way to detect which directory the script was executed in.  I've
-	// shortened the code a little.
-	var startupPath = '.';
-	try { throw dig.dist(dist); } catch(e) { startupPath = e.fileName; }
-	startupPath = backslash(startupPath.replace(/[\/\\][^\/\\]*$/,''));
-
 	// Open the main configuration file.  First look for it in the sbbs/mods
 	// directory, then sbbs/ctrl, then in the same directory as this script.
 	var cfgFilename = "ddfilelister.cfg";
@@ -3751,7 +3743,7 @@ function readConfigFile()
 	if (!file_exists(cfgFilenameFullPath))
 		cfgFilenameFullPath = file_cfgname(system.ctrl_dir, cfgFilename);
 	if (!file_exists(cfgFilenameFullPath))
-		cfgFilenameFullPath = file_cfgname(startupPath, cfgFilename);
+		cfgFilenameFullPath = file_cfgname(js.exec_dir, cfgFilename);
 	var cfgFile = new File(cfgFilenameFullPath);
 	if (cfgFile.open("r"))
 	{
@@ -3819,7 +3811,7 @@ function readConfigFile()
 					if (!file_exists(themeFilename))
 						themeFilename = system.ctrl_dir + settingsObj[prop];
 					if (!file_exists(themeFilename))
-						themeFilename = startupPath + settingsObj[prop];
+						themeFilename = js.exec_dir + settingsObj[prop];
 				}
 			}
 		}
diff --git a/xtrn/gttrivia/gttrivia.js b/xtrn/gttrivia/gttrivia.js
index 7d855fcb73d7dc82841bc28d8abf5958318af421..8729f4d686ae1cc03fbafe0827094c24538b5005 100644
--- a/xtrn/gttrivia/gttrivia.js
+++ b/xtrn/gttrivia/gttrivia.js
@@ -59,29 +59,19 @@ if (system.version_num < 31500)
 var GAME_VERSION = "1.03";
 var GAME_VER_DATE = "2023-01-14";
 
-// Determine the location of this script (its startup directory).
-// The code for figuring this out is a trick that was created by Deuce,
-// suggested by Rob Swindell.  I've shortened the code a little.
-var gStartupPath = '.';
-var gThisScriptFilename = "";
-try { throw dig.dist(dist); } catch(e) {
-	gStartupPath = backslash(e.fileName.replace(/[\/\\][^\/\\]*$/,''));
-	gThisScriptFilename = file_getname(e.fileName);
-}
-
 // Load required .js libraries
 var requireFnExists = (typeof(require) === "function");
 if (requireFnExists)
 {
 	require("sbbsdefs.js", "P_NONE");
 	require("json-client.js", "JSONClient");
-	require(gStartupPath + "lib.js", "getJSONSvcPortFromServicesIni");
+	require(js.exec_dir + "lib.js", "getJSONSvcPortFromServicesIni");
 }
 else
 {
 	load("sbbsdefs.js");
 	load("json-client.js");
-	load(gStartupPath + "lib.js");
+	load(js.exec_dir + "lib.js");
 }
 
 
@@ -134,10 +124,10 @@ var LOWER_CENTER_BLOCK = "\xDC";
 // Maximum Levenshtein distance (inclusive) to consisder an answer matching (when appropriate)
 var MAX_LEVENSHTEIN_DISTANCE = 2;
 // Scores filename
-var SCORES_FILENAME = gStartupPath + "scores.json";
+var SCORES_FILENAME = js.exec_dir + "scores.json";
 // Semaphore filename to use when saving the user's score to try to prevent multiple instances
 // from overwriting the score on each other
-var SCORES_SEMAPHORE_FILENAME = gStartupPath + "SCORES_SEMAPHORE.tmp";
+var SCORES_SEMAPHORE_FILENAME = js.exec_dir + "SCORES_SEMAPHORE.tmp";
 // Main menu actions
 var ACTION_PLAY = 0;
 var ACTION_SHOW_HELP_SCREEN = 1;
@@ -158,7 +148,7 @@ var JSON_DB_LOCK_UNLOCK = -1;
 
 
 // Load the settings from the .ini file
-var gSettings = loadSettings(gStartupPath);
+var gSettings = loadSettings();
 
 // Parse command-line arguments
 var gCmdLineArgs = parseCmdLineArgs(argv);
@@ -518,11 +508,8 @@ function playTrivia()
 
 // Loads settings from the .ini file
 //
-// Parameters:
-//  gStartupPath: The path to the directory where the .ini file is located
-//
 // Return value: An object with 'behavior' and 'color' sections with the settings loaded from the .ini file
-function loadSettings(pStartupPath)
+function loadSettings()
 {
 	var settings = {
 		colors: {
@@ -548,7 +535,7 @@ function loadSettings(pStartupPath)
 			answerFact: "G"
 		}
 	};
-	var cfgFileName = genFullPathCfgFilename("gttrivia.ini", pStartupPath);
+	var cfgFileName = genFullPathCfgFilename("gttrivia.ini", js.exec_dir);
 	var iniFile = new File(cfgFileName);
 	if (iniFile.open("r"))
 	{
@@ -647,7 +634,9 @@ function genFullPathCfgFilename(pFilename, pDefaultPath)
 		if (typeof(pDefaultPath) == "string")
 		{
 			// Make sure the default path has a trailing path separator
-			var defaultPath = backslash(pDefaultPath);
+			var defaultPath = pDefaultPath;
+			if (defaultPath.length > 0 && defaultPath.charAt(defaultPath.length-1) != "/" && defaultPath.charAt(defaultPath.length-1) != "\\")
+				defaultPath += "/";
 			fullyPathedFilename = defaultPath + pFilename;
 		}
 		else
@@ -699,7 +688,7 @@ function displayProgramLogo(pClearScreenFirst, pPauseAfter)
 	if (typeof(pClearScreenFirst) === "boolean" && pClearScreenFirst)
 		console.clear("\x01n");
 
-	console.printfile(gStartupPath + "gttrivia.asc", P_NONE, 80);
+	console.printfile(js.exec_dir + "gttrivia.asc", P_NONE, 80);
 
 	if (typeof(pPauseAfter) === "boolean" && pPauseAfter)
 		console.pause();
@@ -761,7 +750,7 @@ function doMainMenu()
 function getQACategoriesAndFilenames()
 {
 	var sectionsAndFilenames = [];
-	var QAFilenames = directory(gStartupPath + "qa/*.qa");
+	var QAFilenames = directory(js.exec_dir + "qa/*.qa");
 	for (var i = 0; i < QAFilenames.length; ++i)
 	{
 		// Get the section name - Start by removing the .qa filename extension
@@ -853,6 +842,8 @@ function getQACategoriesAndFilenames()
 	return sectionsAndFilenames;
 }
 
+// TODO: Put the next 3 functions into a common JS library for use with this and
+// qa_edit.js?
 // Parses a Q&A file with questions and answers
 //
 // Parameters:
@@ -1840,7 +1831,7 @@ function doSysopMenu()
 		console.print("\x01c1\x01y\x01h) \x01bClear high scores\x01n");
 		console.print("     \x01cQ\x01y\x01h)\x01buit\x01n");
 		// If there is an inter-BBS scores JSON file, then add some options to manage that
-		if (file_exists(backslash(gStartupPath + "server") + "gttrivia.json"))
+		if (file_exists(js.exec_dir + "server/" + "gttrivia.json"))
 		{
 			validKeys += "23";
 			console.crlf();
diff --git a/xtrn/slyvote/slyvote.js b/xtrn/slyvote/slyvote.js
index 90672212ab6bbf2f972cf4cf60348124b6318ddd..5ab614e137dc045641a3e90cdff7e42867d0923a 100644
--- a/xtrn/slyvote/slyvote.js
+++ b/xtrn/slyvote/slyvote.js
@@ -427,7 +427,7 @@ if (!subBoardsConfigured)
 
 // Read the user settings file
 // The name of the user's settings file for SlyVote
-var gUserSettingsFilename = backslash(system.data_dir + "user") + format("%04d", user.number) + ".slyvote.cfg";
+var gUserSettingsFilename = system.data_dir + "user/" + format("%04d", user.number) + ".slyvote.cfg";
 var gUserSettings = ReadUserSettingsFile(gUserSettingsFilename, gSlyVoteCfg);
 
 // Determine which sub-board to use - Prioritize the last sub-board in the user