diff --git a/xtrn/DDMsgReader/DDMsgReader.cfg b/xtrn/DDMsgReader/DDMsgReader.cfg
index 948439d4c5874bd5d7a4622fd59799b4f9975b41..f1ea4c897acf5f46be52745568bd4ccf0081d259 100644
--- a/xtrn/DDMsgReader/DDMsgReader.cfg
+++ b/xtrn/DDMsgReader/DDMsgReader.cfg
@@ -99,5 +99,14 @@ promptDelPersonalEmailAfterReply=false
 ; The default directory on the BBS machine to save messages to (for the sysop)
 msgSaveDir=
 
+; Sub-board sorting for changing to another sub-board: None, Alphabetical,
+; LatestMsgDateOldestFirst, or LatestMsgDateNewestFirst
+subBoardChangeSorting=None
+
+; For indexed-mode newscan, whether to only show sub-boards with new messages.
+; This is the default for a user setting; users can toggle this for themselves
+; as they like. Valid values are true or false.
+indexedModeNewscanOnlyShowSubsWithNewMsgs=false
+
 ; The theme file name (for colors, strings, etc.)
 themeFilename=DefaultTheme.cfg
diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js
index 6aea1cc93aa65c0032ab520e0fccc526537c9623..978ee0f75b605918b6689898cbe0c16a967d2f31 100644
--- a/xtrn/DDMsgReader/DDMsgReader.js
+++ b/xtrn/DDMsgReader/DDMsgReader.js
@@ -163,6 +163,16 @@
  *                              Updates to help with the newscan issues placing the user at the first message, etc.
  * 2024-09-03 Eric Oulashin     Version 1.95h
  *                              Fix for saving an ANSI message to the local BBS PC
+ * 2024-10-16 Eric Oulashin     Version 1.96 Beta
+ *                              Started working on sub-board sorting for changing sub-boards
+ * 2024-10-24 Eric Oulashin     Updated for bbs.msg_number and bbs.smb_curmsg being writeable
+ * 2024-10-25 Eric Oulashin     Message sub-board sort fixes
+ * 2024-10-26 Eric Oulashin     When downloading attachments, let the user select a download
+ *                              protocol if they don't have one configured.
+ *                              User options for sub-board sorting when changing to another
+ *                              sub-board, and whether to show sub-boards with new messages in
+ *                              the indexed newscan.
+ *                              Releasing this version (1.96).
  */
 
 "use strict";
@@ -263,23 +273,17 @@ require("attr_conv.js", "convertAttrsToSyncPerSysCfg");
 require("graphic.js", 'Graphic');
 require("smbdefs.js", "SMB_POLL_ANSWER");
 load('822header.js');
+// If using Synchronet 3.20 or newer, load user_settings_lib.js, for
+// prompt_user_for_download_protocol
+if (system.version_num >= 32000)
+	require("user_settings_lib.js", "prompt_user_for_download_protocol");
 var ansiterm = require("ansiterm_lib.js", 'expand_ctrl_a');
 var hexdump = load('hexdump_lib.js');
 
-/*
-// Temporary
-//if (user.is_sysop)
-{
-	//bbs.scan_subs(SCAN_NEW);
-	bbs.scan_posts();
-	exit(0);
-}
-// End Temporary
-*/
 
 // Reader version information
-var READER_VERSION = "1.95h";
-var READER_DATE = "2024-09-03";
+var READER_VERSION = "1.96";
+var READER_DATE = "2024-10-26";
 
 // Keyboard key codes for displaying on the screen
 var UP_ARROW = ascii(24);
@@ -454,6 +458,12 @@ const MSG_LIST_SORT_DATETIME_WRITTEN = 1;
 const POPULATE_MSG_HDRS_FROM_SCAN_PTR = -1;
 const POPULATE_NEWSCAN_FORCE_GET_ALL_HDRS = -2; // Get all message headers even for a newscan
 
+// Sub-board sort options for changing to another sub-board
+const SUB_BOARD_SORT_NONE = 0;
+const SUB_BOARD_SORT_ALPHABETICAL = 1;
+const SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST = 2;
+const SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST = 3;
+
 // Misc. defines
 var ERROR_WAIT_MS = 1500;
 var SEARCH_TIMEOUT_MS = 10000;
@@ -574,8 +584,7 @@ if (gCmdLineArgVals.hasOwnProperty("search") && (gCmdLineArgVals.search.toLowerC
 		case "A": // Abort
 		default:
 			gDoDDMR = false;
-			console.print("\x01n\x01h\x01y\x01iAborted\x01n");
-			console.crlf();
+			console.putmsg(bbs.text(Aborted), P_SAVEATR);
 			console.pause();
 			break;
 	}
@@ -1231,6 +1240,8 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 		newscanOnlyShowNewMsgs: true,
 		// Whether or not to use indexed mode for doing a newscan
 		useIndexedModeForNewscan: false,
+		// When using indexed mode newscan, only show sub-boards that have new messages
+		indexedModeNewscanOnlyShowSubsWithNewMsgs: false,
 		// Whether or not the indexed mode sub-board menu should "snap" selection to sub-boards with new messages
 		// when the menu is shown
 		indexedModeMenuSnapToFirstWithNew: false,
@@ -1248,7 +1259,9 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 		// When reading personal email, whether or not to propmt if the user wants to delete a message after replying to it
 		promptDelPersonalEmailAfterReply: false,
 		// Whether or not to display the 'replied' status character (for personal email)
-		displayMsgRepliedChar: true
+		displayMsgRepliedChar: true,
+		// Sub-board sorting for changing to another sub-board: None, Alphabetical, or LatestMsgDate
+		subBoardChangeSorting: SUB_BOARD_SORT_NONE
 	};
 	// Read the settings from the config file (some settings could set user settings)
 	this.cfgFileSuccessfullyRead = false;
@@ -1493,7 +1506,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 	// Lightbar-specific methods
 	this.WriteMsgGroupLine = DigDistMsgReader_writeMsgGroupLine;
 	this.UpdateMsgAreaPageNumInHeader = DigDistMsgReader_updateMsgAreaPageNumInHeader;
-	this.GetMsgSubBoardLine = DigDistMsgReader_GetMsgSubBrdLine;
+	this.GetMsgSubBoardLine = DigDistMsgReader_GetMsgSubBoardLine;
 	// Choose Message Area help screen
 	this.ShowChooseMsgAreaHelpScreen = DigDistMsgReader_showChooseMsgAreaHelpScreen;
 	// Method to build the sub-board printf information for a message
@@ -3948,6 +3961,23 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 	var continueOn = true;
 	while (continueOn)
 	{
+		// TODO: Sometimes, showing the menu here removes the top header line
+		// and bottom key help line. Happens with SyncTerm, but not with KiTTY.
+		// Change to MusicalNet, New Albums
+		// List messages & scroll down to the bottom
+		// Press C and change to the Synthesizers sub-board
+		// At this point, it lists the messages and the top header & bottom help line are gone
+		// Did it clear screen?
+		/*
+		// Temporary
+		if (user.is_sysop)
+		{
+			console.attributes = "N";
+			console.gotoxy(1, 2);
+			console.print("Before showing the menu  \x01p");
+		}
+		// End Temporary
+		*/
 		var userChoice = msgListMenu.GetVal(drawMenu);
 		drawMenu = true;
 		var lastUserInputUpper = (typeof(msgListMenu.lastUserInput) == "string" ? msgListMenu.lastUserInput.toUpperCase() : msgListMenu.lastUserInput);
@@ -4713,10 +4743,74 @@ function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx)
 	subBoardMenu.AddAdditionalQuitKeys("nNqQ ?0123456789/" + CTRL_F);
 
 	// Add the sub-board items to the menu
-	for (var subIdx = 0; subIdx < msg_area.grp_list[pGrpIdx].sub_list.length; ++subIdx)
+	if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_ALPHABETICAL)
+	{
+		var sortedSubs = [];
+		for (var subIdx = 0; subIdx < msg_area.grp_list[pGrpIdx].sub_list.length; ++subIdx)
+		{
+			sortedSubs.push({
+				subIdx: subIdx,
+				desc: msg_area.grp_list[pGrpIdx].sub_list[subIdx].description
+			});
+		}
+		sortedSubs.sort(function(pA, pB)
+		{
+			if (pA.desc < pB.desc)
+				return -1;
+			else if (pA.desc == pB.desc)
+				return 0;
+			else if (pA.desc > pB.desc)
+				return 1;
+		});
+		for (var subsI = 0; subsI < sortedSubs.length; ++subsI)
+		{
+			var itemText = this.GetMsgSubBoardLine(pGrpIdx, sortedSubs[subsI].subIdx, false, subsI+1);
+			subBoardMenu.Add(strip_ctrl(itemText), sortedSubs[subsI].subIdx);
+		}
+	}
+	else if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST ||
+	         this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST)
+	{
+		var sortedSubs = [];
+		for (var subIdx = 0; subIdx < msg_area.grp_list[pGrpIdx].sub_list.length; ++subIdx)
+			sortedSubs.push(getSubBoardInfo(pGrpIdx, subIdx, this.msgAreaList_lastImportedMsg_showImportTime));
+		if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST)
+		{
+			sortedSubs.sort(function(pA, pB)
+			{
+				if (pA.newestTime < pB.newestTime)
+					return -1;
+				else if (pA.newestTime == pB.newestTime)
+					return 0;
+				else if (pA.newestTime > pB.newestTime)
+					return 1;
+			});
+		}
+		else if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST)
+		{
+			sortedSubs.sort(function(pA, pB)
+			{
+				if (pA.newestTime < pB.newestTime)
+					return 1;
+				else if (pA.newestTime == pB.newestTime)
+					return 0;
+				else if (pA.newestTime > pB.newestTime)
+					return -1;
+			});
+		}
+		for (var subsI = 0; subsI < sortedSubs.length; ++subsI)
+		{
+			var itemText = this.GetMsgSubBoardLine(pGrpIdx, msg_area.sub[sortedSubs[subsI].subCode].index, false, subsI+1);
+			subBoardMenu.Add(strip_ctrl(itemText), msg_area.sub[sortedSubs[subsI].subCode].index);
+		}
+	}
+	else // SUB_BOARD_SORT_NONE
 	{
-		var itemText = this.GetMsgSubBoardLine(pGrpIdx, subIdx, false);
-		subBoardMenu.Add(strip_ctrl(itemText), subIdx);
+		for (var subIdx = 0; subIdx < msg_area.grp_list[pGrpIdx].sub_list.length; ++subIdx)
+		{
+			var itemText = this.GetMsgSubBoardLine(pGrpIdx, subIdx, false);
+			subBoardMenu.Add(strip_ctrl(itemText), subIdx);
+		}
 	}
 	// Alternately, we could change the menu's NumItems() and GetItem():
 	/*
@@ -4745,7 +4839,25 @@ function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx)
 	// Set the currently selected item to the current group
 	if (msg_area.sub[this.subBoardCode].grp_index == pGrpIdx)
 	{
-		subBoardMenu.SetSelectedItemIdx(msg_area.sub[this.subBoardCode].index);
+		// If no sorting is being used, then simply set the current selected
+		// index to the sub-board index.
+		if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_NONE)
+			subBoardMenu.SetSelectedItemIdx(msg_area.sub[this.subBoardCode].index);
+		else
+		{
+			// Sorting is being used. Look for the item with the user's current
+			// sub-board index and set that as the current item index in the menu
+			var numItems = subBoardMenu.NumItems();
+			for (var i = 0; i < numItems; ++i)
+			{
+				if (msg_area.sub[this.subBoardCode].index == subBoardMenu.GetItem(i).retval)
+				{
+					subBoardMenu.SetSelectedItemIdx(i);
+					break;
+				}
+			}
+		}
+
 		/*
 		subBoardMenu.selectedItemIdx = msg_area.sub[this.subBoardCode].index;
 		if (subBoardMenu.selectedItemIdx >= subBoardMenu.topItemIdx+subBoardMenu.GetNumItemsPerPage())
@@ -9589,6 +9701,22 @@ function DigDistMsgReader_ReadConfigFile()
 			this.userSettings.promptDelPersonalEmailAfterReply = settingsObj.promptDelPersonalEmailAfterReply;
 		if (typeof(settingsObj.displayIndexedModeMenuIfNoNewMessages) === "boolean")
 			this.userSettings.displayIndexedModeMenuIfNoNewMessages = settingsObj.displayIndexedModeMenuIfNoNewMessages;
+		if (typeof(settingsObj.subBoardChangeSorting) === "string")
+		{
+			var valUpper = settingsObj.subBoardChangeSorting.toUpperCase();
+			if (valUpper == "NONE")
+				this.userSettings.subBoardChangeSorting = SUB_BOARD_SORT_NONE;
+			else if (valUpper == "ALPHABETICAL")
+				this.userSettings.subBoardChangeSorting = SUB_BOARD_SORT_ALPHABETICAL;
+			else if (valUpper == "LATESTMSGDATEOLDESTFIRST" || valUpper == "LATEST_MSG_DATE_OLDEST_FIRST")
+				this.userSettings.subBoardChangeSorting = SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST;
+			else if (valUpper == "LATESTMSGDATENEWESTFIRST" || valUpper == "LATEST_MSG_DATE_NEWEST_FIRST")
+				this.userSettings.subBoardChangeSorting = SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST;
+			else
+				this.userSettings.subBoardChangeSorting = SUB_BOARD_SORT_NONE;
+		}
+		if (typeof(settingsObj.indexedModeNewscanOnlyShowSubsWithNewMsgs) === "boolean")
+			this.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs = settingsObj.indexedModeNewscanOnlyShowSubsWithNewMsgs;
 	}
 	else
 	{
@@ -11253,15 +11381,54 @@ function DigDistMsgReader_ReplyToMsg(pMsgHdr, pMsgText, pPrivate, pMsgIdx)
 		// propmt), this information is stored in bbs.smb_last_msg,
 		// bbs.smb_total_msgs, and bbs.smb_curmsg, but this message lister
 		// can't change those values.  Thus, we need to write them to a file.
-		var msgBaseInfoFile = new File(system.node_dir + "DDML_SyncSMBInfo.txt");
-		if (msgBaseInfoFile.open("w"))
+
+		// Some message editors (i.e., SlyEdit) need to access the message
+		// base and get the number of the message being replied to (in order
+		// to get the author's initials for quoting, etc.).  This information
+		// is stored in bbs.msg_number and bbs.smb_curmsg (there's also
+		// bbs.smb_last_msg and bbs.smb_total_msgs).  We really only need to
+		// change bbs.msg_number and bbs.smb_curmsg. In Synchronet versions 3.19
+		// and lower, these are all read-only, so we'd need to write them to a file.
+		// Try and change them, and only write the file if we get an exception
+		// (which would be due to them being read-only in the running version of
+		// Synchronet).
+		var msgbaseInfoDropFileName = system.node_dir + "DDML_SyncSMBInfo.txt"; // Will be removed later if it exists
+		try
 		{
-			msgBaseInfoFile.writeln(msgbase.last_msg.toString()); // Highest message #
-			msgBaseInfoFile.writeln(this.NumMessages(msgbase).toString()); // Total # messages
-			// Message number (Note: For SlyEdit, requires SlyEdit 1.27 or newer).
-			msgBaseInfoFile.writeln(pMsgHdr.number.toString()); // # of the message being read (New: 2013-05-14)
-			msgBaseInfoFile.writeln(this.subBoardCode); // Sub-board code
-			msgBaseInfoFile.close();
+			bbs.msg_number = pMsgHdr.number;
+			bbs.smb_curmsg = pMsgHdr.number;
+
+			// bbs.smb_sub_code is also used by SlyEdit, but it
+			// probably doesn't need to be changed; it's still
+			// read-only.  SlyEdit gets message information in
+			// its getCurMsgInfo() function in SlyEdit_Misc.js.
+			//bbs.smb_sub_code = this.subBoardCode;
+			/*
+			bbs.smb_last_msg = msgbase.last_msg;
+			bbs.smb_total_msgs = msgbase.total_msgs;
+			*/
+		}
+		catch (e)
+		{
+			// e would be something like "TypeError: bbs.msg_number is read-only"
+			log(LOG_INFO, "Error setting bbs.msg_number or bbs.smb_curmsg (" + e + "); writing " + msgbaseInfoDropFileName + " with messagebase info");
+
+			// Open a file in the node directory and write some information
+			// about the current sub-board and message being read:
+			// - The highest message number in the sub-board (last message)
+			// - The total number of messages in the sub-board
+			// - The number of the message being read
+			// - The current sub-board code
+			var msgBaseInfoFile = new File(msgbaseInfoDropFileName);
+			if (msgBaseInfoFile.open("w"))
+			{
+				msgBaseInfoFile.writeln(msgbase.last_msg.toString()); // Highest message #
+				msgBaseInfoFile.writeln(this.NumMessages(msgbase).toString()); // Total # messages
+				// Message number (Note: For SlyEdit, requires SlyEdit 1.27 or newer).
+				msgBaseInfoFile.writeln(pMsgHdr.number.toString()); // # of the message being read (New: 2013-05-14)
+				msgBaseInfoFile.writeln(this.subBoardCode); // Sub-board code
+				msgBaseInfoFile.close();
+			}
 		}
 
 		// Store the current total number of messages so that we can search new
@@ -11295,7 +11462,12 @@ function DigDistMsgReader_ReplyToMsg(pMsgHdr, pMsgText, pPrivate, pMsgIdx)
 			retObj.postSucceeded = bbs.post_msg(this.subBoardCode, replyMode, pMsgHdr);
 			console.pause();
 		}
-		msgBaseInfoFile.remove();
+		// Remove the messagebase info drop file if it exists
+		if (file_exists(msgbaseInfoDropFileName))
+		{
+			if (!file_remove(msgbaseInfoDropFileName))
+				log(LOG_ERROR, "Failed to remove " + msgbaseInfoDropFileName);
+		}
 		var msgbaseReOpened = msgbase.open();
 
 		// If the user replied to the message and a message search was done that
@@ -12784,9 +12956,9 @@ function DigDistMsgReader_SelectMsgArea_Lightbar(pMsgGrp, pGrpIdx)
 		// and the user would have pressed one of the additional quit keys set
 		// up for the menu.  So look at the menu's lastUserInput and do the
 		// appropriate thing.
-		else if ((lastUserInputUpper == "Q") || (lastUserInputUpper == KEY_ESC)) // Quit
+		else if (lastUserInputUpper == "Q" || lastUserInputUpper == KEY_ESC) // Quit
 			continueOn = false;
-		else if ((lastUserInputUpper == "/") || (lastUserInputUpper == CTRL_F)) // Start of find
+		else if (lastUserInputUpper == "/" || lastUserInputUpper == CTRL_F) // Start of find
 		{
 			console.gotoxy(1, console.screen_rows);
 			console.cleartoeol("\x01n");
@@ -12960,10 +13132,13 @@ function DigDistMsgReader_SelectMsgArea_Lightbar(pMsgGrp, pGrpIdx)
 			// prompt the user for the message number.
 			console.gotoxy(1, console.screen_rows);
 			console.clearline("\x01n");
-			console.print("\x01cChoose group #: \x01h");
-			var userInput = console.getnum(msg_area.grp_list.length);
+			if (chooseMsgGrp)
+				console.print("\x01cChoose group #: \x01h");
+			else
+				console.print("\x01cChoose sub #: \x01h");
+			var userInput = console.getnum(msgAreaMenu.NumItems());
 			if (userInput > 0)
-				chosenIdx = userInput - 1;
+				chosenIdx = msgAreaMenu.GetItem(userInput - 1).retval; // The item retval is the sub-board index
 			else
 			{
 				// The user didn't make a selection.  So, we need to refresh
@@ -13115,7 +13290,7 @@ function DigDistMsgReader_SelectMsgArea_Traditional()
 				{
 					console.clear("\x01n");
 					this.DisplayAreaChgHdr();
-					this.ListSubBoardsInMsgGroup(selectedGrp-1, defaultSubBoard-1, null, subSearchText);
+					var subIndexes = this.ListSubBoardsInMsgGroup(selectedGrp-1, defaultSubBoard-1, this.userSettings.subBoardChangeSorting, subSearchText);
 					console.crlf();
 					console.print("\x01n\x01b\x01h" + TALL_UPPER_MID_BLOCK + " \x01n\x01cWhich, \x01h/\x01n\x01c or \x01hCTRL-F\x01n\x01c, \x01hQ\x01n\x01cuit, or [\x01h" +
 					              defaultSubBoard + "\x01n\x01c]: \x01h");
@@ -13152,7 +13327,8 @@ function DigDistMsgReader_SelectMsgArea_Traditional()
 						// there or false if not.  If there is no search specified,
 						// the validator function will return a 'true' value.
 						var selectedGrpIdx = selectedGrp - 1;
-						var selectedSubIdx = selectedSubBoard - 1;
+						//var selectedSubIdx = selectedSubBoard - 1;
+						var selectedSubIdx = subIndexes[selectedSubBoard - 1];
 						var msgAreaValidRetval = this.ValidateMsgAreaChoice(selectedGrpIdx, selectedSubIdx);
 						if (msgAreaValidRetval.msgAreaGood)
 						{
@@ -13228,14 +13404,18 @@ function DigDistMsgReader_ListMsgGrps_Traditional(pSearchText)
 //  pMarkIndex: An index of a message group to highlight.  This
 //                   is optional; if left off, this will default to
 //                   the current sub-board.
-//  pSortType: Optional - A string describing how to sort the list (if desired):
-//             "none": Default behavior - Sort by sub-board #
-//             "dateAsc": Sort by date, ascending
-//             "dateDesc": Sort by date, descending
-//             "description": Sort by description
+//  pSortType: Optional - A numeric value to specify how to sort the list (if desired):
+//             SUB_BOARD_SORT_NONE: Default behavior - Sort by sub-board #
+//             SUB_BOARD_SORT_ALPHABETICAL: Alphetical
+//             SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST: Sort by date, ascending
+//             SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST: Sort by date, descending
 //  pSearchText: Optional - Search text for the message sub-boards
+//
+// Return value: An array of sub-board indexes, in order of their display (useful when sorting is being used)
 function DigDistMsgReader_ListSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIndex, pSortType, pSearchText)
 {
+	var subIndexes = [];
+
 	// Default to the current message group & sub-board if pGrpIndex
 	// and pMarkIndex aren't specified.
 	var grpIndex = bbs.curgrp;
@@ -13268,94 +13448,58 @@ function DigDistMsgReader_ListSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIn
 	var newestDate = {}; // For storing the date of the newest post in a sub-board
 	var msgBase = null;    // For opening the sub-boards with a MsgBase object
 	var msgHeader = null;  // For getting the date & time of the newest post in a sub-board
-	var subBoardNum = 0;   // 0-based sub-board number (because the array index is the number as a str)
+	//var subBoardNum = 0;   // 0-based sub-board number (because the array index is the number as a str)
 	var includeSubBoard = true;
 	// If a sort type is specified, then add the sub-board information to
 	// subBoardArray so that it can be sorted.
-	if ((typeof(pSortType) == "string") && (pSortType != "") && (pSortType != "none"))
+	if (typeof(pSortType) == "number" && pSortType != SUB_BOARD_SORT_NONE)
 	{
 		subBoardArray = [];
 		var subBoardInfo = null;
-		for (var arrSubBoardNum in msg_area.grp_list[grpIndex].sub_list)
+		for (var subIdx = 0; subIdx < msg_area.grp_list[grpIndex].sub_list.length; ++subIdx)
 		{
 			if (searchText.length > 0)
-				includeSubBoard = ((msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].name.toUpperCase().indexOf(searchText) >= 0) || (msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.toUpperCase().indexOf(searchText) >= 0));
+				includeSubBoard = ((msg_area.grp_list[grpIndex].sub_list[subIdx].name.toUpperCase().indexOf(searchText) >= 0) || (msg_area.grp_list[grpIndex].sub_list[subIdx].description.toUpperCase().indexOf(searchText) >= 0));
 			else
 				includeSubBoard = true;
 			if (!includeSubBoard)
 				continue;
-
-			// Open the current sub-board with the msgBase object.
-			msgBase = new MsgBase(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
-			if (msgBase.open())
-			{
-				subBoardInfo = new MsgSubBoardInfo();
-				subBoardInfo.subBoardNum = +(arrSubBoardNum);
-				subBoardInfo.description = msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description;
-				// Note: numReadableMsgs() is slow because it goes through and
-				// checks for deleted messages, etc., so just use msgBase.total_msgs
-				//subBoardInfo.numPosts = numReadableMsgs(msgBase, msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
-				subBoardInfo.numPosts = msgBase.total_msgs;
-
-				// Get the date & time when the last message was imported.
-				if (subBoardInfo.numPosts > 0)
-				{
-					var msgIdx = msgBase.total_msgs-1;
-					msgHeader = msgBase.get_msg_index(true, msgIdx, false);
-					while (!isReadableMsgHdr(msgHeader, msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code) && (msgIdx >= 0))
-						msgHeader = msgBase.get_msg_index(true, --msgIdx, true);
-					if (msgHeader != null)
-						msgHeader = msgBase.get_msg_header(true, msgIdx, false);
-					if (msgHeader != null)
-					{
-						if (this.msgAreaList_lastImportedMsg_showImportTime)
-							subBoardInfo.newestPostDate = msgHeader.when_imported_time;
-						else
-						{
-							//subBoardInfo.newestPostDate = msgHeader.when_written_time;
-							var msgWrittenLocalTime = msgWrittenTimeToLocalBBSTime(msgHeader);
-							if (msgWrittenLocalTime != -1)
-								subBoardInfo.newestPostDate = msgWrittenTimeToLocalBBSTime(msgHeader);
-							else
-								subBoardInfo.newestPostDate = msgHeader.when_written_time;
-						}
-					}
-				}
-			}
-			msgBase.close();
+			
+			var subBoardInfo = getSubBoardInfo(grpIndex, subIdx, this.msgAreaList_lastImportedMsg_showImportTime);
+			subBoardInfo.subIdx = subIdx;
 			subBoardArray.push(subBoardInfo);
 		}
 
 		// Possibly sort the sub-board list.
-		if (pSortType == "dateAsc")
+		if (pSortType == SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST)
 		{
 			subBoardArray.sort(function(pA, pB)
 			{
 				// Return -1, 0, or 1, depending on whether pA's date comes
 				// before, is equal to, or comes after pB's date.
 				var returnValue = 0;
-				if (pA.newestPostDate < pB.newestPostDate)
+				if (pA.newestTime < pB.newestTime)
 					returnValue = -1;
-				else if (pA.newestPostDate > pB.newestPostDate)
+				else if (pA.newestTime > pB.newestTime)
 					returnValue = 1;
 				return returnValue;
 			});
 		}
-		else if (pSortType == "dateDesc")
+		else if (pSortType == SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST)
 		{
 			subBoardArray.sort(function(pA, pB)
 			{
 				// Return -1, 0, or 1, depending on whether pA's date comes
 				// after, is equal to, or comes before pB's date.
 				var returnValue = 0;
-				if (pA.newestPostDate > pB.newestPostDate)
+				if (pA.newestTime > pB.newestTime)
 					returnValue = -1;
-				else if (pA.newestPostDate < pB.newestPostDate)
+				else if (pA.newestTime < pB.newestTime)
 					returnValue = 1;
 				return returnValue;
 			});
 		}
-		else if (pSortType == "description")
+		else if (pSortType == SUB_BOARD_SORT_ALPHABETICAL)
 		{
 			// Binary safe string comparison  
 			// 
@@ -13369,49 +13513,54 @@ function DigDistMsgReader_ListSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIn
 			// *     returns 2: -1
 			subBoardArray.sort(function(pA, pB)
 			{
-				return ((pA.description == pB.description) ? 0 : ((pA.description > pB.description) ? 1 : -1));
+				return (pA.desc == pB.desc ? 0 : (pA.desc > pB.desc ? 1 : -1));
 			});
 		}
 
 		// Display the sub-board list.
 		for (var i = 0; i < subBoardArray.length; ++i)
 		{
+			subIndexes.push(subBoardArray[i].subIdx);
 			console.crlf();
-			console.print((subBoardArray[i].subBoardNum == highlightIndex) ? "\x01n" +
+			console.print((subBoardArray[i].subIdx == highlightIndex) ? "\x01n" +
 			              this.colors.areaChooserMsgAreaMarkColor + "*" : " ");
-			printf(this.subBoardListPrintfInfo[grpIndex].printfStr, +(subBoardArray[i].subBoardNum+1),
-			       subBoardArray[i].description.substr(0, this.subBoardNameLen),
-			       subBoardArray[i].numPosts, strftime("%Y-%m-%d", subBoardArray[i].newestPostDate),
-			       strftime("%H:%M:%S", subBoardArray[i].newestPostDate));
+			//var itemNum = subBoardArray[i].subIdx + 1;
+			var itemNum = i + 1;
+			printf(this.subBoardListPrintfInfo[grpIndex].printfStr, itemNum,
+			       subBoardArray[i].desc.substr(0, this.subBoardNameLen),
+			       subBoardArray[i].numItems, strftime("%Y-%m-%d", subBoardArray[i].newestTime),
+			       strftime("%H:%M:%S", subBoardArray[i].newestTime));
 		}
 	}
 	// If no sort type is specified, then output the sub-board information in
 	// order of sub-board number.
 	else
 	{
-		for (var arrSubBoardNum in msg_area.grp_list[grpIndex].sub_list)
+		for (var subIdx = 0; subIdx < msg_area.grp_list[grpIndex].sub_list.length; ++subIdx)
 		{
+			subIndexes.push(subIdx);
+
 			if (searchText.length > 0)
-				includeSubBoard = ((msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].name.toUpperCase().indexOf(searchText) >= 0) || (msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.toUpperCase().indexOf(searchText) >= 0));
+				includeSubBoard = ((msg_area.grp_list[grpIndex].sub_list[subIdx].name.toUpperCase().indexOf(searchText) >= 0) || (msg_area.grp_list[grpIndex].sub_list[subIdx].description.toUpperCase().indexOf(searchText) >= 0));
 			else
 				includeSubBoard = true;
 			if (!includeSubBoard)
 				continue;
 
 			// Open the current sub-board with the msgBase object.
-			msgBase = new MsgBase(msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
+			msgBase = new MsgBase(msg_area.grp_list[grpIndex].sub_list[subIdx].code);
 			if (msgBase.open())
 			{
 				// Get the date & time when the last message was imported.
 				// Note: numReadableMsgs() is slow because it goes through and
 				// checks for deleted messages, etc., so just use msgBase.total_msgs
-				//var numMsgs = numReadableMsgs(msgBase, msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code);
+				//var numMsgs = numReadableMsgs(msgBase, msg_area.grp_list[grpIndex].sub_list[subIdx].code);
 				var numMsgs = msgBase.total_msgs;
 				if (numMsgs > 0)
 				{
 					var msgIdx = msgBase.total_msgs-1;
 					msgHeader = msgBase.get_msg_index(true, msgIdx, false);
-					while (!isReadableMsgHdr(msgHeader, msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].code) && (msgIdx >= 0))
+					while (!isReadableMsgHdr(msgHeader, msg_area.grp_list[grpIndex].sub_list[subIdx].code) && (msgIdx >= 0))
 					  msgHeader = msgBase.get_msg_index(true, --msgIdx, true);
 					if (msgHeader != null)
 						msgHeader = msgBase.get_msg_header(true, msgIdx, false);
@@ -13445,18 +13594,20 @@ function DigDistMsgReader_ListSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIn
 					newestDate.date = newestDate.time = "";
 
 				// Print the sub-board information
-				subBoardNum = +(arrSubBoardNum);
+				var subBoardNum = +(subIdx);
 				console.crlf();
 				console.print((subBoardNum == highlightIndex) ? "\x01n" +
 				              this.colors.areaChooserMsgAreaMarkColor + "*" : " ");
 				printf(this.subBoardListPrintfInfo[grpIndex].printfStr, +(subBoardNum+1),
-				       msg_area.grp_list[grpIndex].sub_list[arrSubBoardNum].description.substr(0, this.subBoardListPrintfInfo[grpIndex].nameLen),
+				       msg_area.grp_list[grpIndex].sub_list[subIdx].description.substr(0, this.subBoardListPrintfInfo[grpIndex].nameLen),
 				       numMsgs, newestDate.date, newestDate.time);
 
 				msgBase.close();
 			}
 		}
 	}
+
+	return subIndexes;
 }
 
 //////////////////////////////////////////////
@@ -13532,9 +13683,11 @@ function DigDistMsgReader_updateMsgAreaPageNumInHeader(pPageNum, pNumPages, pGro
 //  pGrpIndex: The index of the message group (assumed to be valid)
 //  pSubIndex: The index of the sub-board within the message group (assumed to be valid)
 //  pHighlight: Boolean - Whether or not to write the line highlighted.
+//  pDisplayNum: Optional - The number to display in the item text, if different from
+//               its index+1
 //
 // Return value: A string with the sub-board information
-function DigDistMsgReader_GetMsgSubBrdLine(pGrpIndex, pSubIndex, pHighlight)
+function DigDistMsgReader_GetMsgSubBoardLine(pGrpIndex, pSubIndex, pHighlight, pDisplayNum)
 {
 	// Determine if pGrpIndex and pSubIndex specify the user's
 	// currently-selected group and sub-board.
@@ -13550,9 +13703,10 @@ function DigDistMsgReader_GetMsgSubBrdLine(pGrpIndex, pSubIndex, pHighlight)
 	var subBoardInfo = getSubBoardInfo(pGrpIndex, pSubIndex, this.msgAreaList_lastImportedMsg_showImportTime);
 	var latestDateStr = strftime("%Y-%m-%d", subBoardInfo.newestTime);
 	var latestTimeStr = strftime("%H:%M:%S", subBoardInfo.newestTime);
+	var displayNum = (pDisplayNum == undefined ? pSubIndex + 1 : pDisplayNum);
 	subBoardStr += (currentSub ? this.colors.areaChooserMsgAreaMarkColor + "*" : " ");
 	subBoardStr += format((pHighlight ? this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr : this.subBoardListPrintfInfo[pGrpIndex].printfStr),
-						  +(pSubIndex+1),
+						  displayNum, //+(pSubIndex+1),
 						  msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].description.substr(0, this.subBoardListPrintfInfo[pGrpIndex].nameLen),
 						  subBoardInfo.numItems, latestDateStr, latestTimeStr);
 	return subBoardStr;
@@ -15492,7 +15646,7 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn, pTopR
 	// Create the user settings box
 	var optBoxTitle = "Setting                                      Enabled";
 	var optBoxWidth = ChoiceScrollbox_MinWidth();
-	var optBoxHeight = 10;
+	var optBoxHeight = 12;
 	if (this.doingNewscan)
 		optBoxHeight += 3;
 	if (this.readingPersonalEmail)
@@ -15538,7 +15692,11 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn, pTopR
 	if (this.userSettings.useIndexedModeForNewscan)
 		optionBox.chgCharInTextItem(INDEXED_MODE_NEWSCAN_OPT_INDEX, checkIdx, CHECK_CHAR);
 
-	// Indexed-mode newscan options
+	const INDEXED_NEWSCAN_ONLY_SHOW_SUBS_WITH_NEW_MSGS_OPT_INDEX = optionBox.addTextItem(format(optionFormatStr, "Indexed newscan: Only show subs w/ new msgs"));
+	if (this.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs)
+		optionBox.chgCharInTextItem(INDEXED_NEWSCAN_ONLY_SHOW_SUBS_WITH_NEW_MSGS_OPT_INDEX, checkIdx, CHECK_CHAR);
+
+	// Some options to show only when doing an indexed-mode newscan
 	var SHOW_INDEXED_NEWSCAN_MENU_IF_NO_NEW_MSGS_OPT_INDEX = -1;
 	var INDEXED_MODE_MENU_SNAP_TO_NEW_MSGS_OPT_INDEX = -1;
 	var INDEXED_MODE_MENU_SNAP_TO_NEW_MSGS_WHEN_MARK_ALL_READ_OPT_IDX = -1;
@@ -15565,10 +15723,6 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn, pTopR
 	if (this.userSettings.quitFromReaderGoesToMsgList)
 		optionBox.chgCharInTextItem(READER_QUIT_TO_MSG_LIST_OPT_INDEX, checkIdx, CHECK_CHAR);
 
-	const PROPMT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_INDEX = optionBox.addTextItem(format(optionFormatStr, "Prompt delete after reply to personal email"));
-	if (this.userSettings.promptDelPersonalEmailAfterReply)
-		optionBox.chgCharInTextItem(PROPMT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_INDEX, checkIdx, CHECK_CHAR);
-
 	// Specific to personal email
 	var DISPLAY_PERSONAL_MAIL_REPLIED_INDICATOR_CHAR_OPT_INDEX = -1;
 	if (this.readingPersonalEmail)
@@ -15578,22 +15732,28 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn, pTopR
 			optionBox.chgCharInTextItem(DISPLAY_PERSONAL_MAIL_REPLIED_INDICATOR_CHAR_OPT_INDEX, checkIdx, CHECK_CHAR);
 	}
 
+	const PROMPT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_INDEX = optionBox.addTextItem(format(optionFormatStr, "Prompt delete after reply to personal email"));
+	if (this.userSettings.promptDelPersonalEmailAfterReply)
+		optionBox.chgCharInTextItem(PROMPT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_INDEX, checkIdx, CHECK_CHAR);
+
 	// Create an object containing toggle values (true/false) for each option index
 	var optionToggles = {};
 	optionToggles[ENH_SCROLLBAR_OPT_INDEX] = this.userSettings.useEnhReaderScrollbar;
 	optionToggles[LIST_MESSAGES_IN_REVERSE_OPT_INDEX] = this.userSettings.listMessagesInReverse;
 	optionToggles[NEWSCAN_ONLY_SHOW_NEW_MSGS_INDEX] = this.userSettings.newscanOnlyShowNewMsgs;
 	optionToggles[INDEXED_MODE_NEWSCAN_OPT_INDEX] = this.userSettings.useIndexedModeForNewscan;
+	optionToggles[INDEXED_NEWSCAN_ONLY_SHOW_SUBS_WITH_NEW_MSGS_OPT_INDEX] = this.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs;
 	optionToggles[SHOW_INDEXED_NEWSCAN_MENU_IF_NO_NEW_MSGS_OPT_INDEX] = this.userSettings.displayIndexedModeMenuIfNoNewMessages;
 	optionToggles[INDEXED_MODE_MENU_SNAP_TO_NEW_MSGS_OPT_INDEX] = this.userSettings.indexedModeMenuSnapToFirstWithNew;
 	optionToggles[INDEXED_MODE_MENU_SNAP_TO_NEW_MSGS_WHEN_MARK_ALL_READ_OPT_IDX] = this.userSettings.indexedModeMenuSnapToNextWithNewAftarMarkAllRead;
 	optionToggles[INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_INDEX] = this.userSettings.enterFromIndexMenuShowsMsgList;
 	optionToggles[READER_QUIT_TO_MSG_LIST_OPT_INDEX] = this.userSettings.quitFromReaderGoesToMsgList;
-	optionToggles[PROPMT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_INDEX] = this.userSettings.promptDelPersonalEmailAfterReply;
+	optionToggles[PROMPT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_INDEX] = this.userSettings.promptDelPersonalEmailAfterReply;
 	optionToggles[DISPLAY_PERSONAL_MAIL_REPLIED_INDICATOR_CHAR_OPT_INDEX] = this.userSettings.displayMsgRepliedChar;
 
 	// Other actions
 	var USER_TWITLIST_OPT_INDEX = optionBox.addTextItem("Personal twit list");
+	var SUB_BOARD_CHANGE_SORTING_OPT_INDEX = optionBox.addTextItem("Sorting for sub-board change");
 
 	// Set up the enter key in the box to toggle the selected item.
 	optionBox.readerObj = this;
@@ -15628,6 +15788,9 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn, pTopR
 					case INDEXED_MODE_NEWSCAN_OPT_INDEX:
 						this.readerObj.userSettings.useIndexedModeForNewscan = !this.readerObj.userSettings.useIndexedModeForNewscan;
 						break;
+					case INDEXED_NEWSCAN_ONLY_SHOW_SUBS_WITH_NEW_MSGS_OPT_INDEX:
+						this.readerObj.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs = !this.readerObj.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs;
+						break;
 					case SHOW_INDEXED_NEWSCAN_MENU_IF_NO_NEW_MSGS_OPT_INDEX:
 						this.readerObj.userSettings.displayIndexedModeMenuIfNoNewMessages = !this.readerObj.userSettings.displayIndexedModeMenuIfNoNewMessages;
 						break;
@@ -15643,7 +15806,7 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn, pTopR
 					case READER_QUIT_TO_MSG_LIST_OPT_INDEX:
 						this.readerObj.userSettings.quitFromReaderGoesToMsgList = !this.readerObj.userSettings.quitFromReaderGoesToMsgList;
 						break;
-					case PROPMT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_INDEX:
+					case PROMPT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_INDEX:
 						this.readerObj.userSettings.promptDelPersonalEmailAfterReply = !this.readerObj.userSettings.promptDelPersonalEmailAfterReply;
 						break;
 					case DISPLAY_PERSONAL_MAIL_REPLIED_INDICATOR_CHAR_OPT_INDEX:
@@ -15671,6 +15834,16 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn, pTopR
 						optionBox.continueInputLoopOverride = false; // Exit the input loop of the option box
 						retObj.needWholeScreenRefresh = true;
 						break;
+					case SUB_BOARD_CHANGE_SORTING_OPT_INDEX:
+						var sortOptMenu = CreateSubBoardChangeSortOptMenu(optBoxStartX, msgBoxTopRow, optBoxWidth, optBoxHeight, this.readerObj.userSettings.subBoardChangeSorting);
+						var chosenSortOpt = sortOptMenu.GetVal();
+						console.attributes = "N";
+						if (typeof(chosenSortOpt) === "number")
+							this.readerObj.userSettings.subBoardChangeSorting = chosenSortOpt;
+						retObj.needWholeScreenRefresh = false;
+						this.drawBorder();
+						this.drawInnerMenu(SUB_BOARD_CHANGE_SORTING_OPT_INDEX);
+						break;
 					default:
 						break;
 				}
@@ -15712,6 +15885,54 @@ function DigDistMsgReader_DoUserSettings_Scrollable(pDrawBottomhelpLineFn, pTopR
 	retObj.optionBoxHeight = optionBox.dimensions.height;
 	return retObj;
 }
+// Helper function for DigDistMsgReader_DoUserSettings_Scrollable(): Creates the
+// menu object to let the user choose a sub-board change sorting option, and returns
+// the object
+function CreateSubBoardChangeSortOptMenu(pX, pY, pWidth, pHeight, pCurrentSortSetting)
+{
+	var sortOptMenu = new DDLightbarMenu(pX, pY, pWidth, pHeight);
+	sortOptMenu.AddAdditionalQuitKeys("qQ");
+	sortOptMenu.borderEnabled = true;
+	sortOptMenu.colors.borderColor = "\x01n\x01b";
+	sortOptMenu.borderChars = {
+		upperLeft: UPPER_LEFT_DOUBLE,
+		upperRight: UPPER_RIGHT_DOUBLE,
+		lowerLeft: LOWER_LEFT_DOUBLE,
+		lowerRight: LOWER_RIGHT_DOUBLE,
+		top: HORIZONTAL_DOUBLE,
+		bottom: HORIZONTAL_DOUBLE,
+		left: VERTICAL_DOUBLE,
+		right: VERTICAL_DOUBLE
+	};
+	sortOptMenu.topBorderText = "Sub-board change sorting";
+	sortOptMenu.Add("None", SUB_BOARD_SORT_NONE);
+	sortOptMenu.Add("Alphabetical", SUB_BOARD_SORT_ALPHABETICAL);
+	sortOptMenu.Add("Msg date: Oldest first", SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST);
+	sortOptMenu.Add("Msg date: Newest first", SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST);
+	switch (pCurrentSortSetting)
+	{
+		case SUB_BOARD_SORT_NONE:
+			sortOptMenu.selectedItemIdx = 0;
+			break;
+		case SUB_BOARD_SORT_ALPHABETICAL:
+			sortOptMenu.selectedItemIdx = 1;
+			break;
+		case SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST:
+			sortOptMenu.selectedItemIdx = 2;
+			break;
+		case SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST:
+			sortOptMenu.selectedItemIdx = 3;
+			break;
+	}
+
+	sortOptMenu.colors.itemColor = "\x01n\x01c\x01h";
+
+	// For use in numbered mode for the traditional UI:
+	sortOptMenu.colors.itemNumColor = "\x01n\x01c";
+	sortOptMenu.colors.highlightedItemNumColor = "\x01n\x01g\x01h";
+
+	return sortOptMenu;
+}
 // For the DigDistMsgReader class:  Lets the user manage their preferences/settings (traditional user interface)
 //
 // Return value: An object containing the following properties:
@@ -15737,11 +15958,14 @@ function DigDistMsgReader_DoUserSettings_Traditional()
 	var LIST_MESSAGES_IN_REVERSE_OPT_NUM = optNum++;
 	var NEWSCAN_ONLY_SHOW_NEW_MSGS_OPT_NUM = optNum++;
 	var USE_INDEXED_MODE_FOR_NEWSCAN_OPT_NUM = optNum++;
+	var INDEXED_NEWSCAN_ONLY_SHOW_SUBS_WITH_NEW_MSGS_OPT_NUM = optNum++;
 	var INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_NUM = optNum++;
 	var READER_QUIT_TO_MSG_LIST_OPT_NUM = optNum++;
 	var PROPMT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_NUM = optNum++;
 	var USER_TWITLIST_OPT_NUM = optNum++;
-	var HIGHEST_CHOICE_NUM = USER_TWITLIST_OPT_NUM; // Highest choice number
+	// Sub-board sorting for changing to another sub-board
+	var SUB_BOARD_CHANGE_SORTING_OPT_NUM = optNum++;
+	var HIGHEST_CHOICE_NUM = SUB_BOARD_CHANGE_SORTING_OPT_NUM; // Highest choice number
 	// Specific to personal email
 	var DISPLAY_PERSONAL_MAIL_REPLIED_INDICATOR_CHAR_OPT_NUM = -1;
 	if (this.readingPersonalEmail)
@@ -15764,10 +15988,12 @@ function DigDistMsgReader_DoUserSettings_Traditional()
 	printTradUserSettingOption(LIST_MESSAGES_IN_REVERSE_OPT_NUM, "List messages in reverse", wordFirstCharAttrs, wordRemainingAttrs);
 	printTradUserSettingOption(NEWSCAN_ONLY_SHOW_NEW_MSGS_OPT_NUM, "Only show new messages for newscan", wordFirstCharAttrs, wordRemainingAttrs);
 	printTradUserSettingOption(USE_INDEXED_MODE_FOR_NEWSCAN_OPT_NUM, "Use Indexed mode for newscan", wordFirstCharAttrs, wordRemainingAttrs);
+	printTradUserSettingOption(INDEXED_NEWSCAN_ONLY_SHOW_SUBS_WITH_NEW_MSGS_OPT_NUM, "Indexed newscan: Only show subs w/ new msgs", wordFirstCharAttrs, wordRemainingAttrs);
 	printTradUserSettingOption(INDEX_NEWSCAN_ENTER_SHOWS_MSG_LIST_OPT_NUM, "Index: Selection shows message list", wordFirstCharAttrs, wordRemainingAttrs);
 	printTradUserSettingOption(READER_QUIT_TO_MSG_LIST_OPT_NUM, "Quitting From reader goes to message list", wordFirstCharAttrs, wordRemainingAttrs);
 	printTradUserSettingOption(PROPMT_DEL_PERSONAL_MSG_AFTER_REPLY_OPT_NUM, "Prompt to delete personal message after replying", wordFirstCharAttrs, wordRemainingAttrs);
 	printTradUserSettingOption(USER_TWITLIST_OPT_NUM, "Personal twit list", wordFirstCharAttrs, wordRemainingAttrs);
+	printTradUserSettingOption(SUB_BOARD_CHANGE_SORTING_OPT_NUM, "Sorting for sub-board change", wordFirstCharAttrs, wordRemainingAttrs);
 	// Specific to personal email
 	if (this.readingPersonalEmail)
 		printTradUserSettingOption(DISPLAY_PERSONAL_MAIL_REPLIED_INDICATOR_CHAR_OPT_NUM, "Display email replied indicator", wordFirstCharAttrs, wordRemainingAttrs);
@@ -15800,6 +16026,11 @@ function DigDistMsgReader_DoUserSettings_Traditional()
 			this.userSettings.useIndexedModeForNewscan = !console.noyes("Use indexed mode for newscan-all");
 			userSettingsChanged = (this.userSettings.useIndexedModeForNewscan != oldIndexedModeNewscanSetting);
 			break;
+		case INDEXED_NEWSCAN_ONLY_SHOW_SUBS_WITH_NEW_MSGS_OPT_NUM:
+			var oldIndexedModeNewscanSubsWithNewMsgsSetting = this.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs;
+			this.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs = !console.noyes("Indexed newscan: Only subs w/ new msgs");
+			userSettingsChanged = (this.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs != oldIndexedModeNewscanSubsWithNewMsgsSetting);
+			break;
 		case SHOW_INDEXED_NEWSCAN_MENU_IF_NO_NEW_MSGS_OPT_NUM:
 			var oldIndexedMenuIfNoMsgsSetting = this.userSettings.displayIndexedModeMenuIfNoNewMessages;
 			this.userSettings.displayIndexedModeMenuIfNoNewMessages = !console.noyes("Show indexed menu if there are no new messages");
@@ -15834,6 +16065,43 @@ function DigDistMsgReader_DoUserSettings_Traditional()
 			retObj.userTwitListChanged = !arraysHaveSameValues(this.userSettings.twitList, oldUserTwitList);
 			retObj.needWholeScreenRefresh = true;
 			break;
+		case SUB_BOARD_CHANGE_SORTING_OPT_NUM:
+			console.attributes = "N";
+			console.crlf();
+			console.print("\x01cChoose a sorting option for sub-board change (\x01hQ\x01n\x01c to quit)");
+			console.crlf();
+			var sortOptMenu = CreateSubBoardChangeSortOptMenu(1, 1, console.screen_columns, console.screen_rows, this.userSettings.subBoardChangeSorting);
+			sortOptMenu.numberedMode = true;
+			sortOptMenu.allowANSI = false;
+			var chosenSortOpt = sortOptMenu.GetVal();
+			if (typeof(chosenSortOpt) === "number")
+			{
+				this.userSettings.subBoardChangeSorting = chosenSortOpt;
+				console.print("\x01n\x01cYou chose\x01g\x01h: \x01c");
+				switch (chosenSortOpt)
+				{
+					case SUB_BOARD_SORT_NONE:
+						console.print("None");
+						break;
+					case SUB_BOARD_SORT_ALPHABETICAL:
+						console.print("Alphabetical");
+						break;
+					case SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST:
+						console.print("Message date (oldest first)");
+						break;
+					case SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST:
+						console.print("Message date (newest first)");
+						break;
+				}
+				console.crlf();
+			}
+			else
+			{
+				console.putmsg(bbs.text(Aborted), P_SAVEATR);
+				console.pause();
+			}
+			console.attributes = "N";
+			break;
 	}
 
 	// If any user settings changed, then write them to the user settings file
@@ -16251,6 +16519,12 @@ function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDi
 
 			++numSubBoards;
 
+			var itemInfo = this.GetIndexedModeSubBoardMenuItemTextAndInfo(msg_area.grp_list[grpIdx].sub_list[subIdx].code);
+			// If configured to only show sub-boards with new messages and this sub-board
+			// does'nt have any new messages, then skip it
+			if (this.userSettings.indexedModeNewscanOnlyShowSubsWithNewMsgs && itemInfo.numNewMsgs == 0)
+				continue;
+
 			if (!grpNameItemAddedToMenu)
 			{
 				//var grpDesc = msg_area.grp_list[grpIdx].name + " - " + msg_area.grp_list[grpIdx].description;
@@ -16272,7 +16546,7 @@ function DigDistMsgReader_IndexedModeChooseSubBoard(pClearScreen, pDrawMenu, pDi
 				grpNameItemAddedToMenu = true;
 			}
 
-			var itemInfo = this.GetIndexedModeSubBoardMenuItemTextAndInfo(msg_area.grp_list[grpIdx].sub_list[subIdx].code);
+			//var itemInfo = this.GetIndexedModeSubBoardMenuItemTextAndInfo(msg_area.grp_list[grpIdx].sub_list[subIdx].code);
 			this.indexedModeMenu.Add(itemInfo.itemText, {
 				subCode: msg_area.grp_list[grpIdx].sub_list[subIdx].code,
 				numNewMsgs: itemInfo.numNewMsgs
@@ -16932,7 +17206,6 @@ function DigDistMsgReader_CreateLightbarIndexedModeMenu(pNumMsgsWidth, pNumNewMs
 	// Add additional keypresses for quitting the menu's input loop so we can
 	// respond to these keys
 	// TODO: Include Mm to allow the user to view the message list instead of read it from the indexed menu
-	//indexedModeMenu.AddAdditionalQuitKeys();
 	for (var key in this.indexedModeMenuKeys)
 	{
 		if (/[a-zA-Z]/.test(this.indexedModeMenuKeys[key]))
@@ -22622,17 +22895,39 @@ function msgHdrHasAttachmentFlag(pMsgHdr)
 //
 // Parameters:
 //  pMsgHdr: The message header
-//  pSubCode: The sub-board code that the message is in
-function allowUserToDownloadMessage_NewInterface(pMsgHdr, pSubCode)
+//  pMsgbaseCode: The internal code for the messagebase that the message is in
+function allowUserToDownloadMessage_NewInterface(pMsgHdr, pMsgbaseCode)
 {
 	if (typeof(bbs.download_msg_attachments) !== "function")
 		return;
-	if (typeof(pSubCode) !== "string")
+	if (typeof(pMsgbaseCode) !== "string")
 		return;
 	if (typeof(pMsgHdr) !== "object" || typeof(pMsgHdr.number) == "undefined")
 		return;
 
-	var msgBase = new MsgBase(pSubCode);
+	// If the user doesn't have a download protocol configured, then allow them
+	// to choose one
+	if (user.download_protocol == "")
+	{
+		// Due to using file.ini, this only works on Synchronet 3.20 and newer
+		if (system.version_num >= 32000)
+		{
+			prompt_user_for_download_protocol();
+			// If the user's download protocol is still blank (user aborted choosing a download protocol),
+			// then just return
+			if (user.download_protocol == "")
+				return;
+		}
+		else
+		{
+			console.print(word_wrap("\x01nYou don't have a default download protocol. You can configure it in your user preferences.\r\n"));
+			console.pause();
+			return;
+		}
+	}
+
+	// Open the messagebase and let the user download the message and/or attachments
+	var msgBase = new MsgBase(pMsgbaseCode);
 	if (msgBase.open())
 	{
 		// bbs.download_msg_attachments() requires a message header returned
diff --git a/xtrn/DDMsgReader/ddmr_cfg.js b/xtrn/DDMsgReader/ddmr_cfg.js
index 098564017bbb2029277b86b70a0bd448777d8198..3c186328ea7e2c855f9b436e582c50096df3e6c2 100644
--- a/xtrn/DDMsgReader/ddmr_cfg.js
+++ b/xtrn/DDMsgReader/ddmr_cfg.js
@@ -5,7 +5,7 @@
 // If you have DDMsgReader in a directory other than xtrn/DDMsgReader, then the changes to
 // DDMsgReader.cfg will be saved in that directory (assuming you're running ddmr_cfg.js from
 // that same directory).
-// Currently for DDMsgReader 1.95h.
+// Currently for DDMsgReader 1.96.
 //
 // If you're running DDMsgReader from xtrn/DDMsgReader (the standard location) and you want
 // to save the configuration file there (rather than sbbs/mods), you can use one of the
@@ -18,7 +18,7 @@ require("sbbsdefs.js", "P_NONE");
 require("uifcdefs.js", "UIFC_INMSG");
 
 
-if (!uifc.init("DigDist. Message Reader 1.95h Configurator"))
+if (!uifc.init("DigDist. Message Reader 1.96 Configurator"))
 {
 	print("Failed to initialize uifc");
 	exit(1);
@@ -117,6 +117,8 @@ function doMainMenu()
 		"indexedModeMenuSnapToNextWithNewAftarMarkAllRead", // Boolean
 		"newscanOnlyShowNewMsgs", // Boolean
 		"promptDelPersonalEmailAfterReply", // Boolean
+		"subBoardChangeSorting", // String: None, Alphabetical, LatestMsgDateOldestFirst, or LatestMsgDateNewestFirst
+		"indexedModeNewscanOnlyShowSubsWithNewMsgs", // Boolean
 		"themeFilename" // String
 	];
 	// Strings for the options to display on the menu
@@ -151,6 +153,8 @@ function doMainMenu()
 		"Index menu mark all read: Snap to subs w/ new msgs",
 		"During a newscan, only show new messages",
 		"Personal email: Prompt to delete after reply",
+		"Sorting for sub-board change",
+		"Index newscan: Only show subs w/ new msgs",
 		"Theme Filename"
 	];
 	// Build an array of formatted string to be displayed on the menu
@@ -266,6 +270,35 @@ function doMainMenu()
 							uifc.msg("That directory doesn't exist!");
 					}
 				}
+				else if (optName == "subBoardChangeSorting")
+				{
+					// Multiple-choice
+					var options = ["None", "Alphabetical", "Msg date: Oldest first", "Msg date: Newest first"];
+					var promptStr = optionStrs[optionMenuSelection];
+					var userChoice = promptMultipleChoice(promptStr, options, gCfgInfo.cfgOptions[optName]);
+					//if (userChoice != null && userChoice != undefined)
+					var userChoiceIdx = options.indexOf(userChoice);
+					if (userChoiceIdx >= 0 && userChoiceIdx < options.length)
+					{
+						switch (userChoiceIdx)
+						{
+							case 0: // None
+								gCfgInfo.cfgOptions[optName] = "None";
+								break;
+							case 1: // Alphabetical
+								gCfgInfo.cfgOptions[optName] = "Alphabetical";
+								break;
+							case 2: // Msg date: Oldest first
+								gCfgInfo.cfgOptions[optName] = "LatestMsgDateOldestFirst";
+								break;
+							case 3: // Msg date: Newest first
+								gCfgInfo.cfgOptions[optName] = "LatestMsgDateNewestFirst";
+								break;
+						}
+						anyOptionChanged = true;
+						menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgOptions[optName]);
+					}
+				}
 				else
 				{
 					// Multiple-choice
@@ -792,6 +825,10 @@ function readDDMsgReaderCfgFile()
 		retObj.cfgOptions.indexedModeMenuSnapToNextWithNewAftarMarkAllRead = true;
 	if (!retObj.cfgOptions.hasOwnProperty("promptDelPersonalEmailAfterReply"))
 		retObj.cfgOptions.promptDelPersonalEmailAfterReply = false;
+	if (!retObj.cfgOptions.hasOwnProperty("subBoardChangeSorting"))
+		retObj.cfgOptions.subBoardChangeSorting = "None";
+	if (!retObj.cfgOptions.hasOwnProperty("indexedModeNewscanOnlyShowSubsWithNewMsgs"))
+		retObj.cfgOptions.indexedModeNewscanOnlyShowSubsWithNewMsgs = false;
 	if (!retObj.cfgOptions.hasOwnProperty("themeFilename"))
 		retObj.cfgOptions.themeFilename = "DefaultTheme.cfg";
 
diff --git a/xtrn/DDMsgReader/readme.txt b/xtrn/DDMsgReader/readme.txt
index 6a68cb259a92514b52a94511ed7afb02e72a8dce..2863d078e0199ab7f180adc82915e784e1a35d27 100644
--- a/xtrn/DDMsgReader/readme.txt
+++ b/xtrn/DDMsgReader/readme.txt
@@ -1,6 +1,6 @@
                       Digital Distortion Message Reader
-                                 Version 1.95h
-                           Release date: 2024-09-03
+                                 Version 1.96
+                           Release date: 2024-10-26
 
                                      by
 
@@ -841,6 +841,18 @@ msgSaveDir                            The default directory on the BBS machine
                                       a fully-pathed filename to save a message
                                       in a different directory.
 
+subBoardChangeSorting                 Sub-board sorting for changing to another
+                                      sub-board: None, Alphabetical,
+                                      LatestMsgDateOldestFirst, or
+                                      LatestMsgDateNewestFirst
+
+indexedModeNewscanOnlyShowSubsWithNewMsgs  For indexed-mode newscan, whether to
+                                           only show sub-boards with new
+                                           messages. This is the default for a
+                                           user setting; users can toggle this
+                                           for themselves as they like. Valid
+                                           values are true or false.
+
 themeFilename                         The name of the configuration file to
                                       use for colors & string settings
 
diff --git a/xtrn/DDMsgReader/revision_history.txt b/xtrn/DDMsgReader/revision_history.txt
index dd31ef905f2e6f52fc2e1adccc0d31e9660ae180..4467c3411ef89bf769eacb38d0f7147f84f3c818 100644
--- a/xtrn/DDMsgReader/revision_history.txt
+++ b/xtrn/DDMsgReader/revision_history.txt
@@ -5,6 +5,28 @@ Revision History (change log)
 =============================
 Version  Date         Description
 -------  ----         -----------
+1.96     2024-10-26   New feature: sub-board sorting for changing sub-boards
+                      (and users can change their sorting option).
+
+                      The new configuration option subBoardChangeSorting
+                      specifies the sub-board sorting (None, Alphabetical,
+                      LatestMsgDateOldestFirst, or LatestMsgDateNewestFirst).
+                      This is a default for a user option that users can change
+                      for themselves.
+
+                      New: Toggleable behavior for whether to show sub-boards
+                      with new messages in the indexed newscan. Users can change
+                      this option for themselves.
+
+                      The new configuration option
+                      indexedModeNewscanOnlyShowSubsWithNewMsgs sets the default
+                      setting for this behavior (true or false).
+
+                      Internal: Updated for bbs.msg_number and bbs.smb_curmsg
+                      being writeable in Synchronet 3.20 - If these can be
+                      changed, there's no need to write the drop file
+                      DDML_SyncSMBInfo.txt containing information about the
+                      message being replied to etc. for message editors to use
 1.95h    2024-09-03   Fix for saving an ANSI message to the local BBS PC
 1.95g    2024-08-12   Updates to help with the newscan issues placing the user
                       at the first message, etc.