diff --git a/xtrn/dd_arc_viewer/dd_arc_viewer.cfg b/xtrn/dd_arc_viewer/dd_arc_viewer.cfg
index d3b82c6e12fd3262ffb75d5a7fb0a6a730c91695..b68ea19545a3e3963e3e1467eca8753961505a46 100644
--- a/xtrn/dd_arc_viewer/dd_arc_viewer.cfg
+++ b/xtrn/dd_arc_viewer/dd_arc_viewer.cfg
@@ -15,21 +15,21 @@ maxTextFileSize=5M
 
 [COLORS]
 ; The "Archive:" text
-archiveFilenameHdrText=nwh
+archiveFilenameHdrText=nwh
 ; Archive filename
-archiveFilename=ngh
+archiveFilename=ngh
 ; The column header line above the file list
-headerLine=nyh
+headerLine=nyh
 ; The line between the header line and the file list
-headerSeparatorLine=nkh
+headerSeparatorLine=nkh
 ; File numbers
-fileNums=nmh
+fileNums=nmh
 ; File information columns
-fileSize=nw
-fileDate=ng
-fileTime=nr
-filename=nc
+fileSize=nw
+fileDate=ng
+fileTime=nr
+filename=nc
 ; For subdirectories
-subdir=ngh
+subdir=ngh
 ; For lightbar mode, highlighted filenames will have this color:
-highlightedFile=n4wh4
\ No newline at end of file
+highlightedFile=nwh4
\ No newline at end of file
diff --git a/xtrn/dd_arc_viewer/dd_arc_viewer.js b/xtrn/dd_arc_viewer/dd_arc_viewer.js
index 4f0366b2e49fc882fd136a605bbb5bb86bbdd8f3..55fe66175ab75919548483762c28b02c0917f941 100644
--- a/xtrn/dd_arc_viewer/dd_arc_viewer.js
+++ b/xtrn/dd_arc_viewer/dd_arc_viewer.js
@@ -83,6 +83,9 @@
  * 2022-05-17 Eric Oulashin     When extracting an archive with Synchronet's
  *                              internal archiver, extracts with path information
  *                              to trust that filename characters are safe.
+ * 2023-08-08 Eric Oulashin     Version 1.05
+ *                              Refactored how the configuration files are loaded.
+ *                              Colors in dd_arc_viewer.cfg no longer need the control character.
  */
 
 "use strict";
@@ -115,8 +118,8 @@ load(gStartupPath + "dd_arc_viewer_cleanup.js");
 
 
 // Version information
-var gDDArcViewerVersion = "1.04";
-var gDDArcViewerVerDate = "2022-05-14";
+var gDDArcViewerVersion = "1.05";
+var gDDArcViewerVerDate = "2023-08-08";
 var gDDArcViewerProgName = "Digital Distortion Archive Viewer";
 
 
@@ -232,7 +235,6 @@ if (!configFileRead)
 }
 
 
-// Now, the fun begins..
 
 // gRootWorkDir will containing the name of the root work directory.  This
 // is where we'll extract the archive.
@@ -328,85 +330,37 @@ function ReadConfig(pCfgFilePath)
 	{
 		if (fileTypeCfgFile.length > 0)
 		{
+			var allFileTypeCfg = fileTypeCfgFile.iniGetAllObjects();
 			fileTypeSettingsRead = true;
-			// Read each line from the config file and set the
-			// various options.
-			var pos = 0;               // Index of = in the file lines
-			var fileLine = "";
-			var filenameExt = "";      // Archive filename extension
-			var option = "";           // Configuration option
-			var optionValue = "";      // Configuration option value
-			var optionValueUpper;      // Upper-cased configuration option value
-			var viewableFile = null;   // Will be used to create & store viewable archive options
-			while (!fileTypeCfgFile.eof)
+			for (var i = 0; i < allFileTypeCfg.length; ++i)
 			{
-				// Read the line from the config file, look for a =, and
-				// if found, read the option & value and set them
-				// in cfgObj.
-				fileLine = fileTypeCfgFile.readln(1024);
-
-				// fileLine should be a string, but I've seen some cases
-				// where it isn't, so check its type.
-				if (typeof(fileLine) != "string")
-					continue;
-
-				// If the line is blank or starts with with a semicolon
-				// (the comment character), then skip it.
-				if ((fileLine.length == 0) || (fileLine.substr(0, 1) == ";"))
-					continue;
-
-				// Look for a file extension in square brackets ([ and ]).
-				// If found, then set filenameExt and continue onto the next line.
-				// Note: This regular expression allows whitespace around the [...].
-				if (/^\s*\[.*\]\s*$/.test(fileLine))
+				var filenameExt = allFileTypeCfg[i].name; // Filename extension
+				var viewableFile = new ViewableFile();
+				viewableFile.extension = filenameExt;
+				for (var prop in allFileTypeCfg[i])
 				{
-					var startIndex = fileLine.indexOf("[") + 1;
-					var endIndex = fileLine.lastIndexOf("]");
-					var ext = fileLine.substr(startIndex, endIndex-startIndex).toUpperCase();
-					// If the filename extension is different than the last one
-					// we've seen, then:
-					// 1. If viewableFile is not null, then add it to gViewableFileTypes.
-					// 2. Create a new one (referenced as viewableFile).
-					if (ext != filenameExt)
+					var propUpper = prop.toUpperCase();
+					if (propUpper == "VIEW")
+						viewableFile.viewCmd = allFileTypeCfg[i][prop];
+					else if (propUpper == "EXTRACT")
+						viewableFile.extractCmd = allFileTypeCfg[i][prop];
+					else if (propUpper == "ISTEXT")
 					{
-						if ((viewableFile != null) && (viewableFile != undefined) && (filenameExt.length > 0))
+						if (typeof(allFileTypeCfg[i][prop]) === "string")
 						{
-							gViewableFileTypes[filenameExt] = viewableFile;
+							var valueUpper = allFileTypeCfg[i][prop].toUpperCase();
+							viewableFile.isText = (valueUpper == "YES" || valueUpper == "TRUE");
 						}
-						filenameExt = ext;
-						viewableFile = new ViewableFile();
-						viewableFile.extension = ext;
+						else if (typeof(allFileTypeCfg[i][prop]) === "boolean")
+							viewableFile.isText = allFileTypeCfg[i][prop];
 					}
-					continue;
-				}
-
-				// If filenameExt is blank, then continue onto the next line.
-				if (filenameExt.length == 0)
-					continue;
-
-				// If we're here, then filenameExt is set, and this is a valid
-				// line to process.
-				// Look for an = in the line, and if found, split into
-				// option & value.
-				pos = fileLine.indexOf("=");
-				if (pos > -1)
-				{
-					// Extract the option & value, trimming leading & trailing spaces.
-					option = trimSpaces(fileLine.substr(0, pos), true, false, true).toUpperCase();
-					optionValue = trimSpaces(fileLine.substr(pos+1), true, false, true);
-
-					if (option == "VIEW")
-						viewableFile.viewCmd = optionValue;
-					else if (option == "EXTRACT")
-						viewableFile.extractCmd = optionValue;
-					else if (option == "ISTEXT")
-						viewableFile.isText = (optionValue.toUpperCase() == "YES");
 				}
+				gViewableFileTypes[filenameExt] = viewableFile;
 			}
 		}
-
 		fileTypeCfgFile.close();
 	}
+
 	// Read the extractable and viewable file configuration from the Synchronet
 	// configuration and add any that we haven't seen in dd_arc_viewer_file_types.cfg
 	var SCFGFileCmds = getFileExtractAndViewCmdsFromSCFG();
@@ -436,93 +390,55 @@ function ReadConfig(pCfgFilePath)
 	{
 		if (genCfgFile.length > 0)
 		{
+			var behaviorSettings = genCfgFile.iniGetObject("BEHAVIOR");
+			var colorSettings = genCfgFile.iniGetObject("COLORS");
 			genSettingsRead = true;
-			var settingsMode = "";
-			var fileLine = null;     // A line read from the file
-			var equalsPos = 0;       // Position of a = in the line
-			var commentPos = 0;      // Position of the start of a comment
-			var setting = null;      // A setting name (string)
-			var settingUpper = null; // Upper-case setting name
-			var value = null;        // A value for a setting (string)
-			while (!genCfgFile.eof)
-			{
-				// Read the next line from the config file.
-				fileLine = genCfgFile.readln(1024);
-
-				// fileLine should be a string, but I've seen some cases
-				// where it isn't, so check its type.
-				if (typeof(fileLine) != "string")
-					continue;
-
-				// If the line starts with with a semicolon (the comment
-				// character) or is blank, then skip it.
-				if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
-					continue;
 
-				// If in the "behavior" section, then set the behavior-related variables.
-				if (fileLine.toUpperCase() == "[BEHAVIOR]")
+			// General/behavior settings
+			for (var prop in behaviorSettings)
+			{
+				var propUpper = prop.toUpperCase();
+				if (propUpper == "INTERFACESTYLE")
 				{
-					settingsMode = "behavior";
-					continue;
+					var valueUpper = behaviorSettings[prop].toUpperCase();
+					// Ensure that the first character is uppercase and the
+					// rest is lower-case.
+					if ((valueUpper == "LIGHTBAR") || (valueUpper == "TRADITIONAL"))
+					{
+						gGenConfig.interfaceStyle = behaviorSettings[prop].substr(0, 1).toUpperCase()
+						                          + behaviorSettings[prop].substr(1).toLowerCase();
+					}
 				}
-				else if (fileLine.toUpperCase() == "[COLORS]")
+				else if (propUpper == "INPUTTIMEOUTMS")
 				{
-					settingsMode = "colors";
-					continue;
+					var timeoutMSInt = parseInt(behaviorSettings[prop]);
+					if (!isNaN(timeoutMSInt) && timeoutMSInt > 0)
+						gGenConfig.inputTimeoutMS = timeoutMSInt;
 				}
+				else if (propUpper == "MAXARCFILESIZE")
+					gGenConfig.maxArcFileSize = sizeStrToBytes(behaviorSettings[prop]);
+				else if (propUpper == "MAXTEXTFILESIZE")
+					gGenConfig.maxTextFileSize = sizeStrToBytes(behaviorSettings[prop]);
+			}
 
-				// If settingsMode is blank, then skip this line.
-				if (settingsMode.length == 0)
-					continue;
-
-				// If the line has a semicolon anywhere in it, then remove
-				// everything from the semicolon onward.
-				commentPos = fileLine.indexOf(";");
-				if (commentPos > -1)
-					fileLine = fileLine.substr(0, commentPos);
-
-				// Look for an equals sign, and if found, separate the line
-				// into the setting name (before the =) and the value (after the
-				// equals sign).
-				equalsPos = fileLine.indexOf("=");
-				if (equalsPos > 0)
+			// Color settings
+			var onlySyncAttrsRegexWholeWord = new RegExp("^[\x01krgybmcw01234567hinq,;\.dtlasz]+$", 'i');
+			for (var prop in gGenConfig.colors)
+			{
+				if (colorSettings.hasOwnProperty(prop))
 				{
-					// Read the setting & value, and trim leading & trailing spaces.
-					setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
-					settingUpper = setting.toUpperCase();
-					value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true).toUpperCase();
-
-					if (settingsMode == "behavior")
-					{
-						// Skip this one if the value is blank.
-						if (value.length == 0)
-							continue;
-
-						// Set the appropriate value in the settings object.
-						if (settingUpper == "INTERFACESTYLE")
-						{
-							// Ensure that the first character is uppercase and the
-							// rest is lower-case.
-							if ((value == "LIGHTBAR") || (value == "TRADITIONAL"))
-							{
-								gGenConfig.interfaceStyle = value.substr(0, 1).toUpperCase()
-								                          + value.substr(1).toLowerCase();
-							}
-						}
-						else if (settingUpper == "INPUTTIMEOUTMS")
-							gGenConfig.inputTimeoutMS = +value;
-						else if (settingUpper == "MAXARCFILESIZE")
-							gGenConfig.maxArcFileSize = sizeStrToBytes(value);
-						else if (settingUpper == "MAXTEXTFILESIZE")
-							gGenConfig.maxTextFileSize = sizeStrToBytes(value);
-					}
-					else if (settingsMode == "colors")
-						gGenConfig.colors[setting] = value;
+					// Make sure the value is a string (for attrCodeStr() etc; in some cases, such as a background attribute of 4, it will be a number)
+					var value = colorSettings[prop].toString();
+					// If the value doesn't have any control characters, then add the control character
+					// before attribute characters
+					if (!/\x01/.test(value))
+						value = attrCodeStr(value);
+					if (onlySyncAttrsRegexWholeWord.test(value))
+						gGenConfig.colors[prop] = value;
 				}
 			}
-
-			genCfgFile.close();
 		}
+		genCfgFile.close();
 	}
 
 	return (fileTypeSettingsRead && genSettingsRead);
@@ -1207,9 +1123,9 @@ function writeFileListHeader(pFilename)
 	if (writeFileListHeader.topHelp3 == undefined)
 	{
 		writeFileListHeader.topHelp3 = gGenConfig.colors.headerSeparatorLine
-							   + charStr(HORIZONTAL_SINGLE, 4) + " " + charStr(HORIZONTAL_SINGLE, 8) + " "
-							   + charStr(HORIZONTAL_SINGLE, 10) + " " + charStr(HORIZONTAL_SINGLE, 5) + " "
-							   + charStr(HORIZONTAL_SINGLE, console.screen_columns - 32);
+		                             + charStr(HORIZONTAL_SINGLE, 4) + " " + charStr(HORIZONTAL_SINGLE, 8) + " "
+		                             + charStr(HORIZONTAL_SINGLE, 10) + " " + charStr(HORIZONTAL_SINGLE, 5) + " "
+		                             + charStr(HORIZONTAL_SINGLE, console.screen_columns - 32);
 		// Add line characters to the end of the screen.
 		//for (var x = 30; x < console.screen_columns - 2; ++x)
 		//	writeFileListHeader.topHelp3 += HORIZONTAL_SINGLE;
@@ -2337,4 +2253,28 @@ function charStr(pChar, pNumTimes)
 	for (var i = 0; i < pNumTimes; ++i)
 		str += pChar;
 	return str;
+}
+
+// Given a string of attribute characters, this function inserts the control code
+// in front of each attribute character and returns the new string.
+//
+// Parameters:
+//  pAttrCodeCharStr: A string of attribute characters (i.e., "YH" for yellow high)
+//
+// Return value: A string with the control character inserted in front of the attribute characters
+function attrCodeStr(pAttrCodeCharStr)
+{
+	if (typeof(pAttrCodeCharStr) !== "string")
+		return "";
+
+	var str = "";
+	// See this page for Synchronet color attribute codes:
+	// http://wiki.synchro.net/custom:ctrl-a_codes
+	for (var i = 0; i < pAttrCodeCharStr.length; ++i)
+	{
+		var currentChar = pAttrCodeCharStr.charAt(i);
+		if (/[krgybmcwKRGYBMCWHhIiEeFfNn01234567]/.test(currentChar))
+			str += "\x01" + currentChar;
+	}
+	return str;
 }
\ No newline at end of file
diff --git a/xtrn/dd_arc_viewer/readme.txt b/xtrn/dd_arc_viewer/readme.txt
index f57b956454e6f664c6b2ce2a07bc8aa7ef05799e..fb6f2b893397f4b279b64f42554038faae981951 100644
--- a/xtrn/dd_arc_viewer/readme.txt
+++ b/xtrn/dd_arc_viewer/readme.txt
@@ -1,6 +1,6 @@
                     Digital Distortion Archive Viewer
-                              Version 1.04
-                        Release date: 2022-05-16
+                              Version 1.05
+                        Release date: 2023-08-08
 
                                   by
 
@@ -280,8 +280,11 @@ folows:
 
 setting=value
 
-where "setting" is the behavior setting or color, and "value" is the corresponding
-value for the setting/color.  The colors are Synchronet color codes.
+where "setting" is the behavior setting or color, and "value" is the
+corresponding value for the setting/color.  The colors are Synchronet color
+codes. The control character is not needed for the color codes; for sample, to
+set a color of high-intensity green, you could use "gh" (or "ngh" if you want
+to ensure that the normal attribute gets set first).
 
 Also, comments are allowed in the configuration file.  Comments begin with a
 semicolon (;).
diff --git a/xtrn/dd_arc_viewer/revision_history.txt b/xtrn/dd_arc_viewer/revision_history.txt
index fb20d495ecfe21bb24d326901fd83da8f39f86ee..279d2b1b1c2a29798dcaed8cb51592a5212b0733 100644
--- a/xtrn/dd_arc_viewer/revision_history.txt
+++ b/xtrn/dd_arc_viewer/revision_history.txt
@@ -2,6 +2,9 @@ Revision History for Digital Distortion Archive Viewer
 ======================================================
 Version  Date         Description
 -------  ----         -----------
+1.05     2023-08-08   Internal refactor of how the configuration files are
+                      loaded. Colors in dd_arc_viewer.cfg no longer need the
+                      control character.
 1.04     2022-05-16   Updated to use the new Archive class in Synchronet 3.19.
                       Now has built-in support for the same archive types that
                       Synchronet has built-in (zip, 7z, tgz, etc.). Other
diff --git a/xtrn/dd_upload_processor/ddup.js b/xtrn/dd_upload_processor/ddup.js
index de7386451b39c0da442d9e2311ff38a32ce1fe47..2b43029646d1232f7fbd393425c13c3ea65bb48b 100644
--- a/xtrn/dd_upload_processor/ddup.js
+++ b/xtrn/dd_upload_processor/ddup.js
@@ -9,16 +9,23 @@
  * BBS: Digital Distortion
  * BBS address: digdist.bbsindex.com
  *
- * Date       Author            Version  Description
+ * Date       Author            Description
  * 2009-12-25-
- * 2009-12-28 Eric Oulashin              Initial development
- * 2009-12-29 Eric Oulashin     1.00     Initial public release
- * 2022-06-08 Eric Oulashin     1.01     Made fixes to get the scanner functionality working properly in Linux
- * 2022-06-11 Eric Oulashin     1.02     Improved file/dir permissions more: Set file permissions after extracting
- *                                       an archive so that they're all readable.
- * 2022-06-11 Eric Oulashin     1.03     Removed the chmod stuff, as it is actually not needed.
- * 2023-08-06 Eric Oulashin     1.04     Now uses Synchronet's built-in archiver (added in Synchronet 3.19),
- *                                       if available, to extract archives.
+ * 2009-12-28 Eric Oulashin     Initial development
+ * 2009-12-29 Eric Oulashin     Version 1.00
+ *                              Initial public release
+ * 2022-06-08 Eric Oulashin     Version 1.01
+                                Made fixes to get the scanner functionality working properly in Linux
+ * 2022-06-11 Eric Oulashin     Version 1.02
+ *                              Improved file/dir permissions more: Set file permissions after extracting
+ *                              an archive so that they're all readable.
+ * 2022-06-11 Eric Oulashin     Version 1.03
+ *                              Removed the chmod stuff, as it is actually not needed.
+ * 2023-08-06 Eric Oulashin     Version 1.04
+ *                              Now uses Synchronet's built-in archiver (added in Synchronet 3.19),
+ *                              if available, to extract archives.
+ * 2023-08-07 Eric Oulashin     Version 1.05
+ *                              Internal refactor of how the configuration files are read
  */
 
 /* Command-line arguments:
@@ -46,8 +53,8 @@ gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
 load(gStartupPath + "ddup_cleanup.js");
 
 // Version information
-var gDDUPVersion = "1.04";
-var gDDUPVerDate = "2023-08-06";
+var gDDUPVersion = "1.05";
+var gDDUPVerDate = "2023-08-07";
 
 // Store whether or not this is running in Windows
 var gRunningInWindows = /^WIN/.test(system.platform.toUpperCase());
@@ -96,14 +103,14 @@ else
 
 // Make sure the slashes in the filename are correct for the platform.
 if (gFileToScan.length > 0)
-   gFileToScan = fixPathSlashes(gFileToScan);
+	gFileToScan = fixPathSlashes(gFileToScan);
 
 // A filename must be specified as the first argument, so give an error and return
 // if not.
 if (gFileToScan.length == 0)
 {
-   console.print("\1n\1y\1hError: \1n\1cNo filename specified to process.\r\n\1p");
-   exit(1);
+	console.print("\1n\1y\1hError: \1n\1cNo filename specified to process.\r\n\1p");
+	exit(1);
 }
 
 // Create the global configuration objects.
@@ -151,33 +158,33 @@ exit(main());
 // Return value: An integer to return upon script exit.
 function main()
 {
-   // Output the program name & version information
-   console.print("\1n\r\n\1c\1hD\1n\1cigital \1hD\1n\1cistortion \1hU\1n\1cpload \1hP\1n\1crocessor \1w\1hv\1n\1g" +
-                 gDDUPVersion);
-   // Originally I had this script output the version date, but now I'm not sure
-   // if I want to do that..
-   //console.print(" \1w\1h(\1b" + gDDUPVerDate + "\1w)");
-   console.print("\1n");
-   console.crlf();
-
-   // Process the file
-   var exitCode = processFile(gFileToScan);
-   // Depending on the exit code, display a success or failure message.
-   console.crlf();
-   if (exitCode == 0)
-      console.print(gOKStr + " \1n\1b\1hScan successful - The file passed.\r\n");
-   else
-      console.print(gFailStr + " \1n\1y\1hScan failed!\r\n");
+	// Output the program name & version information
+	console.print("\1n\r\n\1c\1hD\1n\1cigital \1hD\1n\1cistortion \1hU\1n\1cpload \1hP\1n\1crocessor \1w\1hv\1n\1g" +
+	              gDDUPVersion);
+	// Originally I had this script output the version date, but now I'm not sure
+	// if I want to do that..
+	//console.print(" \1w\1h(\1b" + gDDUPVerDate + "\1w)");
+	console.attributes = "N";
+	console.crlf();
+
+	// Process the file
+	var exitCode = processFile(gFileToScan);
+	// Depending on the exit code, display a success or failure message.
+	console.crlf();
+	if (exitCode == 0)
+		console.print(gOKStr + " \1n\1b\1hScan successful - The file passed.\r\n");
+	else
+		console.print(gFailStr + " \1n\1y\1hScan failed!\r\n");
 
-   // If the option to pause at the end is enabled, then prompt the user for
-   // a keypress.
-   if (gGenCfg.pauseAtEnd)
-   {
-      console.print("\1n\1w\1hPress any key to continue:\1n");
-      console.getkey(K_NOECHO);
-   }
+	// If the option to pause at the end is enabled, then prompt the user for
+	// a keypress.
+	if (gGenCfg.pauseAtEnd)
+	{
+		console.print("\1n\1w\1hPress any key to continue:\1n");
+		console.getkey(K_NOECHO);
+	}
 
-   return exitCode;
+	return exitCode;
 }
 
 
@@ -200,17 +207,17 @@ function main()
 //               "always fail": Don't scan the file, and assume it's bad
 function ScannableFile(pExtension, pExtractCmd, pScanOption)
 { 
-   this.extension = "";      // The archive filename extension
-   this.extractCmd = "";     // The command to extract the archive (if applicable)
-   this.scanOption = "scan"; // The scan option ("scan", "always pass", "always fail")
-
-   // If the parameters are valid, then use them to set the object properties.
-   if ((pExtension != null) && (pExtension != undefined) && (typeof(pExtension) == "string"))
-      this.extension = pExtension;
-   if ((pExtractCmd != null) && (pExtractCmd != undefined) && (typeof(pExtractCmd) == "string"))
-      this.extractCmd = pExtractCmd;
-   if ((pScanOption != null) && (pScanOption != undefined) && (typeof(pScanOption) == "string"))
-      this.scanOption = pScanOption;
+	this.extension = "";      // The archive filename extension
+	this.extractCmd = "";     // The command to extract the archive (if applicable)
+	this.scanOption = "scan"; // The scan option ("scan", "always pass", "always fail")
+
+	// If the parameters are valid, then use them to set the object properties.
+	if ((pExtension != null) && (typeof(pExtension) == "string"))
+		this.extension = pExtension;
+	if ((pExtractCmd != null) && (typeof(pExtractCmd) == "string"))
+		this.extractCmd = pExtractCmd;
+	if ((pScanOption != null) && (typeof(pScanOption) == "string"))
+		this.scanOption = pScanOption;
 }
 
 
@@ -223,12 +230,12 @@ function ScannableFile(pExtension, pExtractCmd, pScanOption)
 // in the DOVE-Net Synchronet Discussion sub-board on December 20, 2009.
 function fixArgs(input)
 {
-   var patt1 = /\"[^\"]*\"|\S+/g;
-   var patt2 = /^\"?([^\"]*)\"?$/;
-   return input.join(' ').match(patt1).map(function(item)
-   {
-     return item.replace(patt2, "$1")
-   });
+	var patt1 = /\"[^\"]*\"|\S+/g;
+	var patt2 = /^\"?([^\"]*)\"?$/;
+	return input.join(' ').match(patt1).map(function(item)
+	{
+		return item.replace(patt2, "$1")
+	});
 }
 
 // Scans a file.
@@ -243,7 +250,7 @@ function processFile(pFilename)
 	// Display the program header stuff - The name of the file being scanned
 	// and the status header line
 	var justFilename = getFilenameFromPath(pFilename);
-	console.print("\1n\1w\1hScanning \1b" + justFilename.substr(0, 70));
+	console.print("\1n\1w\1hScanning \1b" + justFilename);
 	console.print("\1n\r\n\1b\1" + "7                             File Scan Status                                  \1n\r\n");
 
 	// If the skipScanIfSysop option is enabled and the user is a sysop,
@@ -305,6 +312,7 @@ function processFile(pFilename)
 				{
 					// Extract the file to the work directory
 					printf(gStatusPrintfStr, "\1m\1h", "Extracting the file...");
+					console.crlf();
 					var errorStr = extractFileToDir(pFilename, workDir);
 					if (errorStr.length == 0)
 					{
@@ -399,90 +407,86 @@ function processFile(pFilename)
 //                          file scan.
 function scanFilesInDir(pDir)
 {
-   // If pDir is unspecified, then just return.
-   if (typeof(pDir) != "string")
-   {
-      var retObj = new Object();
-      retObj.cmdOutput = new Array();
-      retObj.returnCode = -1;
-      return retObj;
-   }
-   if (pDir.length == 0)
-   {
-      var retObj = new Object();
-      retObj.cmdOutput = new Array();
-      retObj.returnCode = -2;
-      return retObj;
-   }
-   // Also, just return if gGenCfg.scanCmd is blank.
-   if (gGenCfg.scanCmd.length == 0)
-   {
-      var retObj = new Object();
-      retObj.cmdOutput = new Array();
-      retObj.returnCode = -3;
-      return retObj;
-   }
+	var retObj = {
+		cmdOutput: [],
+		returnCode: 0
+	};
 
-   // If the filename has a trailing slash, remove it.
-   if ((/\/$/.test(pDir)) || (/\\$/.test(pDir)))
-      pDir = pDir.substr(0, pDir.length-1);
+	// If pDir is unspecified, then just return.
+	if (typeof(pDir) != "string")
+	{
+		retObj.returnCode = -1;
+		return retObj;
+	}
+	if (pDir.length == 0)
+	{
+		retObj.returnCode = -2;
+		return retObj;
+	}
+	// Also, just return if gGenCfg.scanCmd is blank.
+	if (gGenCfg.scanCmd.length == 0)
+	{
+		retObj.returnCode = -3;
+		return retObj;
+	}
 
-   var retObj = null;  // Will be used to capture the return from the scan commands
+	// If the filename has a trailing slash, remove it.
+	if ((/\/$/.test(pDir)) || (/\\$/.test(pDir)))
+		pDir = pDir.substr(0, pDir.length-1);
 
-   // If the virus scan command contains %FILESPEC%, then
-   // replace %FILESPEC% with pDir and run the scan command.
-   if (gGenCfg.scanCmd.indexOf("%FILESPEC%") > -1)
-   {
-      var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pDir) + "\"");
-      retObj = runExternalCmdWithOutput(scanCmd);
-
-      // This is old code, for scanning each file individually (slow):
-      /*
-      // Get a list of the files, and scan them.
-      var files = directory(pDir + "/*");
-      if (files.length > 0)
-      {
-         var scanCmd = null; // Will be used for the scan commands (string)
-         var counter = 0;    // Loop variable
-         for (var i in files)
-         {
-            // If the file is a directory, then recurse into it.  Otherwise,
-            // scan the file using the configured scan command.
-            if (file_isdir(files[i]))
-               retObj = scanFilesInDir(files[i]);
-            else
-            {
-               scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(files[i]) + "\"");
-               // Run the scan command and capture its output, in case the scan fails.
-               retObj = runExternalCmdWithOutput(scanCmd);
-            }
-   
-            // If there's a problem, then stop going through the list of files.
-            if (retObj.returnCode != 0)
-               break;
-         }
-      }
-      else
-      {
-         // There are no files.  So create retObj with default settings
-         // for a good result.
-         retObj = new Object();
-         retObj.returnCode = 0;
-         retObj.cmdOutput = new Array();
-      }
-      */
-   }
-   else
-   {
-      // gGenCfg.scanCmd doesn't contain %FILESPEC%, so set up
-      // retObj with a non-zero return code (for failure)
-      retObj = new Object();
-      retObj.returnCode = -4;
-      retObj.cmdOutput = new Array();
-      retObj.cmdOutput.push("The virus scanner is not set up correctly.");
-   }
+	// If the virus scan command contains %FILESPEC%, then
+	// replace %FILESPEC% with pDir and run the scan command.
+	if (gGenCfg.scanCmd.indexOf("%FILESPEC%") > -1)
+	{
+		var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pDir) + "\"");
+		retObj = runExternalCmdWithOutput(scanCmd);
+
+		// This is old code, for scanning each file individually (slow):
+		/*
+		// Get a list of the files, and scan them.
+		var files = directory(pDir + "/*");
+		if (files.length > 0)
+		{
+			var scanCmd = null; // Will be used for the scan commands (string)
+			var counter = 0;    // Loop variable
+			for (var i in files)
+			{
+				// If the file is a directory, then recurse into it.  Otherwise,
+				// scan the file using the configured scan command.
+				if (file_isdir(files[i]))
+					retObj = scanFilesInDir(files[i]);
+				else
+				{
+					scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(files[i]) + "\"");
+					// Run the scan command and capture its output, in case the scan fails.
+					retObj = runExternalCmdWithOutput(scanCmd);
+				}
 
-   return retObj;
+				// If there's a problem, then stop going through the list of files.
+				if (retObj.returnCode != 0)
+					break;
+			}
+		}
+		else
+		{
+			// There are no files.  So create retObj with default settings
+			// for a good result.
+			retObj = {
+				returnCode: 0,
+				cmdOutput = []
+			};
+		}
+		*/
+	}
+	else
+	{
+		// gGenCfg.scanCmd doesn't contain %FILESPEC%, so set up
+		// retObj with a non-zero return code (for failure)
+		retObj.returnCode = -4;
+		retObj.cmdOutput.push("The virus scanner is not set up correctly.");
+	}
+
+	return retObj;
 }
 
 // Reads the configuration file and returns an object containing the
@@ -494,156 +498,70 @@ function scanFilesInDir(pDir)
 // Return value: Boolean - Whether or not the configuration was read.
 function ReadConfigFile(pCfgFilePath)
 {
-   // Read the file type settings.
-   var fileTypeSettingsRead = false;
-   var fileTypeCfgFile = new File(pCfgFilePath + "ddup_file_types.cfg");
-   if (fileTypeCfgFile.open("r"))
-   {
-      if (fileTypeCfgFile.length > 0)
-      {
-         fileTypeSettingsRead = true;
-         // Read each line from the config file and set the
-         // various options.
-         var pos = 0;               // Index of = in the file lines
-         var fileLine = "";
-         var filenameExt = "";      // Archive filename extension
-         var option = "";           // Configuration option
-         var optionValue = "";      // Configuration option value
-         var optionValueUpper;      // Upper-cased configuration option value
-         var scannableFile = null;  // Will be used to create & store scannable file options
-         while (!fileTypeCfgFile.eof)
-         {
-            // Read the line from the config file, look for a =, and
-            // if found, read the option & value and set them
-            // in cfgObj.
-            fileLine = fileTypeCfgFile.readln(1024);
-
-            // fileLine should be a string, but I've seen some cases
-            // where it isn't, so check its type.
-            if (typeof(fileLine) != "string")
-               continue;
-
-            // If the line is blank or starts with with a semicolon
-            // (the comment character), then skip it.
-            if ((fileLine.length == 0) || (fileLine.substr(0, 1) == ";"))
-               continue;
-
-            // Look for a file extension in square brackets ([ and ]).
-            // If found, then set filenameExt and continue onto the next line.
-            // Note: This regular expression allows whitespace around the [...].
-            if (/^\s*\[.*\]\s*$/.test(fileLine))
-            {
-               var startIndex = fileLine.indexOf("[") + 1;
-               var endIndex = fileLine.lastIndexOf("]");
-               var ext = fileLine.substr(startIndex, endIndex-startIndex).toUpperCase();
-               // If the filename extension is different than the last one
-               // we've seen, then:
-               // 1. If scannableFile is not null, then add it to gScannableFileTypes.
-               // 2. Create a new one (referenced as scannableFile).
-               if (ext != filenameExt)
-               {
-                  if ((scannableFile != null) && (scannableFile != undefined) &&
-                      (filenameExt.length > 0))
-                  {
-                     gFileTypeCfg[filenameExt] = scannableFile;
-                  }
-                  filenameExt = ext;
-                  scannableFile = new ScannableFile(ext, "", "scan");
-               }
-               continue;
-            }
-
-            // If filenameExt is blank, then continue onto the next line.
-            if (filenameExt.length == 0)
-               continue;
-
-            // If we're here, then filenameExt is set, and this is a valid
-            // line to process.
-            // Look for an = in the line, and if found, split into
-            // option & value.
-            pos = fileLine.indexOf("=");
-            if (pos > -1)
-            {
-               // Extract the option & value, trimming leading & trailing spaces.
-               option = trimSpaces(fileLine.substr(0, pos), true, false, true).toUpperCase();
-               optionValue = trimSpaces(fileLine.substr(pos+1), true, false, true);
-
-               if (option == "EXTRACT")
-                  scannableFile.extractCmd = optionValue;
-               else if (option == "SCANOPTION")
-                  scannableFile.scanOption = optionValue;
-            }
-         }
-      }
-
-      fileTypeCfgFile.close();
-   }
-
-   // Read the general program configuration
-   var genSettingsRead = false;
-   var genCfgFile = new File(pCfgFilePath + "ddup.cfg");
-   if (genCfgFile.open("r"))
-   {
-      if (genCfgFile.length > 0)
-      {
-         genSettingsRead = true;
-         var fileLine = null;     // A line read from the file
-         var equalsPos = 0;       // Position of a = in the line
-         var commentPos = 0;      // Position of the start of a comment
-         var setting = null;      // A setting name (string)
-         var settingUpper = null; // Upper-case setting name
-         var value = null;        // A value for a setting (string)
-         while (!genCfgFile.eof)
-         {
-            // Read the next line from the config file.
-            fileLine = genCfgFile.readln(1024);
-
-            // fileLine should be a string, but I've seen some cases
-            // where it isn't, so check its type.
-            if (typeof(fileLine) != "string")
-               continue;
-
-            // If the line starts with with a semicolon (the comment
-            // character) or is blank, then skip it.
-            if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
-               continue;
-
-            // If the line has a semicolon anywhere in it, then remove
-            // everything from the semicolon onward.
-            commentPos = fileLine.indexOf(";");
-            if (commentPos > -1)
-               fileLine = fileLine.substr(0, commentPos);
-
-            // Look for an equals sign, and if found, separate the line
-            // into the setting name (before the =) and the value (after the
-            // equals sign).
-            equalsPos = fileLine.indexOf("=");
-            if (equalsPos > 0)
-            {
-               // Read the setting & value, and trim leading & trailing spaces.
-               setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
-               settingUpper = setting.toUpperCase();
-               value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
-
-               // Skip this one if the value is blank.
-               if (value.length == 0)
-                  continue;
-
-               // Set the appropriate value in the settings object.
-               if (settingUpper == "SCANCMD")
-                  gGenCfg.scanCmd = value;
-               else if (settingUpper == "SKIPSCANIFSYSOP")
-                  gGenCfg.skipScanIfSysop = (value.toUpperCase() == "YES");
-               else if (settingUpper == "PAUSEATEND")
-                  gGenCfg.pauseAtEnd = (value.toUpperCase() == "YES");
-            }
-         }
+	// Read the file type settings.
+	var fileTypeSettingsRead = false;
+	var fileTypeCfgFile = new File(pCfgFilePath + "ddup_file_types.cfg");
+	if (fileTypeCfgFile.open("r"))
+	{
+		if (fileTypeCfgFile.length > 0)
+		{
+			var allFileTypeCfg = fileTypeCfgFile.iniGetAllObjects();
+			fileTypeSettingsRead = true;
+			for (var i = 0; i < allFileTypeCfg.length; ++i)
+			{
+				var filenameExt = allFileTypeCfg[i].name; // Filename extension
+				var scannableFile = new ScannableFile(filenameExt, "", "scan");
+				for (var prop in allFileTypeCfg[i])
+				{
+					var propUpper = prop.toUpperCase();
+					if (propUpper === "EXTRACT")
+						scannableFile.extractCmd = allFileTypeCfg[i][prop];
+					else if (propUpper === "SCANOPTION")
+						scannableFile.scanOption = allFileTypeCfg[i][prop];
+				}
+				gFileTypeCfg[filenameExt] = scannableFile;
+			}
+		}
+		fileTypeCfgFile.close();
+	}
 
-         genCfgFile.close();
-      }
-   }
+	// Read the general configuration
+	var genSettingsRead = false;
+	var genCfgFile = new File(pCfgFilePath + "ddup.cfg");
+	if (genCfgFile.open("r"))
+	{
+		if (genCfgFile.length > 0)
+		{
+			var settingsObj = genCfgFile.iniGetObject();
+			genSettingsRead = true;
+			for (var prop in settingsObj)
+			{
+				// Set the appropriate value in the settings object.
+				var settingUpper = prop.toUpperCase();
+				if (settingUpper == "SCANCMD")
+					gGenCfg.scanCmd = settingsObj[prop];
+				else if (settingUpper == "SKIPSCANIFSYSOP")
+				{
+					if (typeof(settingsObj[prop]) === "string")
+						gGenCfg.skipScanIfSysop = (settingsObj[prop].toUpperCase() == "YES");
+					else if (typeof(settingsObj[prop]) === "boolean")
+						gGenCfg.skipScanIfSysop = settingsObj[prop];
+				}
+				else if (settingUpper == "PAUSEATEND")
+				{
+					if (typeof(settingsObj[prop]) === "string")
+					{
+						var valueUpper = settingsObj[prop].toUpperCase();
+						gGenCfg.pauseAtEnd = (valueUpper == "YES" || valueUpper == "TRUE");
+					}
+					else if (typeof(settingsObj[prop]) === "boolean")
+						gGenCfg.pauseAtEnd = settingsObj[prop];
+				}
+			}
+		}
+	}
 
-   return (fileTypeSettingsRead && genSettingsRead);
+	return (fileTypeSettingsRead && genSettingsRead);
 }
 
 // Removes multiple, leading, and/or trailing spaces
@@ -872,7 +790,6 @@ function extractFileToDir(pFilename, pWorkDir)
 			// due to certain characters in the filename.
 			var numFilesExtracted = arcFile.extract(pWorkDir, true);
 			builtInExtractSucceeded = true;
-			if (user.is_sysop) console.print("\x01n\r\n\x01gExtracted with Synchronet's built-in Archive support\x01n\r\n\r\n"); // Temporary
 		}
 		catch (e)
 		{
diff --git a/xtrn/dd_upload_processor/readme.txt b/xtrn/dd_upload_processor/readme.txt
index ff0ef27df0369a0d8feeb15d451c03a12932f13c..da0b7c93f47e3e8caba5594f013926c83f860238 100644
--- a/xtrn/dd_upload_processor/readme.txt
+++ b/xtrn/dd_upload_processor/readme.txt
@@ -1,6 +1,6 @@
                    Digital Distortion Upload Processor
-                              Version 1.04
-                        Release date: 2023-08-06
+                              Version 1.05
+                        Release date: 2023-08-07
 
                                   by
 
diff --git a/xtrn/dd_upload_processor/version_history.txt b/xtrn/dd_upload_processor/version_history.txt
index 81cc8dae61a05a22e01c679b4ffb450648d65a35..c7a9fa699b85daae511f5389035a5e9deaec69a7 100644
--- a/xtrn/dd_upload_processor/version_history.txt
+++ b/xtrn/dd_upload_processor/version_history.txt
@@ -2,6 +2,8 @@ Revision History for Digital Distortion Upload Processor
 ========================================================
 Version  Date         Description
 -------  ----         -----------
+1.05     2023-08-07   Internal refactor of how the configuration files are
+                      read. Behavior is the same.
 1.04     2023-08-06   Now uses Synchronet's built-in archiver (added in
                       Synchronet 3.19), if available, to extract archives.
 1.03     2022-06-11   Removed the chmod updates because they're actually not