diff --git a/indra/integration_tests/llimage_libtest/llimage_libtest.cpp b/indra/integration_tests/llimage_libtest/llimage_libtest.cpp
index 36c5b6782618b63a5c35d2ba0174caa898071712..034c816742b23090990c6446ba125117ae9ca6b0 100644
--- a/indra/integration_tests/llimage_libtest/llimage_libtest.cpp
+++ b/indra/integration_tests/llimage_libtest/llimage_libtest.cpp
@@ -240,7 +240,7 @@ void store_input_file(std::list<std::string> &input_filenames, const std::string
 		LLDirIterator iter(dir, name);
 		while (iter.next(next_name))
 		{
-			std::string file_name = dir + gDirUtilp->getDirDelimiter() + next_name;
+			std::string file_name = gDirUtilp->add(dir, next_name);
 			input_filenames.push_back(file_name);
 		}
 	}
diff --git a/indra/integration_tests/llui_libtest/llui_libtest.cpp b/indra/integration_tests/llui_libtest/llui_libtest.cpp
index 217e26c3ca527380c91fd8bbc1e1d42d3debc83b..38aa1bbeb2a7a4e6f9f2ff497eae96b25c8b4b3c 100644
--- a/indra/integration_tests/llui_libtest/llui_libtest.cpp
+++ b/indra/integration_tests/llui_libtest/llui_libtest.cpp
@@ -107,12 +107,6 @@ public:
 };
 TestImageProvider gTestImageProvider;
 
-static std::string get_xui_dir()
-{
-	std::string delim = gDirUtilp->getDirDelimiter();
-	return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim;
-}
-
 void init_llui()
 {
 	// Font lookup needs directory support
@@ -122,13 +116,12 @@ void init_llui()
 	const char* newview_path = "../../../newview";
 #endif
 	gDirUtilp->initAppDirs("SecondLife", newview_path);
-	gDirUtilp->setSkinFolder("default");
+	gDirUtilp->setSkinFolder("default", "en");
 	
 	// colors are no longer stored in a LLControlGroup file
 	LLUIColorTable::instance().loadFromSettings();
 
-	std::string config_filename = gDirUtilp->getExpandedFilename(
-																 LL_PATH_APP_SETTINGS, "settings.xml");
+	std::string config_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "settings.xml");
 	gSavedSettings.loadFromFile(config_filename);
 	
 	// See LLAppViewer::init()
@@ -143,9 +136,7 @@ void init_llui()
 	
 	const bool no_register_widgets = false;
 	LLWidgetReg::initClass( no_register_widgets );
-	
-	// Unclear if this is needed
-	LLUI::setupPaths();
+
 	// Otherwise we get translation warnings when setting up floaters
 	// (tooltips for buttons)
 	std::set<std::string> default_args;
@@ -157,7 +148,6 @@ void init_llui()
 	// otherwise it crashes.
 	LLFontGL::initClass(96.f, 1.f, 1.f,
 						gDirUtilp->getAppRODataDir(),
-						LLUI::getXUIPaths(),
 						false );	// don't create gl textures
 	
 	LLFloaterView::Params fvparams;
@@ -169,6 +159,14 @@ void init_llui()
 	gFloaterView = LLUICtrlFactory::create<LLFloaterView> (fvparams);
 }
 
+/*==========================================================================*|
+static std::string get_xui_dir()
+{
+	std::string delim = gDirUtilp->getDirDelimiter();
+	return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim;
+}
+
+// buildFromFile() no longer supports generate-output-LLXMLNode
 void export_test_floaters()
 {
 	// Convert all test floaters to new XML format
@@ -191,7 +189,7 @@ void export_test_floaters()
 		floater->buildFromFile(	filename,
 								//	 FALSE,	// don't open floater
 								output_node);
-		std::string out_filename = xui_dir + filename;
+		std::string out_filename = gDirUtilp->add(xui_dir, filename);
 		std::string::size_type extension_pos = out_filename.rfind(".xml");
 		out_filename.resize(extension_pos);
 		out_filename += "_new.xml";
@@ -203,6 +201,7 @@ void export_test_floaters()
 		fclose(floater_file);
 	}
 }
+|*==========================================================================*/
 
 int main(int argc, char** argv)
 {
@@ -211,7 +210,7 @@ int main(int argc, char** argv)
 
 	init_llui();
 	
-	export_test_floaters();
+//	export_test_floaters();
 	
 	return 0;
 }
diff --git a/indra/linux_updater/linux_updater.cpp b/indra/linux_updater/linux_updater.cpp
index 277f0a5367a1718b382b555a6b623382b27b6595..991dfd9dce5693a914d8e7578e774de96a13c926 100644
--- a/indra/linux_updater/linux_updater.cpp
+++ b/indra/linux_updater/linux_updater.cpp
@@ -251,7 +251,7 @@ std::string next_image_filename(std::string& image_path, LLDirIterator& iter)
 {
 	std::string image_filename;
 	iter.next(image_filename);
-	return image_path + "/" + image_filename;
+	return gDirUtilp->add(image_path, image_filename);
 }
 
 void on_window_closed(GtkWidget *sender, GdkEvent* event, gpointer data)
diff --git a/indra/llmath/v3color.h b/indra/llmath/v3color.h
index 56cb2ae73e57be7f0cdce9c1065479a965819cf6..daf3a6857b489c5f01701ebfb9bbb66169b0c4de 100644
--- a/indra/llmath/v3color.h
+++ b/indra/llmath/v3color.h
@@ -33,6 +33,7 @@ class LLVector4;
 #include "llerror.h"
 #include "llmath.h"
 #include "llsd.h"
+#include <string.h>
 
 //  LLColor3 = |r g b|
 
diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp
index 4dc2fcd7146d9e3f60474dcf803f2402de93acc7..647512eb2edff26b935ea737bdf98ef86cfddd10 100644
--- a/indra/llrender/llfontgl.cpp
+++ b/indra/llrender/llfontgl.cpp
@@ -789,7 +789,7 @@ const LLFontDescriptor& LLFontGL::getFontDesc() const
 }
 
 // static
-void LLFontGL::initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, const std::vector<std::string>& xui_paths, bool create_gl_textures)
+void LLFontGL::initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, bool create_gl_textures)
 {
 	sVertDPI = (F32)llfloor(screen_dpi * y_scale);
 	sHorizDPI = (F32)llfloor(screen_dpi * x_scale);
@@ -800,7 +800,7 @@ void LLFontGL::initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::st
 	// Font registry init
 	if (!sFontRegistry)
 	{
-		sFontRegistry = new LLFontRegistry(xui_paths, create_gl_textures);
+		sFontRegistry = new LLFontRegistry(create_gl_textures);
 		sFontRegistry->parseFontInfo("fonts.xml");
 	}
 	else
diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h
index 5ed5d2c4ebe1bce1f51656a5a5f10b8fbdf3371b..0988e99deb37cb1a842475977422a44cd7ddbf1d 100644
--- a/indra/llrender/llfontgl.h
+++ b/indra/llrender/llfontgl.h
@@ -150,7 +150,7 @@ public:
 	const LLFontDescriptor& getFontDesc() const;
 
 
-	static void initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, const std::vector<std::string>& xui_paths, bool create_gl_textures = true);
+	static void initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, bool create_gl_textures = true);
 
 	// Load sans-serif, sans-serif-small, etc.
 	// Slow, requires multiple seconds to load fonts.
diff --git a/indra/llrender/llfontregistry.cpp b/indra/llrender/llfontregistry.cpp
index 4d22eba3d97163f2615f6c10c2a3b7b7bf6cd04b..b5bdba996ffeb4ea2a558c3574a5fe99cfd528f4 100644
--- a/indra/llrender/llfontregistry.cpp
+++ b/indra/llrender/llfontregistry.cpp
@@ -163,14 +163,9 @@ LLFontDescriptor LLFontDescriptor::normalize() const
 	return LLFontDescriptor(new_name,new_size,new_style,getFileNames());
 }
 
-LLFontRegistry::LLFontRegistry(const string_vec_t& xui_paths,
-							   bool create_gl_textures)
+LLFontRegistry::LLFontRegistry(bool create_gl_textures)
 :	mCreateGLTextures(create_gl_textures)
 {
-	// Propagate this down from LLUICtrlFactory so LLRender doesn't
-	// need an upstream dependency on LLUI.
-	mXUIPaths = xui_paths;
-	
 	// This is potentially a slow directory traversal, so we want to
 	// cache the result.
 	mUltimateFallbackList = LLWindow::getDynamicFallbackFontList();
@@ -183,27 +178,30 @@ LLFontRegistry::~LLFontRegistry()
 
 bool LLFontRegistry::parseFontInfo(const std::string& xml_filename)
 {
-	bool success = false;  // Succeed if we find at least one XUI file
-	const string_vec_t& xml_paths = mXUIPaths;
+	bool success = false;  // Succeed if we find and read at least one XUI file
+	const string_vec_t xml_paths = gDirUtilp->findSkinnedFilenames(LLDir::XUI, xml_filename);
+	if (xml_paths.empty())
+	{
+		// We didn't even find one single XUI file
+		return false;
+	}
+
 	for (string_vec_t::const_iterator path_it = xml_paths.begin();
 		 path_it != xml_paths.end();
 		 ++path_it)
 	{
-	
 		LLXMLNodePtr root;
-		std::string full_filename = gDirUtilp->findSkinnedFilename(*path_it, xml_filename);
-		bool parsed_file = LLXMLNode::parseFile(full_filename, root, NULL);
+		bool parsed_file = LLXMLNode::parseFile(*path_it, root, NULL);
 
 		if (!parsed_file)
 			continue;
-		
+
 		if ( root.isNull() || ! root->hasName( "fonts" ) )
 		{
-			llwarns << "Bad font info file: "
-					<< full_filename << llendl;
+			llwarns << "Bad font info file: " << *path_it << llendl;
 			continue;
 		}
-		
+
 		std::string root_name;
 		root->getAttributeString("name",root_name);
 		if (root->hasName("fonts"))
@@ -215,7 +213,7 @@ bool LLFontRegistry::parseFontInfo(const std::string& xml_filename)
 	}
 	//if (success)
 	//	dump();
-	
+
 	return success;
 }
 
diff --git a/indra/llrender/llfontregistry.h b/indra/llrender/llfontregistry.h
index 8b06191c564b14ffde3ba5d105fc4bf3d9b88680..059248fbbdb333f51dc3392e7a2fde1c3c0cb9ad 100644
--- a/indra/llrender/llfontregistry.h
+++ b/indra/llrender/llfontregistry.h
@@ -67,8 +67,7 @@ class LLFontRegistry
 public:
 	// create_gl_textures - set to false for test apps with no OpenGL window,
 	// such as llui_libtest
-	LLFontRegistry(const string_vec_t& xui_paths,
-		bool create_gl_textures);
+	LLFontRegistry(bool create_gl_textures);
 	~LLFontRegistry();
 
 	// Load standard font info from XML file(s).
@@ -105,7 +104,6 @@ private:
 	font_size_map_t mFontSizes;
 
 	string_vec_t mUltimateFallbackList;
-	string_vec_t mXUIPaths;
 	bool mCreateGLTextures;
 };
 
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index 90251ac7c657b77036d6db1f06c90ece4a60c954..054b9173d37a344f474adce16fab2026cdefbd0f 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -3233,24 +3233,14 @@ bool LLFloater::isVisible(const LLFloater* floater)
 
 static LLFastTimer::DeclareTimer FTM_BUILD_FLOATERS("Build Floaters");
 
-bool LLFloater::buildFromFile(const std::string& filename, LLXMLNodePtr output_node)
+bool LLFloater::buildFromFile(const std::string& filename)
 {
 	LLFastTimer timer(FTM_BUILD_FLOATERS);
 	LLXMLNodePtr root;
 
-	//if exporting, only load the language being exported, 
-	//instead of layering localized version on top of english
-	if (output_node)
-	{
-		if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root))
-		{
-			llwarns << "Couldn't parse floater from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl;
-			return false;
-		}
-	}
-	else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
+	if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
 	{
-		llwarns << "Couldn't parse floater from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl;
+		llwarns << "Couldn't find (or parse) floater from: " << filename << llendl;
 		return false;
 	}
 	
@@ -3275,7 +3265,7 @@ bool LLFloater::buildFromFile(const std::string& filename, LLXMLNodePtr output_n
 		getCommitCallbackRegistrar().pushScope();
 		getEnableCallbackRegistrar().pushScope();
 		
-		res = initFloaterXML(root, getParent(), filename, output_node);
+		res = initFloaterXML(root, getParent(), filename, NULL);
 
 		setXMLFilename(filename);
 		
diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h
index 64d6dcea0444afeffcd7de5758e1a40fe3953884..e64b6d04d3f914daa8a066c8e74adb754e5eceba 100644
--- a/indra/llui/llfloater.h
+++ b/indra/llui/llfloater.h
@@ -202,7 +202,7 @@ public:
 
 	// Don't export top/left for rect, only height/width
 	static void setupParamsForExport(Params& p, LLView* parent);
-	bool buildFromFile(const std::string &filename, LLXMLNodePtr output_node = NULL);
+	bool buildFromFile(const std::string &filename);
 
 	boost::signals2::connection setMinimizeCallback( const commit_signal_t::slot_type& cb );
 	boost::signals2::connection setOpenCallback( const commit_signal_t::slot_type& cb );
diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp
index 9115eb71740760af6522fb80f6170b31f46b595a..306caf2b91be82137fa44d6589cae5dbbe754d4a 100644
--- a/indra/llui/llfloaterreg.cpp
+++ b/indra/llui/llfloaterreg.cpp
@@ -154,7 +154,7 @@ LLFloater* LLFloaterReg::getInstance(const std::string& name, const LLSD& key)
 					llwarns << "Failed to build floater type: '" << name << "'." << llendl;
 					return NULL;
 				}
-				bool success = res->buildFromFile(xui_file, NULL);
+				bool success = res->buildFromFile(xui_file);
 				if (!success)
 				{
 					llwarns << "Failed to build floater type: '" << name << "'." << llendl;
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
index 629eef2c3bc024c8d0236551026395427825c74b..210a320f41d79ce902209358202a3c44d549b9ae 100644
--- a/indra/llui/llnotifications.cpp
+++ b/indra/llui/llnotifications.cpp
@@ -1424,25 +1424,19 @@ void addPathIfExists(const std::string& new_path, std::vector<std::string>& path
 bool LLNotifications::loadTemplates()
 {
 	llinfos << "Reading notifications template" << llendl;
-	std::vector<std::string> search_paths;
-	
-	std::string skin_relative_path = gDirUtilp->getDirDelimiter() + LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + "notifications.xml";
-	std::string localized_skin_relative_path = gDirUtilp->getDirDelimiter() + LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + "notifications.xml";
-
-	addPathIfExists(gDirUtilp->getDefaultSkinDir() + skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getDefaultSkinDir() + localized_skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getSkinDir() + skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getSkinDir() + localized_skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getUserSkinDir() + skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getUserSkinDir() + localized_skin_relative_path, search_paths);
+	// Passing findSkinnedFilenames(constraint=LLDir::ALL_SKINS) makes it
+	// output all relevant pathnames instead of just the ones from the most
+	// specific skin.
+	std::vector<std::string> search_paths =
+		gDirUtilp->findSkinnedFilenames(LLDir::XUI, "notifications.xml", LLDir::ALL_SKINS);
 
 	std::string base_filename = search_paths.front();
 	LLXMLNodePtr root;
 	BOOL success  = LLXMLNode::getLayeredXMLNode(root, search_paths);
-	
+
 	if (!success || root.isNull() || !root->hasName( "notifications" ))
 	{
-		llerrs << "Problem reading UI Notifications file: " << base_filename << llendl;
+		llerrs << "Problem reading XML from UI Notifications file: " << base_filename << llendl;
 		return false;
 	}
 
@@ -1452,7 +1446,7 @@ bool LLNotifications::loadTemplates()
 
 	if(!params.validateBlock())
 	{
-		llerrs << "Problem reading UI Notifications file: " << base_filename << llendl;
+		llerrs << "Problem reading XUI from UI Notifications file: " << base_filename << llendl;
 		return false;
 	}
 
@@ -1508,7 +1502,9 @@ bool LLNotifications::loadTemplates()
 bool LLNotifications::loadVisibilityRules()
 {
 	const std::string xml_filename = "notification_visibility.xml";
-	std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getXUIPaths().front(), xml_filename);
+	// Note that here we're looking for the "en" version, the default
+	// language, rather than the most localized version of this file.
+	std::string full_filename = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, xml_filename);
 
 	LLNotificationVisibilityRule::Rules params;
 	LLSimpleXUIParser parser;
diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp
index 00318cec6b0b3043863eae2754ad9565b0ae1c38..67472ad1666f1a31e1e85752213176816ea07ff5 100644
--- a/indra/llui/llpanel.cpp
+++ b/indra/llui/llpanel.cpp
@@ -968,25 +968,15 @@ static LLFastTimer::DeclareTimer FTM_BUILD_PANELS("Build Panels");
 //-----------------------------------------------------------------------------
 // buildPanel()
 //-----------------------------------------------------------------------------
-BOOL LLPanel::buildFromFile(const std::string& filename, LLXMLNodePtr output_node, const LLPanel::Params& default_params)
+BOOL LLPanel::buildFromFile(const std::string& filename, const LLPanel::Params& default_params)
 {
 	LLFastTimer timer(FTM_BUILD_PANELS);
 	BOOL didPost = FALSE;
 	LLXMLNodePtr root;
 
-	//if exporting, only load the language being exported, 
-	//instead of layering localized version on top of english
-	if (output_node)
-	{	
-		if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root))
-		{
-			llwarns << "Couldn't parse panel from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename  << llendl;
-			return didPost;
-		}
-	}
-	else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
+	if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
 	{
-		llwarns << "Couldn't parse panel from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl;
+		llwarns << "Couldn't parse panel from: " << filename << llendl;
 		return didPost;
 	}
 
@@ -1010,7 +1000,7 @@ BOOL LLPanel::buildFromFile(const std::string& filename, LLXMLNodePtr output_nod
 		getCommitCallbackRegistrar().pushScope();
 		getEnableCallbackRegistrar().pushScope();
 		
-		didPost = initPanelXML(root, NULL, output_node, default_params);
+		didPost = initPanelXML(root, NULL, NULL, default_params);
 
 		getCommitCallbackRegistrar().popScope();
 		getEnableCallbackRegistrar().popScope();
diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h
index f62020102022ce71c9e729012bfa1180be8a0f2c..e63b41f97c56121288d418204658dace49e273ec 100644
--- a/indra/llui/llpanel.h
+++ b/indra/llui/llpanel.h
@@ -105,7 +105,7 @@ protected:
 	LLPanel(const LLPanel::Params& params = getDefaultParams());
 	
 public:
-	BOOL buildFromFile(const std::string &filename, LLXMLNodePtr output_node = NULL, const LLPanel::Params&default_params = getDefaultParams());
+	BOOL buildFromFile(const std::string &filename, const LLPanel::Params& default_params = getDefaultParams());
 
 	static LLPanel* createFactoryPanel(const std::string& name);
 
diff --git a/indra/llui/lltransutil.cpp b/indra/llui/lltransutil.cpp
index 58fa8a0828245a856e4f5730f317a192824acd65..80d079cbc8ca3c0781976841fc79172355c9f007 100644
--- a/indra/llui/lltransutil.cpp
+++ b/indra/llui/lltransutil.cpp
@@ -31,15 +31,20 @@
 #include "lltrans.h"
 #include "lluictrlfactory.h"
 #include "llxmlnode.h"
-
+#include "lldir.h"
 
 bool LLTransUtil::parseStrings(const std::string& xml_filename, const std::set<std::string>& default_args)
 {
 	LLXMLNodePtr root;
-	BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root);
+	// Pass LLDir::ALL_SKINS to load a composite of all the individual string
+	// definitions in the default skin and the current skin. This means an
+	// individual skin can provide an xml_filename that overrides only a
+	// subset of the available string definitions; any string definition not
+	// overridden by that skin will be sought in the default skin.
+	bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root, LLDir::ALL_SKINS);
 	if (!success)
 	{
-		llerrs << "Couldn't load string table" << llendl;
+		llerrs << "Couldn't load string table " << xml_filename << llendl;
 		return false;
 	}
 
@@ -54,7 +59,7 @@ bool LLTransUtil::parseLanguageStrings(const std::string& xml_filename)
 	
 	if (!success)
 	{
-		llerrs << "Couldn't load string table " << xml_filename << llendl;
+		llerrs << "Couldn't load localization table " << xml_filename << llendl;
 		return false;
 	}
 	
diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp
index 87bf518aa17ca6c46b891627f344bc57eb9b7f7f..6d2bc1837cee68b895c284830e45bbbcbf600b28 100644
--- a/indra/llui/llui.cpp
+++ b/indra/llui/llui.cpp
@@ -1836,88 +1836,37 @@ struct Paths : public LLInitParam::Block<Paths>
 	{}
 };
 
-//static
-void LLUI::setupPaths()
-{
-	std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "paths.xml");
-
-	LLXMLNodePtr root;
-	BOOL success  = LLXMLNode::parseFile(filename, root, NULL);
-	Paths paths;
-
-	if(success)
-	{
-		LLXUIParser parser;
-		parser.readXUI(root, paths, filename);
-	}
-	sXUIPaths.clear();
-	
-	if (success && paths.validateBlock())
-	{
-		LLStringUtil::format_map_t path_args;
-		path_args["[LANGUAGE]"] = LLUI::getLanguage();
-		
-		for (LLInitParam::ParamIterator<Directory>::const_iterator it = paths.directories.begin(), 
-				end_it = paths.directories.end();
-			it != end_it;
-			++it)
-		{
-			std::string path_val_ui;
-			for (LLInitParam::ParamIterator<SubDir>::const_iterator subdir_it = it->subdirs.begin(),
-					subdir_end_it = it->subdirs.end();
-				subdir_it != subdir_end_it;)
-			{
-				path_val_ui += subdir_it->value();
-				if (++subdir_it != subdir_end_it)
-					path_val_ui += gDirUtilp->getDirDelimiter();
-			}
-			LLStringUtil::format(path_val_ui, path_args);
-			if (std::find(sXUIPaths.begin(), sXUIPaths.end(), path_val_ui) == sXUIPaths.end())
-			{
-				sXUIPaths.push_back(path_val_ui);
-			}
-
-		}
-	}
-	else // parsing failed
-	{
-		std::string slash = gDirUtilp->getDirDelimiter();
-		std::string dir = "xui" + slash + "en";
-		llwarns << "XUI::config file unable to open: " << filename << llendl;
-		sXUIPaths.push_back(dir);
-	}
-}
-
 
 //static
 std::string LLUI::locateSkin(const std::string& filename)
 {
-	std::string slash = gDirUtilp->getDirDelimiter();
 	std::string found_file = filename;
-	if (!gDirUtilp->fileExists(found_file))
+	if (gDirUtilp->fileExists(found_file))
 	{
-		found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS?
+		return found_file;
 	}
-	if (sSettingGroups["config"] && sSettingGroups["config"]->controlExists("Language"))
+
+	found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS?
+	if (gDirUtilp->fileExists(found_file))
 	{
-		if (!gDirUtilp->fileExists(found_file))
-		{
-			std::string localization = getLanguage();
-			std::string local_skin = "xui" + slash + localization + slash + filename;
-			found_file = gDirUtilp->findSkinnedFilename(local_skin);
-		}
+		return found_file;
 	}
-	if (!gDirUtilp->fileExists(found_file))
+
+	found_file = gDirUtilp->findSkinnedFilename(LLDir::XUI, filename);
+	if (! found_file.empty())
 	{
-		std::string local_skin = "xui" + slash + "en" + slash + filename;
-		found_file = gDirUtilp->findSkinnedFilename(local_skin);
+		return found_file;
 	}
-	if (!gDirUtilp->fileExists(found_file))
+
+	found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename);
+	if (gDirUtilp->fileExists(found_file))
 	{
-		found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename);
+		return found_file;
 	}
-	return found_file;
-}	
+	LL_WARNS("LLUI") << "Can't find '" << filename
+					 << "' in user settings, any skin directory or app_settings" << LL_ENDL;
+	return "";
+}
 
 //static
 LLVector2 LLUI::getWindowSize()
diff --git a/indra/llui/llui.h b/indra/llui/llui.h
index 28e84fa44410e55174ea478d1600f1786cc631c3..c5a12d2b315ec4febfc42468ac23f59d593b4b8f 100644
--- a/indra/llui/llui.h
+++ b/indra/llui/llui.h
@@ -292,11 +292,6 @@ public:
 	// Return the ISO639 language name ("en", "ko", etc.) for the viewer UI.
 	// http://www.loc.gov/standards/iso639-2/php/code_list.php
 	static std::string getLanguage();
-	
-	static void setupPaths();
-	static const std::vector<std::string>& getXUIPaths() { return sXUIPaths; }
-	static std::string getSkinPath() { return sXUIPaths.front(); }
-	static std::string getLocalizedSkinPath() { return sXUIPaths.back(); }  //all files may not exist at the localized path
 
 	//helper functions (should probably move free standing rendering helper functions here)
 	static LLView* getRootView() { return sRootView; }
diff --git a/indra/llui/lluicolortable.cpp b/indra/llui/lluicolortable.cpp
index 9455d09cc0c0d5521279930273575004e04add5c..ffeff15968391ec2ce985791abd0299586fe1e9d 100644
--- a/indra/llui/lluicolortable.cpp
+++ b/indra/llui/lluicolortable.cpp
@@ -32,6 +32,7 @@
 #include "llui.h"
 #include "lluicolortable.h"
 #include "lluictrlfactory.h"
+#include <boost/foreach.hpp>
 
 LLUIColorTable::ColorParams::ColorParams()
 :	value("value"),
@@ -206,19 +207,12 @@ bool LLUIColorTable::loadFromSettings()
 {
 	bool result = false;
 
-	std::string default_filename = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "colors.xml");
-	result |= loadFromFilename(default_filename, mLoadedColors);
-
-	std::string current_filename = gDirUtilp->getExpandedFilename(LL_PATH_TOP_SKIN, "colors.xml");
-	if(current_filename != default_filename)
-	{
-		result |= loadFromFilename(current_filename, mLoadedColors);
-	}
-
-	current_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SKIN, "colors.xml");
-	if(current_filename != default_filename)
+	// pass constraint=LLDir::ALL_SKINS because we want colors.xml from every
+	// skin dir
+	BOOST_FOREACH(std::string colors_path,
+				  gDirUtilp->findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS))
 	{
-		result |= loadFromFilename(current_filename, mLoadedColors);
+		result |= loadFromFilename(colors_path, mLoadedColors);
 	}
 
 	std::string user_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "colors.xml");
diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp
index 25e7a31e9073de346c6a1bc43f2d37d1925d1b47..bd06476936a3e25b324942b7516768fb9118e0cd 100644
--- a/indra/llui/lluictrlfactory.cpp
+++ b/indra/llui/lluictrlfactory.cpp
@@ -90,10 +90,12 @@ LLUICtrlFactory::~LLUICtrlFactory()
 
 void LLUICtrlFactory::loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block)
 {
-	std::string filename = std::string("widgets") + gDirUtilp->getDirDelimiter() + widget_tag + ".xml";
+	std::string filename = gDirUtilp->add("widgets", widget_tag + ".xml");
 	LLXMLNodePtr root_node;
 
-	std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getXUIPaths().front(), filename);
+	// Here we're looking for the "en" version, the default-language version
+	// of the file, rather than the localized version.
+	std::string full_filename = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, filename);
 	if (!full_filename.empty())
 	{
 		LLUICtrlFactory::instance().pushFileName(full_filename);
@@ -149,22 +151,12 @@ static LLFastTimer::DeclareTimer FTM_XML_PARSE("XML Reading/Parsing");
 //-----------------------------------------------------------------------------
 // getLayeredXMLNode()
 //-----------------------------------------------------------------------------
-bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root)
+bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root,
+                                        LLDir::ESkinConstraint constraint)
 {
 	LLFastTimer timer(FTM_XML_PARSE);
-	
-	std::vector<std::string> paths;
-	std::string path = gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), xui_filename);
-	if (!path.empty())
-	{
-		paths.push_back(path);
-	}
-
-	std::string localize_path = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), xui_filename);
-	if (!localize_path.empty() && localize_path != path)
-	{
-		paths.push_back(localize_path);
-	}
+	std::vector<std::string> paths =
+		gDirUtilp->findSkinnedFilenames(LLDir::XUI, xui_filename, constraint);
 
 	if (paths.empty())
 	{
@@ -176,23 +168,6 @@ bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNo
 }
 
 
-//-----------------------------------------------------------------------------
-// getLocalizedXMLNode()
-//-----------------------------------------------------------------------------
-bool LLUICtrlFactory::getLocalizedXMLNode(const std::string &xui_filename, LLXMLNodePtr& root)
-{
-	LLFastTimer timer(FTM_XML_PARSE);
-	std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), xui_filename);
-	if (!LLXMLNode::parseFile(full_filename, root, NULL))
-	{
-		return false;
-	}
-	else
-	{
-		return true;
-	}
-}
-
 //-----------------------------------------------------------------------------
 // saveToXML()
 //-----------------------------------------------------------------------------
@@ -239,8 +214,10 @@ std::string LLUICtrlFactory::getCurFileName()
 
 
 void LLUICtrlFactory::pushFileName(const std::string& name) 
-{ 
-	mFileNames.push_back(gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), name)); 
+{
+	// Here we seem to be looking for the default language file ("en") rather
+	// than the localized one, if any.
+	mFileNames.push_back(gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, name));
 }
 
 void LLUICtrlFactory::popFileName() 
@@ -255,14 +232,6 @@ void LLUICtrlFactory::setCtrlParent(LLView* view, LLView* parent, S32 tab_group)
 	parent->addChild(view, tab_group);
 }
 
-
-// Avoid directly using LLUI and LLDir in the template code
-//static
-std::string LLUICtrlFactory::findSkinnedFilename(const std::string& filename)
-{
-	return gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), filename);
-}
-
 //static 
 void LLUICtrlFactory::copyName(LLXMLNodePtr src, LLXMLNodePtr dest)
 {
diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h
index 4e54354731e57fe76ef1afa9981cadc4e3ab4bc5..f6971261d714403bdff98d3d60100d159335e11a 100644
--- a/indra/llui/lluictrlfactory.h
+++ b/indra/llui/lluictrlfactory.h
@@ -32,6 +32,7 @@
 #include "llregistry.h"
 #include "llxuiparser.h"
 #include "llstl.h"
+#include "lldir.h"
 
 class LLView;
 
@@ -161,32 +162,21 @@ public:
 	LLView* createFromXML(LLXMLNodePtr node, LLView* parent, const std::string& filename, const widget_registry_t&, LLXMLNodePtr output_node );
 
 	template<typename T>
-	static T* createFromFile(const std::string &filename, LLView *parent, const widget_registry_t& registry, LLXMLNodePtr output_node = NULL)
+	static T* createFromFile(const std::string &filename, LLView *parent, const widget_registry_t& registry)
 	{
 		T* widget = NULL;
-		
-		std::string skinned_filename = findSkinnedFilename(filename);
+
 		instance().pushFileName(filename);
 		{
 			LLXMLNodePtr root_node;
 
-			//if exporting, only load the language being exported, 			
-			//instead of layering localized version on top of english			
-			if (output_node)			
-			{					
-				if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root_node))				
-				{							
-					llwarns << "Couldn't parse XUI file: " <<  filename  << llendl;					
-					goto fail;				
-				}
-			}
-			else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root_node))
+			if (!LLUICtrlFactory::getLayeredXMLNode(filename, root_node))
 			{
-				llwarns << "Couldn't parse XUI file: " << skinned_filename << llendl;
+				llwarns << "Couldn't parse XUI file: " << instance().getCurFileName() << llendl;
 				goto fail;
 			}
-			
-			LLView* view = getInstance()->createFromXML(root_node, parent, filename, registry, output_node);
+
+			LLView* view = getInstance()->createFromXML(root_node, parent, filename, registry, NULL);
 			if (view)
 			{
 				widget = dynamic_cast<T*>(view);
@@ -214,8 +204,8 @@ fail:
 
 	static void createChildren(LLView* viewp, LLXMLNodePtr node, const widget_registry_t&, LLXMLNodePtr output_node = NULL);
 
-	static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root);
-	static bool getLocalizedXMLNode(const std::string &xui_filename, LLXMLNodePtr& root);
+	static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root,
+								  LLDir::ESkinConstraint constraint=LLDir::CURRENT_SKIN);
 
 private:
 	//NOTE: both friend declarations are necessary to keep both gcc and msvc happy
@@ -300,9 +290,6 @@ private:
 	// this exists to get around dependency on llview
 	static void setCtrlParent(LLView* view, LLView* parent, S32 tab_group);
 
-	// Avoid directly using LLUI and LLDir in the template code
-	static std::string findSkinnedFilename(const std::string& filename);
-
 	class LLPanel*		mDummyPanel;
 	std::vector<std::string>	mFileNames;
 };
diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp
index 32d081d5524352301dab83357c12bedd7283ace9..5e5aeefba179653f1f9738b8c66fe34b66771cae 100644
--- a/indra/llvfs/lldir.cpp
+++ b/indra/llvfs/lldir.cpp
@@ -41,6 +41,17 @@
 #include "lluuid.h"
 
 #include "lldiriterator.h"
+#include "stringize.h"
+#include <boost/foreach.hpp>
+#include <boost/range/begin.hpp>
+#include <boost/range/end.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/bind.hpp>
+#include <boost/ref.hpp>
+#include <algorithm>
+
+using boost::assign::list_of;
+using boost::assign::map_list_of;
 
 #if LL_WINDOWS
 #include "lldir_win32.h"
@@ -58,6 +69,14 @@ LLDir_Linux gDirUtil;
 
 LLDir *gDirUtilp = (LLDir *)&gDirUtil;
 
+/// Values for findSkinnedFilenames(subdir) parameter
+const char
+	*LLDir::XUI      = "xui",
+	*LLDir::TEXTURES = "textures",
+	*LLDir::SKINBASE = "";
+
+static const char* const empty = "";
+
 LLDir::LLDir()
 :	mAppName(""),
 	mExecutablePathAndName(""),
@@ -70,7 +89,8 @@ LLDir::LLDir()
 	mOSCacheDir(""),
 	mCAFile(""),
 	mTempDir(""),
-	mDirDelimiter("/") // fallback to forward slash if not overridden
+	mDirDelimiter("/"), // fallback to forward slash if not overridden
+	mLanguage("en")
 {
 }
 
@@ -96,9 +116,7 @@ S32 LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask)
 	LLDirIterator iter(dirname, mask);
 	while (iter.next(filename))
 	{
-		fullpath = dirname;
-		fullpath += getDirDelimiter();
-		fullpath += filename;
+		fullpath = add(dirname, filename);
 
 		if(LLFile::isdir(fullpath))
 		{
@@ -270,12 +288,12 @@ std::string LLDir::buildSLOSCacheDir() const
 		}
 		else
 		{
-			res = getOSUserAppDir() + mDirDelimiter + "cache";
+			res = add(getOSUserAppDir(), "cache");
 		}
 	}
 	else
 	{
-		res = getOSCacheDir() + mDirDelimiter + "SecondLife";
+		res = add(getOSCacheDir(), "SecondLife");
 	}
 	return res;
 }
@@ -298,19 +316,24 @@ const std::string &LLDir::getDirDelimiter() const
 	return mDirDelimiter;
 }
 
+const std::string& LLDir::getDefaultSkinDir() const
+{
+	return mDefaultSkinDir;
+}
+
 const std::string &LLDir::getSkinDir() const
 {
 	return mSkinDir;
 }
 
-const std::string &LLDir::getUserSkinDir() const
+const std::string &LLDir::getUserDefaultSkinDir() const
 {
-	return mUserSkinDir;
+    return mUserDefaultSkinDir;
 }
 
-const std::string& LLDir::getDefaultSkinDir() const
+const std::string &LLDir::getUserSkinDir() const
 {
-	return mDefaultSkinDir;
+	return mUserSkinDir;
 }
 
 const std::string LLDir::getSkinBaseDir() const
@@ -323,6 +346,39 @@ const std::string &LLDir::getLLPluginDir() const
 	return mLLPluginDir;
 }
 
+static std::string ELLPathToString(ELLPath location)
+{
+	typedef std::map<ELLPath, const char*> ELLPathMap;
+#define ENT(symbol) (symbol, #symbol)
+	static const ELLPathMap sMap = map_list_of
+		ENT(LL_PATH_NONE)
+		ENT(LL_PATH_USER_SETTINGS)
+		ENT(LL_PATH_APP_SETTINGS)
+		ENT(LL_PATH_PER_SL_ACCOUNT) // returns/expands to blank string if we don't know the account name yet
+		ENT(LL_PATH_CACHE)
+		ENT(LL_PATH_CHARACTER)
+		ENT(LL_PATH_HELP)
+		ENT(LL_PATH_LOGS)
+		ENT(LL_PATH_TEMP)
+		ENT(LL_PATH_SKINS)
+		ENT(LL_PATH_TOP_SKIN)
+		ENT(LL_PATH_CHAT_LOGS)
+		ENT(LL_PATH_PER_ACCOUNT_CHAT_LOGS)
+		ENT(LL_PATH_USER_SKIN)
+		ENT(LL_PATH_LOCAL_ASSETS)
+		ENT(LL_PATH_EXECUTABLE)
+		ENT(LL_PATH_DEFAULT_SKIN)
+		ENT(LL_PATH_FONTS)
+		ENT(LL_PATH_LAST)
+	;
+#undef ENT
+
+	ELLPathMap::const_iterator found = sMap.find(location);
+	if (found != sMap.end())
+		return found->second;
+	return STRINGIZE("Invalid ELLPath value " << location);
+}
+
 std::string LLDir::getExpandedFilename(ELLPath location, const std::string& filename) const
 {
 	return getExpandedFilename(location, "", filename);
@@ -343,15 +399,11 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd
 		break;
 
 	case LL_PATH_APP_SETTINGS:
-		prefix = getAppRODataDir();
-		prefix += mDirDelimiter;
-		prefix += "app_settings";
+		prefix = add(getAppRODataDir(), "app_settings");
 		break;
 	
 	case LL_PATH_CHARACTER:
-		prefix = getAppRODataDir();
-		prefix += mDirDelimiter;
-		prefix += "character";
+		prefix = add(getAppRODataDir(), "character");
 		break;
 		
 	case LL_PATH_HELP:
@@ -363,16 +415,22 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd
 		break;
 		
 	case LL_PATH_USER_SETTINGS:
-		prefix = getOSUserAppDir();
-		prefix += mDirDelimiter;
-		prefix += "user_settings";
+		prefix = add(getOSUserAppDir(), "user_settings");
 		break;
 
 	case LL_PATH_PER_SL_ACCOUNT:
 		prefix = getLindenUserDir();
 		if (prefix.empty())
 		{
-			// if we're asking for the per-SL-account directory but we haven't logged in yet (or otherwise don't know the account name from which to build this string), then intentionally return a blank string to the caller and skip the below warning about a blank prefix.
+			// if we're asking for the per-SL-account directory but we haven't
+			// logged in yet (or otherwise don't know the account name from
+			// which to build this string), then intentionally return a blank
+			// string to the caller and skip the below warning about a blank
+			// prefix.
+			LL_DEBUGS("LLDir") << "getLindenUserDir() not yet set: "
+							   << ELLPathToString(location)
+							   << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
+							   << "' => ''" << LL_ENDL;
 			return std::string();
 		}
 		break;
@@ -386,9 +444,7 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd
 		break;
 
 	case LL_PATH_LOGS:
-		prefix = getOSUserAppDir();
-		prefix += mDirDelimiter;
-		prefix += "logs";
+		prefix = add(getOSUserAppDir(), "logs");
 		break;
 
 	case LL_PATH_TEMP:
@@ -412,9 +468,7 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd
 		break;
 
 	case LL_PATH_LOCAL_ASSETS:
-		prefix = getAppRODataDir();
-		prefix += mDirDelimiter;
-		prefix += "local_assets";
+		prefix = add(getAppRODataDir(), "local_assets");
 		break;
 
 	case LL_PATH_EXECUTABLE:
@@ -422,56 +476,36 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd
 		break;
 		
 	case LL_PATH_FONTS:
-		prefix = getAppRODataDir();
-		prefix += mDirDelimiter;
-		prefix += "fonts";
+		prefix = add(getAppRODataDir(), "fonts");
 		break;
 		
 	default:
 		llassert(0);
 	}
 
-	std::string filename = in_filename;
-	if (!subdir2.empty())
-	{
-		filename = subdir2 + mDirDelimiter + filename;
-	}
-
-	if (!subdir1.empty())
-	{
-		filename = subdir1 + mDirDelimiter + filename;
-	}
-
 	if (prefix.empty())
 	{
-		llwarns << "prefix is empty, possible bad filename" << llendl;
-	}
-	
-	std::string expanded_filename;
-	if (!filename.empty())
-	{
-		if (!prefix.empty())
-		{
-			expanded_filename += prefix;
-			expanded_filename += mDirDelimiter;
-			expanded_filename += filename;
-		}
-		else
-		{
-			expanded_filename = filename;
-		}
-	}
-	else if (!prefix.empty())
-	{
-		// Directory only, no file name.
-		expanded_filename = prefix;
+		llwarns << ELLPathToString(location)
+				<< ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
+				<< "': prefix is empty, possible bad filename" << llendl;
 	}
-	else
+
+	std::string expanded_filename = add(add(prefix, subdir1), subdir2);
+	if (expanded_filename.empty() && in_filename.empty())
 	{
-		expanded_filename.assign("");
+		return "";
 	}
-
-	//llinfos << "*** EXPANDED FILENAME: <" << expanded_filename << ">" << llendl;
+	// Use explicit concatenation here instead of another add() call. Callers
+	// passing in_filename as "" expect to obtain a pathname ending with
+	// mDirSeparator so they can later directly concatenate with a specific
+	// filename. A caller using add() doesn't care, but there's still code
+	// loose in the system that uses std::string::operator+().
+	expanded_filename += mDirDelimiter;
+	expanded_filename += in_filename;
+
+	LL_DEBUGS("LLDir") << ELLPathToString(location)
+					   << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
+					   << "' => '" << expanded_filename << "'" << LL_ENDL;
 	return expanded_filename;
 }
 
@@ -511,31 +545,207 @@ std::string LLDir::getExtension(const std::string& filepath) const
 	return exten;
 }
 
-std::string LLDir::findSkinnedFilename(const std::string &filename) const
+std::string LLDir::findSkinnedFilenameBaseLang(const std::string &subdir,
+											   const std::string &filename,
+											   ESkinConstraint constraint) const
 {
-	return findSkinnedFilename("", "", filename);
+	// This implementation is basically just as described in the declaration comments.
+	std::vector<std::string> found(findSkinnedFilenames(subdir, filename, constraint));
+	if (found.empty())
+	{
+		return "";
+	}
+	return found.front();
 }
 
-std::string LLDir::findSkinnedFilename(const std::string &subdir, const std::string &filename) const
+std::string LLDir::findSkinnedFilename(const std::string &subdir,
+									   const std::string &filename,
+									   ESkinConstraint constraint) const
 {
-	return findSkinnedFilename("", subdir, filename);
+	// This implementation is basically just as described in the declaration comments.
+	std::vector<std::string> found(findSkinnedFilenames(subdir, filename, constraint));
+	if (found.empty())
+	{
+		return "";
+	}
+	return found.back();
 }
 
-std::string LLDir::findSkinnedFilename(const std::string &subdir1, const std::string &subdir2, const std::string &filename) const
+// This method exists because the two code paths for
+// findSkinnedFilenames(ALL_SKINS) and findSkinnedFilenames(CURRENT_SKIN) must
+// generate the list of candidate pathnames in identical ways. The only
+// difference is in the body of the inner loop.
+template <typename FUNCTION>
+void LLDir::walkSearchSkinDirs(const std::string& subdir,
+							   const std::vector<std::string>& subsubdirs,
+							   const std::string& filename,
+							   const FUNCTION& function) const
 {
-	// generate subdirectory path fragment, e.g. "/foo/bar", "/foo", ""
-	std::string subdirs = ((subdir1.empty() ? "" : mDirDelimiter) + subdir1)
-						 + ((subdir2.empty() ? "" : mDirDelimiter) + subdir2);
+	BOOST_FOREACH(std::string skindir, mSearchSkinDirs)
+	{
+		std::string subdir_path(add(skindir, subdir));
+		BOOST_FOREACH(std::string subsubdir, subsubdirs)
+		{
+			std::string full_path(add(add(subdir_path, subsubdir), filename));
+			if (fileExists(full_path))
+			{
+				function(subsubdir, full_path);
+			}
+		}
+	}
+}
 
-	std::vector<std::string> search_paths;
-	
-	search_paths.push_back(getUserSkinDir() + subdirs);		// first look in user skin override
-	search_paths.push_back(getSkinDir() + subdirs);			// then in current skin
-	search_paths.push_back(getDefaultSkinDir() + subdirs);  // then default skin
-	search_paths.push_back(getCacheDir() + subdirs);		// and last in preload directory
+// ridiculous little helper function that should go away when we can use lambda
+inline void push_back(std::vector<std::string>& vector, const std::string& value)
+{
+	vector.push_back(value);
+}
+
+typedef std::map<std::string, std::string> StringMap;
+// ridiculous little helper function that should go away when we can use lambda
+inline void store_in_map(StringMap& map, const std::string& key, const std::string& value)
+{
+	map[key] = value;
+}
+
+std::vector<std::string> LLDir::findSkinnedFilenames(const std::string& subdir,
+													 const std::string& filename,
+													 ESkinConstraint constraint) const
+{
+	// Recognize subdirs that have no localization.
+	static const std::set<std::string> sUnlocalized = list_of
+		("")                        // top-level directory not localized
+		("textures")                // textures not localized
+	;
+
+	LL_DEBUGS("LLDir") << "subdir '" << subdir << "', filename '" << filename
+					   << "', constraint "
+					   << ((constraint == CURRENT_SKIN)? "CURRENT_SKIN" : "ALL_SKINS")
+					   << LL_ENDL;
+
+	// Cache the default language directory for each subdir we've encountered.
+	// A cache entry whose value is the empty string means "not localized,
+	// don't bother checking again."
+	static StringMap sLocalized;
+
+	// Check whether we've already discovered if this subdir is localized.
+	StringMap::const_iterator found = sLocalized.find(subdir);
+	if (found == sLocalized.end())
+	{
+		// We have not yet determined that. Is it one of the subdirs "known"
+		// to be unlocalized?
+		if (sUnlocalized.find(subdir) != sUnlocalized.end())
+		{
+			// This subdir is known to be unlocalized. Remember that.
+			found = sLocalized.insert(StringMap::value_type(subdir, "")).first;
+		}
+		else
+		{
+			// We do not recognize this subdir. Investigate.
+			std::string subdir_path(add(getDefaultSkinDir(), subdir));
+			if (fileExists(add(subdir_path, "en")))
+			{
+				// defaultSkinDir/subdir contains subdir "en". That's our
+				// default language; this subdir is localized.
+				found = sLocalized.insert(StringMap::value_type(subdir, "en")).first;
+			}
+			else if (fileExists(add(subdir_path, "en-us")))
+			{
+				// defaultSkinDir/subdir contains subdir "en-us" but not "en".
+				// Set as default language; this subdir is localized.
+				found = sLocalized.insert(StringMap::value_type(subdir, "en-us")).first;
+			}
+			else
+			{
+				// defaultSkinDir/subdir contains neither "en" nor "en-us".
+				// Assume it's not localized. Remember that assumption.
+				found = sLocalized.insert(StringMap::value_type(subdir, "")).first;
+			}
+		}
+	}
+	// Every code path above should have resulted in 'found' becoming a valid
+	// iterator to an entry in sLocalized.
+	llassert(found != sLocalized.end());
+
+	// Now -- is this subdir localized, or not? The answer determines what
+	// subdirectories we check (under subdir) for the requested filename.
+	std::vector<std::string> subsubdirs;
+	if (found->second.empty())
+	{
+		// subdir is not localized. filename should be located directly within it.
+		subsubdirs.push_back("");
+	}
+	else
+	{
+		// subdir is localized, and found->second is the default language
+		// directory within it. Check both the default language and the
+		// current language -- if it differs from the default, of course.
+		subsubdirs.push_back(found->second);
+		if (mLanguage != found->second)
+		{
+			subsubdirs.push_back(mLanguage);
+		}
+	}
+
+	// Build results vector.
+	std::vector<std::string> results;
+	// The process we use depends on 'constraint'.
+	if (constraint != CURRENT_SKIN) // meaning ALL_SKINS
+	{
+		// ALL_SKINS is simpler: just return every pathname generated by
+		// walkSearchSkinDirs(). Tricky bit: walkSearchSkinDirs() passes its
+		// FUNCTION the subsubdir as well as the full pathname. We just want
+		// the full pathname.
+		walkSearchSkinDirs(subdir, subsubdirs, filename,
+						   boost::bind(push_back, boost::ref(results), _2));
+	}
+	else                            // CURRENT_SKIN
+	{
+		// CURRENT_SKIN turns out to be a bit of a misnomer because we might
+		// still return files from two different skins. In any case, this
+		// value of 'constraint' means we will return at most two paths: one
+		// for the default language, one for the current language (supposing
+		// those differ).
+		// It is important to allow a user to override only the localization
+		// for a particular file, for all viewer installs, without also
+		// overriding the default-language file.
+		// It is important to allow a user to override only the default-
+		// language file, for all viewer installs, without also overriding the
+		// applicable localization of that file.
+		// Therefore, treat the default language and the current language as
+		// two separate cases. For each, capture the most-specialized file
+		// that exists.
+		// Use a map keyed by subsubdir (i.e. language code). This allows us
+		// to handle the case of a single subsubdirs entry with the same logic
+		// that handles two. For every real file path generated by
+		// walkSearchSkinDirs(), update the map entry for its subsubdir.
+		StringMap path_for;
+		walkSearchSkinDirs(subdir, subsubdirs, filename,
+						   boost::bind(store_in_map, boost::ref(path_for), _1, _2));
+		// Now that we have a path for each of the default language and the
+		// current language, copy them -- in proper order -- into results.
+		// Don't drive this by walking the map itself: it matters that we
+		// generate results in the same order as subsubdirs.
+		BOOST_FOREACH(std::string subsubdir, subsubdirs)
+		{
+			StringMap::const_iterator found(path_for.find(subsubdir));
+			if (found != path_for.end())
+			{
+				results.push_back(found->second);
+			}
+		}
+	}
 
-	std::string found_file = findFile(filename, search_paths);
-	return found_file;
+	LL_DEBUGS("LLDir") << empty;
+	const char* comma = "";
+	BOOST_FOREACH(std::string path, results)
+	{
+		LL_CONT << comma << "'" << path << "'";
+		comma = ", ";
+	}
+	LL_CONT << LL_ENDL;
+
+	return results;
 }
 
 std::string LLDir::getTempFilename() const
@@ -546,12 +756,7 @@ std::string LLDir::getTempFilename() const
 	random_uuid.generate();
 	random_uuid.toString(uuid_str);
 
-	std::string temp_filename = getTempDir();
-	temp_filename += mDirDelimiter;
-	temp_filename += uuid_str;
-	temp_filename += ".tmp";
-
-	return temp_filename;
+	return add(getTempDir(), uuid_str + ".tmp");
 }
 
 // static
@@ -587,9 +792,7 @@ void LLDir::setLindenUserDir(const std::string &username)
 		std::string userlower(username);
 		LLStringUtil::toLower(userlower);
 		LLStringUtil::replaceChar(userlower, ' ', '_');
-		mLindenUserDir = getOSUserAppDir();
-		mLindenUserDir += mDirDelimiter;
-		mLindenUserDir += userlower;
+		mLindenUserDir = add(getOSUserAppDir(), userlower);
 	}
 	else
 	{
@@ -621,9 +824,7 @@ void LLDir::setPerAccountChatLogsDir(const std::string &username)
 		std::string userlower(username);
 		LLStringUtil::toLower(userlower);
 		LLStringUtil::replaceChar(userlower, ' ', '_');
-		mPerAccountChatLogsDir = getChatLogsDir();
-		mPerAccountChatLogsDir += mDirDelimiter;
-		mPerAccountChatLogsDir += userlower;
+		mPerAccountChatLogsDir = add(getChatLogsDir(), userlower);
 	}
 	else
 	{
@@ -632,25 +833,59 @@ void LLDir::setPerAccountChatLogsDir(const std::string &username)
 	
 }
 
-void LLDir::setSkinFolder(const std::string &skin_folder)
+void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& language)
 {
-	mSkinDir = getSkinBaseDir();
-	mSkinDir += mDirDelimiter;
-	mSkinDir += skin_folder;
+	LL_DEBUGS("LLDir") << "Setting skin '" << skin_folder << "', language '" << language << "'"
+					   << LL_ENDL;
+	mSkinName = skin_folder;
+	mLanguage = language;
 
-	// user modifications to current skin
-	// e.g. c:\documents and settings\users\username\application data\second life\skins\dazzle
-	mUserSkinDir = getOSUserAppDir();
-	mUserSkinDir += mDirDelimiter;
-	mUserSkinDir += "skins";
-	mUserSkinDir += mDirDelimiter;	
-	mUserSkinDir += skin_folder;
+	// This method is called multiple times during viewer initialization. Each
+	// time it's called, reset mSearchSkinDirs.
+	mSearchSkinDirs.clear();
 
 	// base skin which is used as fallback for all skinned files
 	// e.g. c:\program files\secondlife\skins\default
 	mDefaultSkinDir = getSkinBaseDir();
-	mDefaultSkinDir += mDirDelimiter;	
-	mDefaultSkinDir += "default";
+	append(mDefaultSkinDir, "default");
+	// This is always the most general of the search skin directories.
+	addSearchSkinDir(mDefaultSkinDir);
+
+	mSkinDir = getSkinBaseDir();
+	append(mSkinDir, skin_folder);
+	// Next level of generality is a skin installed with the viewer.
+	addSearchSkinDir(mSkinDir);
+
+	// user modifications to skins, current and default
+	// e.g. c:\documents and settings\users\username\application data\second life\skins\dazzle
+	mUserSkinDir = getOSUserAppDir();
+	append(mUserSkinDir, "skins");
+	mUserDefaultSkinDir = mUserSkinDir;
+	append(mUserDefaultSkinDir, "default");
+	append(mUserSkinDir, skin_folder);
+	// Next level of generality is user modifications to default skin...
+	addSearchSkinDir(mUserDefaultSkinDir);
+	// then user-defined skins.
+	addSearchSkinDir(mUserSkinDir);
+}
+
+void LLDir::addSearchSkinDir(const std::string& skindir)
+{
+	if (std::find(mSearchSkinDirs.begin(), mSearchSkinDirs.end(), skindir) == mSearchSkinDirs.end())
+	{
+		LL_DEBUGS("LLDir") << "search skin: '" << skindir << "'" << LL_ENDL;
+		mSearchSkinDirs.push_back(skindir);
+	}
+}
+
+std::string LLDir::getSkinFolder() const
+{
+	return mSkinName;
+}
+
+std::string LLDir::getLanguage() const
+{
+	return mLanguage;
 }
 
 bool LLDir::setCacheDir(const std::string &path)
@@ -664,7 +899,7 @@ bool LLDir::setCacheDir(const std::string &path)
 	else
 	{
 		LLFile::mkdir(path);
-		std::string tempname = path + mDirDelimiter + "temp";
+		std::string tempname = add(path, "temp");
 		LLFILE* file = LLFile::fopen(tempname,"wt");
 		if (file)
 		{
@@ -697,6 +932,57 @@ void LLDir::dumpCurrentDirectories()
 	LL_DEBUGS2("AppInit","Directories") << "  SkinDir:               " << getSkinDir() << LL_ENDL;
 }
 
+std::string LLDir::add(const std::string& path, const std::string& name) const
+{
+	std::string destpath(path);
+	append(destpath, name);
+	return destpath;
+}
+
+void LLDir::append(std::string& destpath, const std::string& name) const
+{
+	// Delegate question of whether we need a separator to helper method.
+	SepOff sepoff(needSep(destpath, name));
+	if (sepoff.first)               // do we need a separator?
+	{
+		destpath += mDirDelimiter;
+	}
+	// If destpath ends with a separator, AND name starts with one, skip
+	// name's leading separator.
+	destpath += name.substr(sepoff.second);
+}
+
+LLDir::SepOff LLDir::needSep(const std::string& path, const std::string& name) const
+{
+	if (path.empty() || name.empty())
+	{
+		// If either path or name are empty, we do not need a separator
+		// between them.
+		return SepOff(false, 0);
+	}
+	// Here we know path and name are both non-empty. But if path already ends
+	// with a separator, or if name already starts with a separator, we need
+	// not add one.
+	std::string::size_type seplen(mDirDelimiter.length());
+	bool path_ends_sep(path.substr(path.length() - seplen) == mDirDelimiter);
+	bool name_starts_sep(name.substr(0, seplen) == mDirDelimiter);
+	if ((! path_ends_sep) && (! name_starts_sep))
+	{
+		// If neither path nor name brings a separator to the junction, then
+		// we need one.
+		return SepOff(true, 0);
+	}
+	if (path_ends_sep && name_starts_sep)
+	{
+		// But if BOTH path and name bring a separator, we need not add one.
+		// Moreover, we should actually skip the leading separator of 'name'.
+		return SepOff(false, seplen);
+	}
+	// Here we know that either path_ends_sep or name_starts_sep is true --
+	// but not both. So don't add a separator, and don't skip any characters:
+	// simple concatenation will do the trick.
+	return SepOff(false, 0);
+}
 
 void dir_exists_or_crash(const std::string &dir_name)
 {
diff --git a/indra/llvfs/lldir.h b/indra/llvfs/lldir.h
index 3b1883b5d8bc26080ee2e7c9f53c567eb7a2d185..c60d3ef3a2236f26ca018e9d34bf0a7e71ee611d 100644
--- a/indra/llvfs/lldir.h
+++ b/indra/llvfs/lldir.h
@@ -56,7 +56,7 @@ typedef enum ELLPath
 	LL_PATH_LAST
 } ELLPath;
 
-
+/// Directory operations
 class LLDir
 {
  public:
@@ -100,9 +100,10 @@ class LLDir
 	const std::string &getOSCacheDir() const;		// location of OS-specific cache folder (may be empty string)
 	const std::string &getCAFile() const;			// File containing TLS certificate authorities
 	const std::string &getDirDelimiter() const;	// directory separator for platform (ie. '\' or '/' or ':')
+	const std::string &getDefaultSkinDir() const;	// folder for default skin. e.g. c:\program files\second life\skins\default
 	const std::string &getSkinDir() const;		// User-specified skin folder.
+	const std::string &getUserDefaultSkinDir() const; // dir with user modifications to default skin
 	const std::string &getUserSkinDir() const;		// User-specified skin folder with user modifications. e.g. c:\documents and settings\username\application data\second life\skins\curskin
-	const std::string &getDefaultSkinDir() const;	// folder for default skin. e.g. c:\program files\second life\skins\default
 	const std::string getSkinBaseDir() const;		// folder that contains all installed skins (not user modifications). e.g. c:\program files\second life\skins
 	const std::string &getLLPluginDir() const;		// Directory containing plugins and plugin shell
 
@@ -117,10 +118,61 @@ class LLDir
 	std::string getExtension(const std::string& filepath) const; // Excludes '.', e.g getExtension("foo.wav") == "wav"
 
 	// these methods search the various skin paths for the specified file in the following order:
-	// getUserSkinDir(), getSkinDir(), getDefaultSkinDir()
-	std::string findSkinnedFilename(const std::string &filename) const;
-	std::string findSkinnedFilename(const std::string &subdir, const std::string &filename) const;
-	std::string findSkinnedFilename(const std::string &subdir1, const std::string &subdir2, const std::string &filename) const;
+	// getUserSkinDir(), getUserDefaultSkinDir(), getSkinDir(), getDefaultSkinDir()
+	/// param value for findSkinnedFilenames(), explained below
+	enum ESkinConstraint { CURRENT_SKIN, ALL_SKINS };
+	/**
+	 * Given a filename within skin, return an ordered sequence of paths to
+	 * search. Nonexistent files will be filtered out -- which means that the
+	 * vector might be empty.
+	 *
+	 * @param subdir Identify top-level skin subdirectory by passing one of
+	 * LLDir::XUI (file lives under "xui" subtree), LLDir::TEXTURES (file
+	 * lives under "textures" subtree), LLDir::SKINBASE (file lives at top
+	 * level of skin subdirectory).
+	 * @param filename Desired filename within subdir within skin, e.g.
+	 * "panel_login.xml". DO NOT prepend (e.g.) "xui" or the desired language.
+	 * @param constraint Callers perform two different kinds of processing.
+	 * When fetching a XUI file, for instance, the existence of @a filename in
+	 * the specified skin completely supercedes any @a filename in the default
+	 * skin. For that case, leave the default @a constraint=CURRENT_SKIN. The
+	 * returned vector will contain only
+	 * ".../<i>current_skin</i>/xui/en/<i>filename</i>",
+	 * ".../<i>current_skin</i>/xui/<i>current_language</i>/<i>filename</i>".
+	 * But for (e.g.) "strings.xml", we want a given skin to be able to
+	 * override only specific entries from the default skin. Any string not
+	 * defined in the specified skin will be sought in the default skin. For
+	 * that case, pass @a constraint=ALL_SKINS. The returned vector will
+	 * contain at least ".../default/xui/en/strings.xml",
+	 * ".../default/xui/<i>current_language</i>/strings.xml",
+	 * ".../<i>current_skin</i>/xui/en/strings.xml",
+	 * ".../<i>current_skin</i>/xui/<i>current_language</i>/strings.xml".
+	 */
+	std::vector<std::string> findSkinnedFilenames(const std::string& subdir,
+												  const std::string& filename,
+												  ESkinConstraint constraint=CURRENT_SKIN) const;
+	/// Values for findSkinnedFilenames(subdir) parameter
+	static const char *XUI, *TEXTURES, *SKINBASE;
+	/**
+	 * Return the base-language pathname from findSkinnedFilenames(), or
+	 * the empty string if no such file exists. Parameters are identical to
+	 * findSkinnedFilenames(). This is shorthand for capturing the vector
+	 * returned by findSkinnedFilenames(), checking for empty() and then
+	 * returning front().
+	 */
+	std::string findSkinnedFilenameBaseLang(const std::string &subdir,
+											const std::string &filename,
+											ESkinConstraint constraint=CURRENT_SKIN) const;
+	/**
+	 * Return the "most localized" pathname from findSkinnedFilenames(), or
+	 * the empty string if no such file exists. Parameters are identical to
+	 * findSkinnedFilenames(). This is shorthand for capturing the vector
+	 * returned by findSkinnedFilenames(), checking for empty() and then
+	 * returning back().
+	 */
+	std::string findSkinnedFilename(const std::string &subdir,
+									const std::string &filename,
+									ESkinConstraint constraint=CURRENT_SKIN) const;
 
 	// random filename in common temporary directory
 	std::string getTempFilename() const;
@@ -132,15 +184,37 @@ class LLDir
 	virtual void setChatLogsDir(const std::string &path);		// Set the chat logs dir to this user's dir
 	virtual void setPerAccountChatLogsDir(const std::string &username);		// Set the per user chat log directory.
 	virtual void setLindenUserDir(const std::string &username);		// Set the linden user dir to this user's dir
-	virtual void setSkinFolder(const std::string &skin_folder);
+	virtual void setSkinFolder(const std::string &skin_folder, const std::string& language);
+	virtual std::string getSkinFolder() const;
+	virtual std::string getLanguage() const;
 	virtual bool setCacheDir(const std::string &path);
 
 	virtual void dumpCurrentDirectories();
-	
+
 	// Utility routine
 	std::string buildSLOSCacheDir() const;
 
+	/// Append specified @a name to @a destpath, separated by getDirDelimiter()
+	/// if both are non-empty.
+	void append(std::string& destpath, const std::string& name) const;
+	/// Append specified @a name to @a path, separated by getDirDelimiter()
+	/// if both are non-empty. Return result, leaving @a path unmodified.
+	std::string add(const std::string& path, const std::string& name) const;
+
 protected:
+	// Does an add() or append() call need a directory delimiter?
+	typedef std::pair<bool, unsigned short> SepOff;
+	SepOff needSep(const std::string& path, const std::string& name) const;
+	// build mSearchSkinDirs without adding duplicates
+	void addSearchSkinDir(const std::string& skindir);
+
+	// Internal to findSkinnedFilenames()
+	template <typename FUNCTION>
+	void walkSearchSkinDirs(const std::string& subdir,
+							const std::vector<std::string>& subsubdirs,
+							const std::string& filename,
+							const FUNCTION& function) const;
+
 	std::string mAppName;               // install directory under progams/ ie "SecondLife"   
 	std::string mExecutablePathAndName; // full path + Filename of .exe
 	std::string mExecutableFilename;    // Filename of .exe
@@ -158,10 +232,18 @@ protected:
 	std::string mDefaultCacheDir;	// default cache diretory
 	std::string mOSCacheDir;		// operating system cache dir
 	std::string mDirDelimiter;
+	std::string mSkinName;           // caller-specified skin name
 	std::string mSkinBaseDir;			// Base for skins paths.
-	std::string mSkinDir;			// Location for current skin info.
 	std::string mDefaultSkinDir;			// Location for default skin info.
+	std::string mSkinDir;			// Location for current skin info.
+	std::string mUserDefaultSkinDir;		// Location for default skin info.
 	std::string mUserSkinDir;			// Location for user-modified skin info.
+	// Skin directories to search, most general to most specific. This order
+	// works well for composing fine-grained files, in which an individual item
+	// in a specific file overrides the corresponding item in more general
+	// files. Of course, for a file-level search, iterate backwards.
+	std::vector<std::string> mSearchSkinDirs;
+	std::string mLanguage;              // Current viewer language
 	std::string mLLPluginDir;			// Location for plugins and plugin shell
 };
 
diff --git a/indra/llvfs/tests/lldir_test.cpp b/indra/llvfs/tests/lldir_test.cpp
index ea321c5ae958829d739137eae3548f5cbf07c540..323f876c126924de4f21f7b9e3859377c5cd0c43 100644
--- a/indra/llvfs/tests/lldir_test.cpp
+++ b/indra/llvfs/tests/lldir_test.cpp
@@ -27,11 +27,167 @@
 
 #include "linden_common.h"
 
+#include "llstring.h"
+#include "tests/StringVec.h"
 #include "../lldir.h"
 #include "../lldiriterator.h"
 
 #include "../test/lltut.h"
+#include "stringize.h"
+#include <boost/foreach.hpp>
+#include <boost/assign/list_of.hpp>
+
+using boost::assign::list_of;
+
+// We use ensure_equals(..., vec(list_of(...))) not because it's functionally
+// required, but because ensure_equals() knows how to format a StringVec.
+// Turns out that when ensure_equals() displays a test failure with just
+// list_of("string")("another"), you see 'stringanother' vs. '("string",
+// "another")'.
+StringVec vec(const StringVec& v)
+{
+    return v;
+}
 
+// For some tests, use a dummy LLDir that uses memory data instead of touching
+// the filesystem
+struct LLDir_Dummy: public LLDir
+{
+    /*----------------------------- LLDir API ------------------------------*/
+    LLDir_Dummy()
+    {
+        // Initialize important LLDir data members based on the filesystem
+        // data below.
+        mDirDelimiter = "/";
+        mExecutableDir = "install";
+        mExecutableFilename = "test";
+        mExecutablePathAndName = add(mExecutableDir, mExecutableFilename);
+        mWorkingDir = mExecutableDir;
+        mAppRODataDir = "install";
+        mSkinBaseDir = add(mAppRODataDir, "skins");
+        mOSUserDir = "user";
+        mOSUserAppDir = mOSUserDir;
+        mLindenUserDir = "";
+
+        // Make the dummy filesystem look more or less like what we expect in
+        // the real one.
+        static const char* preload[] =
+        {
+            // We group these fixture-data pathnames by basename, rather than
+            // sorting by full path as you might expect, because the outcome
+            // of each test strongly depends on which skins/languages provide
+            // a given basename.
+            "install/skins/default/colors.xml",
+            "install/skins/steam/colors.xml",
+            "user/skins/default/colors.xml",
+            "user/skins/steam/colors.xml",
+
+            "install/skins/default/xui/en/strings.xml",
+            "install/skins/default/xui/fr/strings.xml",
+            "install/skins/steam/xui/en/strings.xml",
+            "install/skins/steam/xui/fr/strings.xml",
+            "user/skins/default/xui/en/strings.xml",
+            "user/skins/default/xui/fr/strings.xml",
+            "user/skins/steam/xui/en/strings.xml",
+            "user/skins/steam/xui/fr/strings.xml",
+
+            "install/skins/default/xui/en/floater.xml",
+            "install/skins/default/xui/fr/floater.xml",
+            "user/skins/default/xui/fr/floater.xml",
+
+            "install/skins/default/xui/en/newfile.xml",
+            "install/skins/default/xui/fr/newfile.xml",
+            "user/skins/default/xui/en/newfile.xml",
+
+            "install/skins/default/html/en-us/welcome.html",
+            "install/skins/default/html/fr/welcome.html",
+
+            "install/skins/default/textures/only_default.jpeg",
+            "install/skins/steam/textures/only_steam.jpeg",
+            "user/skins/default/textures/only_user_default.jpeg",
+            "user/skins/steam/textures/only_user_steam.jpeg",
+
+            "install/skins/default/future/somefile.txt"
+        };
+        BOOST_FOREACH(const char* path, preload)
+        {
+            buildFilesystem(path);
+        }
+    }
+
+    virtual ~LLDir_Dummy() {}
+
+    virtual void initAppDirs(const std::string& app_name, const std::string& app_read_only_data_dir)
+    {
+        // Implement this when we write a test that needs it
+    }
+
+    virtual std::string getCurPath()
+    {
+        // Implement this when we write a test that needs it
+        return "";
+    }
+
+    virtual U32 countFilesInDir(const std::string& dirname, const std::string& mask)
+    {
+        // Implement this when we write a test that needs it
+        return 0;
+    }
+
+    virtual BOOL fileExists(const std::string& pathname) const
+    {
+        // Record fileExists() calls so we can check whether caching is
+        // working right. Certain LLDir calls should be able to make decisions
+        // without calling fileExists() again, having already checked existence.
+        mChecked.insert(pathname);
+        // For our simple flat set of strings, see whether the identical
+        // pathname exists in our set.
+        return (mFilesystem.find(pathname) != mFilesystem.end());
+    }
+
+    virtual std::string getLLPluginLauncher()
+    {
+        // Implement this when we write a test that needs it
+        return "";
+    }
+
+    virtual std::string getLLPluginFilename(std::string base_name)
+    {
+        // Implement this when we write a test that needs it
+        return "";
+    }
+
+    /*----------------------------- Dummy data -----------------------------*/
+    void clearFilesystem() { mFilesystem.clear(); }
+    void buildFilesystem(const std::string& path)
+    {
+        // Split the pathname on slashes, ignoring leading, trailing, doubles
+        StringVec components;
+        LLStringUtil::getTokens(path, components, "/");
+        // Ensure we have an entry representing every level of this path
+        std::string partial;
+        BOOST_FOREACH(std::string component, components)
+        {
+            append(partial, component);
+            mFilesystem.insert(partial);
+        }
+    }
+
+    void clear_checked() { mChecked.clear(); }
+    void ensure_checked(const std::string& pathname) const
+    {
+        tut::ensure(STRINGIZE(pathname << " was not checked but should have been"),
+                    mChecked.find(pathname) != mChecked.end());
+    }
+    void ensure_not_checked(const std::string& pathname) const
+    {
+        tut::ensure(STRINGIZE(pathname << " was checked but should not have been"),
+                    mChecked.find(pathname) == mChecked.end());
+    }
+
+    std::set<std::string> mFilesystem;
+    mutable std::set<std::string> mChecked;
+};
 
 namespace tut
 {
@@ -419,5 +575,193 @@ namespace tut
       LLFile::rmdir(dir1);
       LLFile::rmdir(dir2);
    }
-}
 
+    template<> template<>
+    void LLDirTest_object_t::test<6>()
+    {
+        set_test_name("findSkinnedFilenames()");
+        LLDir_Dummy lldir;
+        /*------------------------ "default", "en" -------------------------*/
+        // Setting "default" means we shouldn't consider any "*/skins/steam"
+        // directories; setting "en" means we shouldn't consider any "xui/fr"
+        // directories.
+        lldir.setSkinFolder("default", "en");
+        ensure_equals(lldir.getSkinFolder(), "default");
+        ensure_equals(lldir.getLanguage(), "en");
+
+        // top-level directory of a skin isn't localized
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS),
+                      vec(list_of("install/skins/default/colors.xml")
+                                 ("user/skins/default/colors.xml")));
+        // We should not have needed to check for skins/default/en. We should
+        // just "know" that SKINBASE is not localized.
+        lldir.ensure_not_checked("install/skins/default/en");
+
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_default.jpeg"),
+                      vec(list_of("install/skins/default/textures/only_default.jpeg")));
+        // Nor should we have needed to check skins/default/textures/en
+        // because textures is known not to be localized.
+        lldir.ensure_not_checked("install/skins/default/textures/en");
+
+        StringVec expected(vec(list_of("install/skins/default/xui/en/strings.xml")
+                               ("user/skins/default/xui/en/strings.xml")));
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
+                      expected);
+        // The first time, we had to probe to find out whether xui was localized.
+        lldir.ensure_checked("install/skins/default/xui/en");
+        lldir.clear_checked();
+        // Now make the same call again -- should return same result --
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
+                      expected);
+        // but this time it should remember that xui is localized.
+        lldir.ensure_not_checked("install/skins/default/xui/en");
+
+        // localized subdir with "en-us" instead of "en"
+        ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"),
+                      vec(list_of("install/skins/default/html/en-us/welcome.html")));
+        lldir.ensure_checked("install/skins/default/html/en");
+        lldir.ensure_checked("install/skins/default/html/en-us");
+        lldir.clear_checked();
+        ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"),
+                      vec(list_of("install/skins/default/html/en-us/welcome.html")));
+        lldir.ensure_not_checked("install/skins/default/html/en");
+        lldir.ensure_not_checked("install/skins/default/html/en-us");
+
+        ensure_equals(lldir.findSkinnedFilenames("future", "somefile.txt"),
+                      vec(list_of("install/skins/default/future/somefile.txt")));
+        // Test probing for an unrecognized unlocalized future subdir.
+        lldir.ensure_checked("install/skins/default/future/en");
+        lldir.clear_checked();
+        ensure_equals(lldir.findSkinnedFilenames("future", "somefile.txt"),
+                      vec(list_of("install/skins/default/future/somefile.txt")));
+        // Second time it should remember that future is unlocalized.
+        lldir.ensure_not_checked("install/skins/default/future/en");
+
+        // When language is set to "en", requesting an html file pulls up the
+        // "en-us" version -- not because it magically matches those strings,
+        // but because there's no "en" localization and it falls back on the
+        // default "en-us"! Note that it would probably still be better to
+        // make the default localization be "en" and allow "en-gb" (or
+        // whatever) localizations, which would work much more the way you'd
+        // expect.
+        ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"),
+                      vec(list_of("install/skins/default/html/en-us/welcome.html")));
+
+        /*------------------------ "default", "fr" -------------------------*/
+        // We start being able to distinguish localized subdirs from
+        // unlocalized when we ask for a non-English language.
+        lldir.setSkinFolder("default", "fr");
+        ensure_equals(lldir.getLanguage(), "fr");
+
+        // pass merge=true to request this filename in all relevant skins
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
+                      vec(list_of
+                          ("install/skins/default/xui/en/strings.xml")
+                          ("install/skins/default/xui/fr/strings.xml")
+                          ("user/skins/default/xui/en/strings.xml")
+                          ("user/skins/default/xui/fr/strings.xml")));
+
+        // pass (or default) merge=false to request only most specific skin
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"),
+                      vec(list_of
+                          ("user/skins/default/xui/en/strings.xml")
+                          ("user/skins/default/xui/fr/strings.xml")));
+
+        // Our dummy floater.xml has a user localization (for "fr") but no
+        // English override. This is a case in which CURRENT_SKIN nonetheless
+        // returns paths from two different skins.
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "floater.xml"),
+                      vec(list_of
+                          ("install/skins/default/xui/en/floater.xml")
+                          ("user/skins/default/xui/fr/floater.xml")));
+
+        // Our dummy newfile.xml has an English override but no user
+        // localization. This is another case in which CURRENT_SKIN
+        // nonetheless returns paths from two different skins.
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "newfile.xml"),
+                      vec(list_of
+                          ("user/skins/default/xui/en/newfile.xml")
+                          ("install/skins/default/xui/fr/newfile.xml")));
+
+        ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"),
+                      vec(list_of
+                          ("install/skins/default/html/en-us/welcome.html")
+                          ("install/skins/default/html/fr/welcome.html")));
+
+        /*------------------------ "default", "zh" -------------------------*/
+        lldir.setSkinFolder("default", "zh");
+        // Because strings.xml has only a "fr" override but no "zh" override
+        // in any skin, the most localized version we can find is "en".
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"),
+                      vec(list_of("user/skins/default/xui/en/strings.xml")));
+
+        /*------------------------- "steam", "en" --------------------------*/
+        lldir.setSkinFolder("steam", "en");
+
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS),
+                      vec(list_of
+                          ("install/skins/default/colors.xml")
+                          ("install/skins/steam/colors.xml")
+                          ("user/skins/default/colors.xml")
+                          ("user/skins/steam/colors.xml")));
+
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_default.jpeg"),
+                      vec(list_of("install/skins/default/textures/only_default.jpeg")));
+
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_steam.jpeg"),
+                      vec(list_of("install/skins/steam/textures/only_steam.jpeg")));
+
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_user_default.jpeg"),
+                      vec(list_of("user/skins/default/textures/only_user_default.jpeg")));
+
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_user_steam.jpeg"),
+                      vec(list_of("user/skins/steam/textures/only_user_steam.jpeg")));
+
+        // CURRENT_SKIN
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"),
+                      vec(list_of("user/skins/steam/xui/en/strings.xml")));
+
+        // pass constraint=ALL_SKINS to request this filename in all relevant skins
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
+                      vec(list_of
+                          ("install/skins/default/xui/en/strings.xml")
+                          ("install/skins/steam/xui/en/strings.xml")
+                          ("user/skins/default/xui/en/strings.xml")
+                          ("user/skins/steam/xui/en/strings.xml")));
+
+        /*------------------------- "steam", "fr" --------------------------*/
+        lldir.setSkinFolder("steam", "fr");
+
+        // pass CURRENT_SKIN to request only the most specialized files
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"),
+                      vec(list_of
+                          ("user/skins/steam/xui/en/strings.xml")
+                          ("user/skins/steam/xui/fr/strings.xml")));
+
+        // pass ALL_SKINS to request this filename in all relevant skins
+        ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
+                      vec(list_of
+                          ("install/skins/default/xui/en/strings.xml")
+                          ("install/skins/default/xui/fr/strings.xml")
+                          ("install/skins/steam/xui/en/strings.xml")
+                          ("install/skins/steam/xui/fr/strings.xml")
+                          ("user/skins/default/xui/en/strings.xml")
+                          ("user/skins/default/xui/fr/strings.xml")
+                          ("user/skins/steam/xui/en/strings.xml")
+                          ("user/skins/steam/xui/fr/strings.xml")));
+    }
+
+    template<> template<>
+    void LLDirTest_object_t::test<7>()
+    {
+        set_test_name("add()");
+        LLDir_Dummy lldir;
+        ensure_equals("both empty", lldir.add("", ""), "");
+        ensure_equals("path empty", lldir.add("", "b"), "b");
+        ensure_equals("name empty", lldir.add("a", ""), "a");
+        ensure_equals("both simple", lldir.add("a", "b"), "a/b");
+        ensure_equals("name leading slash", lldir.add("a", "/b"), "a/b");
+        ensure_equals("path trailing slash", lldir.add("a/", "b"), "a/b");
+        ensure_equals("both bring slashes", lldir.add("a/", "/b"), "a/b");
+    }
+}
diff --git a/indra/llxml/llxmlnode.cpp b/indra/llxml/llxmlnode.cpp
index 2ffb0d8503cd662c106704c3f53d781a7ee55261..b7752492192fd8bd4049ea1b643ba388318fc5d7 100644
--- a/indra/llxml/llxmlnode.cpp
+++ b/indra/llxml/llxmlnode.cpp
@@ -897,7 +897,8 @@ bool LLXMLNode::getLayeredXMLNode(LLXMLNodePtr& root,
 
 	std::vector<std::string>::const_iterator itor;
 
-	for (itor = paths.begin(), ++itor; itor != paths.end(); ++itor)
+	// We've already dealt with the first item, skip that one
+	for (itor = paths.begin() + 1; itor != paths.end(); ++itor)
 	{
 		std::string layer_filename = *itor;
 		if(layer_filename.empty() || layer_filename == filename)
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 5e42fc29f79769d94397f78347d81edada7e912f..f5badb078d45c1e98f4246e64b49f8ea1ed23aeb 100755
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -4051,17 +4051,6 @@
       <key>Value</key>
       <integer>305</integer>
     </map>
-    <key>HelpUseLocal</key>
-    <map>
-      <key>Comment</key>
-      <string>If set, always use this for help: skins/default/html/[LANGUAGE]/help-offline/index.html</string>
-      <key>Persist</key>
-      <integer>0</integer>
-      <key>Type</key>
-      <string>Boolean</string>
-      <key>Value</key>
-      <integer>0</integer>
-    </map>
     <key>HelpURLFormat</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 0a9d6229ef85d11e90f84f633f0a75276d921a06..3cdd5cffc16d7824fcbad6a8f72678cd56b6797b 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -123,7 +123,6 @@
 #include <boost/algorithm/string.hpp>
 
 
-
 #if LL_WINDOWS
 #	include <share.h> // For _SH_DENYWR in initMarkerFile
 #else
@@ -691,7 +690,7 @@ bool LLAppViewer::init()
 	gDirUtilp->initAppDirs("SecondLife");
 	// set skin search path to default, will be overridden later
 	// this allows simple skinned file lookups to work
-	gDirUtilp->setSkinFolder("default");
+	gDirUtilp->setSkinFolder("default", "en");
 
 	initLogging();
 	
@@ -775,12 +774,16 @@ bool LLAppViewer::init()
 		&LLUI::sGLScaleFactor);
 	LL_INFOS("InitInfo") << "UI initialized." << LL_ENDL ;
 
-	// Setup paths and LLTrans after LLUI::initClass has been called.
-	LLUI::setupPaths();
+	// NOW LLUI::getLanguage() should work. gDirUtilp must know the language
+	// for this session ASAP so all the file-loading commands that follow,
+	// that use findSkinnedFilenames(), will include the localized files.
+	gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), LLUI::getLanguage());
+
+	// Setup LLTrans after LLUI::initClass has been called.
 	LLTransUtil::parseStrings("strings.xml", default_trans_args);
 	LLTransUtil::parseLanguageStrings("language_settings.xml");
 
-	// Setup notifications after LLUI::setupPaths() has been called.
+	// Setup notifications after LLUI::initClass() has been called.
 	LLNotifications::instance();
 	LL_INFOS("InitInfo") << "Notifications initialized." << LL_ENDL ;
 
@@ -2252,8 +2255,7 @@ bool LLAppViewer::initConfiguration()
 		OSMessageBox(msg.str(),LLStringUtil::null,OSMB_OK);
 		return false;
 	}
-	
-	LLUI::setupPaths(); // setup paths for LLTrans based on settings files only
+
 	LLTransUtil::parseStrings("strings.xml", default_trans_args);
 	LLTransUtil::parseLanguageStrings("language_settings.xml");
 	// - set procedural settings
@@ -2569,13 +2571,15 @@ bool LLAppViewer::initConfiguration()
 		LLStartUp::setStartSLURL(start_slurl);
     }
 
-    const LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent");
-    if(skinfolder && LLStringUtil::null != skinfolder->getValue().asString())
-    {   
-		// hack to force the skin to default.
-        gDirUtilp->setSkinFolder(skinfolder->getValue().asString());
-		//gDirUtilp->setSkinFolder("default");
-    }
+	const LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent");
+	if(skinfolder && LLStringUtil::null != skinfolder->getValue().asString())
+	{	
+		// Examining "Language" may not suffice -- see LLUI::getLanguage()
+		// logic. Unfortunately LLUI::getLanguage() doesn't yet do us much
+		// good because we haven't yet called LLUI::initClass().
+		gDirUtilp->setSkinFolder(skinfolder->getValue().asString(),
+								 gSavedSettings.getString("Language"));
+	}
 
 	if (gSavedSettings.getBOOL("SpellCheck"))
 	{
@@ -3599,8 +3603,7 @@ void LLAppViewer::migrateCacheDirectory()
 	{
 		gSavedSettings.setBOOL("MigrateCacheDirectory", FALSE);
 
-		std::string delimiter = gDirUtilp->getDirDelimiter();
-		std::string old_cache_dir = gDirUtilp->getOSUserAppDir() + delimiter + "cache";
+		std::string old_cache_dir = gDirUtilp->add(gDirUtilp->getOSUserAppDir(), "cache");
 		std::string new_cache_dir = gDirUtilp->getCacheDir(true);
 
 		if (gDirUtilp->fileExists(old_cache_dir))
@@ -3616,8 +3619,8 @@ void LLAppViewer::migrateCacheDirectory()
 			while (iter.next(file_name))
 			{
 				if (file_name == "." || file_name == "..") continue;
-				std::string source_path = old_cache_dir + delimiter + file_name;
-				std::string dest_path = new_cache_dir + delimiter + file_name;
+				std::string source_path = gDirUtilp->add(old_cache_dir, file_name);
+				std::string dest_path = gDirUtilp->add(new_cache_dir, file_name);
 				if (!LLFile::rename(source_path, dest_path))
 				{
 					file_count++;
@@ -3848,7 +3851,7 @@ bool LLAppViewer::initCache()
 		LLDirIterator iter(dir, mask);
 		if (iter.next(found_file))
 		{
-			old_vfs_data_file = dir + gDirUtilp->getDirDelimiter() + found_file;
+			old_vfs_data_file = gDirUtilp->add(dir, found_file);
 
 			S32 start_pos = found_file.find_last_of('.');
 			if (start_pos > 0)
@@ -5163,20 +5166,20 @@ void LLAppViewer::launchUpdater()
 	// we tell the updater where to find the xml containing string
 	// translations which it can use for its own UI
 	std::string xml_strings_file = "strings.xml";
-	std::vector<std::string> xui_path_vec = LLUI::getXUIPaths();
+	std::vector<std::string> xui_path_vec =
+		gDirUtilp->findSkinnedFilenames(LLDir::XUI, xml_strings_file);
 	std::string xml_search_paths;
-	std::vector<std::string>::const_iterator iter;
+	const char* delim = "";
 	// build comma-delimited list of xml paths to pass to updater
-	for (iter = xui_path_vec.begin(); iter != xui_path_vec.end(); )
-	{
-		std::string this_skin_dir = gDirUtilp->getDefaultSkinDir()
-			+ gDirUtilp->getDirDelimiter()
-			+ (*iter);
-		llinfos << "Got a XUI path: " << this_skin_dir << llendl;
-		xml_search_paths.append(this_skin_dir);
-		++iter;
-		if (iter != xui_path_vec.end())
-			xml_search_paths.append(","); // comma-delimit
+	BOOST_FOREACH(std::string this_skin_path, xui_path_vec)
+	{
+		// Although we already have the full set of paths with the filename
+		// appended, the linux-updater.bin command-line switches require us to
+		// snip the filename OFF and pass it as a separate switch argument. :-P
+		llinfos << "Got a XUI path: " << this_skin_path << llendl;
+		xml_search_paths.append(delim);
+		xml_search_paths.append(gDirUtilp->getDirName(this_skin_path));
+		delim = ",";
 	}
 	// build the overall command-line to run the updater correctly
 	LLAppViewer::sUpdaterInfo->mUpdateExePath = 
diff --git a/indra/newview/lldaycyclemanager.cpp b/indra/newview/lldaycyclemanager.cpp
index 347a467a8bfa3ce7c05f7cea334451e9e5dd71ea..8af2f4ea3366818c9cbcada2a4cdcb7236a1f209 100644
--- a/indra/newview/lldaycyclemanager.cpp
+++ b/indra/newview/lldaycyclemanager.cpp
@@ -184,7 +184,7 @@ void LLDayCycleManager::loadPresets(const std::string& dir)
 	{
 		std::string file;
 		if (!dir_iter.next(file)) break; // no more files
-		loadPreset(dir + file);
+		loadPreset(gDirUtilp->add(dir, file));
 	}
 }
 
diff --git a/indra/newview/llfloaterhelpbrowser.cpp b/indra/newview/llfloaterhelpbrowser.cpp
index fd9c37ae7328991723402fde96a2e8114b3f43d4..4cb632bd6abf0c16b905d86aa9601e75c385b45a 100644
--- a/indra/newview/llfloaterhelpbrowser.cpp
+++ b/indra/newview/llfloaterhelpbrowser.cpp
@@ -77,15 +77,7 @@ void LLFloaterHelpBrowser::onOpen(const LLSD& key)
 	gSavedSettings.setBOOL("HelpFloaterOpen", TRUE);
 
 	std::string topic = key.asString();
-
-	if (topic == "__local")
-	{
-		mBrowser->navigateToLocalPage( "help-offline" , "index.html" );
-	}
-	else
-	{
-		mBrowser->navigateTo(LLViewerHelp::instance().getURL(topic));
-	}
+	mBrowser->navigateTo(LLViewerHelp::instance().getURL(topic));
 }
 
 //virtual
diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp
index d741b5b1335c3a6f14926dae90a18829f16f478d..15e0b89f6ccb970cd37e91133cd2c1bc3baa5ae9 100644
--- a/indra/newview/llfloateruipreview.cpp
+++ b/indra/newview/llfloateruipreview.cpp
@@ -137,7 +137,7 @@ public:
 	virtual ~LLFloaterUIPreview();
 
 	std::string getLocStr(S32 ID);							// fetches the localization string based on what is selected in the drop-down menu
-	void displayFloater(BOOL click, S32 ID, bool save = false);			// needs to be public so live file can call it when it finds an update
+	void displayFloater(BOOL click, S32 ID);			// needs to be public so live file can call it when it finds an update
 
 	/*virtual*/ BOOL postBuild();
 	/*virtual*/ void onClose(bool app_quitting);
@@ -291,7 +291,8 @@ LLLocalizationResetForcer::LLLocalizationResetForcer(LLFloaterUIPreview* floater
 {
 	mSavedLocalization = LLUI::sSettingGroups["config"]->getString("Language");				// save current localization setting
 	LLUI::sSettingGroups["config"]->setString("Language", floater->getLocStr(ID));// hack language to be the one we want to preview floaters in
-	LLUI::setupPaths();														// forcibly reset XUI paths with this new language
+	// forcibly reset XUI paths with this new language
+	gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), floater->getLocStr(ID));
 }
 
 // Actually reset in destructor
@@ -299,7 +300,8 @@ LLLocalizationResetForcer::LLLocalizationResetForcer(LLFloaterUIPreview* floater
 LLLocalizationResetForcer::~LLLocalizationResetForcer()
 {
 	LLUI::sSettingGroups["config"]->setString("Language", mSavedLocalization);	// reset language to what it was before we changed it
-	LLUI::setupPaths();														// forcibly reset XUI paths with this new language
+	// forcibly reset XUI paths with this new language
+	gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), mSavedLocalization);
 }
 
 // Live file constructor
@@ -488,7 +490,7 @@ BOOL LLFloaterUIPreview::postBuild()
 	{
 		if((found = iter.next(language_directory)))							// get next directory
 		{
-			std::string full_path = xui_dir + language_directory;
+			std::string full_path = gDirUtilp->add(xui_dir, language_directory);
 			if(LLFile::isfile(full_path.c_str()))																	// if it's not a directory, skip it
 			{
 				continue;
@@ -773,7 +775,8 @@ void LLFloaterUIPreview::onClickDisplayFloater(S32 caller_id)
 // Saves the current floater/panel
 void LLFloaterUIPreview::onClickSaveFloater(S32 caller_id)
 {
-	displayFloater(TRUE, caller_id, true);
+	displayFloater(TRUE, caller_id);
+	popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files");
 }
 
 // Saves all floater/panels
@@ -784,25 +787,15 @@ void LLFloaterUIPreview::onClickSaveAll(S32 caller_id)
 	for (int index = 0; index < listSize; index++)
 	{
 		mFileList->selectNthItem(index);
-		displayFloater(TRUE, caller_id, true);
+		displayFloater(TRUE, caller_id);
 	}
-}
-
-// Given path to floater or panel XML file "filename.xml",
-// returns "filename_new.xml"
-static std::string append_new_to_xml_filename(const std::string& path)
-{
-	std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), path);
-	std::string::size_type extension_pos = full_filename.rfind(".xml");
-	full_filename.resize(extension_pos);
-	full_filename += "_new.xml";
-	return full_filename;
+	popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files");
 }
 
 // Actually display the floater
 // Only set up a new live file if this came from a click (at which point there should be no existing live file), rather than from the live file's update itself;
 // otherwise, we get an infinite loop as the live file keeps recreating itself.  That means this function is generally called twice.
-void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID, bool save)
+void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID)
 {
 	// Convince UI that we're in a different language (the one selected on the drop-down menu)
 	LLLocalizationResetForcer reset_forcer(this, ID);						// save old language in reset forcer object (to be reset upon destruction when it falls out of scope)
@@ -843,48 +836,13 @@ void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID, bool save)
 	if(!strncmp(path.c_str(),"floater_",8)
 		|| !strncmp(path.c_str(), "inspect_", 8))		// if it's a floater
 	{
-		if (save)
-		{
-			LLXMLNodePtr floater_write = new LLXMLNode();			
-			(*floaterp)->buildFromFile(path, floater_write);	// just build it
-
-			if (!floater_write->isNull())
-			{
-				std::string full_filename = append_new_to_xml_filename(path);
-				LLFILE* floater_temp = LLFile::fopen(full_filename.c_str(), "w");
-				LLXMLNode::writeHeaderToFile(floater_temp);
-				const bool use_type_decorations = false;
-				floater_write->writeToFile(floater_temp, std::string(), use_type_decorations);
-				fclose(floater_temp);
-			}
-		}
-		else
-		{
-			(*floaterp)->buildFromFile(path);	// just build it
-			(*floaterp)->openFloater((*floaterp)->getKey());
-			(*floaterp)->setCanResize((*floaterp)->isResizable());
-		}
-
+		(*floaterp)->buildFromFile(path);	// just build it
+		(*floaterp)->openFloater((*floaterp)->getKey());
+		(*floaterp)->setCanResize((*floaterp)->isResizable());
 	}
 	else if (!strncmp(path.c_str(),"menu_",5))								// if it's a menu
 	{
-		if (save)
-		{	
-			LLXMLNodePtr menu_write = new LLXMLNode();	
-			LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>(path, gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance(), menu_write);
-
-			if (!menu_write->isNull())
-			{
-				std::string full_filename = append_new_to_xml_filename(path);
-				LLFILE* menu_temp = LLFile::fopen(full_filename.c_str(), "w");
-				LLXMLNode::writeHeaderToFile(menu_temp);
-				const bool use_type_decorations = false;
-				menu_write->writeToFile(menu_temp, std::string(), use_type_decorations);
-				fclose(menu_temp);
-			}
-
-			delete menu;
-		}
+		// former 'save' processing excised
 	}
 	else																// if it is a panel...
 	{
@@ -896,39 +854,21 @@ void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID, bool save)
 		LLPanel::Params panel_params;
 		LLPanel* panel = LLUICtrlFactory::create<LLPanel>(panel_params);	// create a new panel
 
-		if (save)
-		{
-			LLXMLNodePtr panel_write = new LLXMLNode();
-			panel->buildFromFile(path, panel_write);		// build it
-			
-			if (!panel_write->isNull())
-			{
-				std::string full_filename = append_new_to_xml_filename(path);
-				LLFILE* panel_temp = LLFile::fopen(full_filename.c_str(), "w");
-				LLXMLNode::writeHeaderToFile(panel_temp);
-				const bool use_type_decorations = false;
-				panel_write->writeToFile(panel_temp, std::string(), use_type_decorations);
-				fclose(panel_temp);
-			}
-		}
-		else
-		{
-			panel->buildFromFile(path);										// build it
-			LLRect new_size = panel->getRect();								// get its rectangle
-			panel->setOrigin(2,2);											// reset its origin point so it's not offset by -left or other XUI attributes
-			(*floaterp)->setTitle(path);									// use the file name as its title, since panels have no guaranteed meaningful name attribute
-			panel->setUseBoundingRect(TRUE);								// enable the use of its outer bounding rect (normally disabled because it's O(n) on the number of sub-elements)
-			panel->updateBoundingRect();									// update bounding rect
-			LLRect bounding_rect = panel->getBoundingRect();				// get the bounding rect
-			LLRect new_rect = panel->getRect();								// get the panel's rect
-			new_rect.unionWith(bounding_rect);								// union them to make sure we get the biggest one possible
-			LLRect floater_rect = new_rect;
-			floater_rect.stretch(4, 4);
-			(*floaterp)->reshape(floater_rect.getWidth(), floater_rect.getHeight() + floater_header_size);	// reshape floater to match the union rect's dimensions
-			panel->reshape(new_rect.getWidth(), new_rect.getHeight());		// reshape panel to match the union rect's dimensions as well (both are needed)
-			(*floaterp)->addChild(panel);					// add panel as child
-			(*floaterp)->openFloater();						// open floater (needed?)
-		}
+		panel->buildFromFile(path);										// build it
+		LLRect new_size = panel->getRect();								// get its rectangle
+		panel->setOrigin(2,2);											// reset its origin point so it's not offset by -left or other XUI attributes
+		(*floaterp)->setTitle(path);									// use the file name as its title, since panels have no guaranteed meaningful name attribute
+		panel->setUseBoundingRect(TRUE);								// enable the use of its outer bounding rect (normally disabled because it's O(n) on the number of sub-elements)
+		panel->updateBoundingRect();									// update bounding rect
+		LLRect bounding_rect = panel->getBoundingRect();				// get the bounding rect
+		LLRect new_rect = panel->getRect();								// get the panel's rect
+		new_rect.unionWith(bounding_rect);								// union them to make sure we get the biggest one possible
+		LLRect floater_rect = new_rect;
+		floater_rect.stretch(4, 4);
+		(*floaterp)->reshape(floater_rect.getWidth(), floater_rect.getHeight() + floater_header_size);	// reshape floater to match the union rect's dimensions
+		panel->reshape(new_rect.getWidth(), new_rect.getHeight());		// reshape panel to match the union rect's dimensions as well (both are needed)
+		(*floaterp)->addChild(panel);					// add panel as child
+		(*floaterp)->openFloater();						// open floater (needed?)
 	}
 
 	if(ID == 1)
@@ -964,7 +904,7 @@ void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID, bool save)
 	(*floaterp)->center();
 	addDependentFloater(*floaterp);
 
-	if(click && ID == 1 && !save)
+	if(click && ID == 1)
 	{
 		// set up live file to track it
 		if(mLiveFile)
diff --git a/indra/newview/llhints.cpp b/indra/newview/llhints.cpp
index e15862e2a4d6fd2662395142bbeeb3759437eb54..197408b40e8e9fe167e3aa86d6dcf6ea134ae51f 100644
--- a/indra/newview/llhints.cpp
+++ b/indra/newview/llhints.cpp
@@ -171,12 +171,12 @@ LLHintPopup::LLHintPopup(const LLHintPopup::Params& p)
 	}
 	if (p.hint_image.isProvided())
 	{
-		buildFromFile("panel_hint_image.xml", NULL, p);
+		buildFromFile("panel_hint_image.xml", p);
 		getChild<LLIconCtrl>("hint_image")->setImage(p.hint_image());
 	}
 	else
 	{
-		buildFromFile( "panel_hint.xml", NULL, p);
+		buildFromFile( "panel_hint.xml", p);
 	}
 }
 
diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp
index 7650fe92296b825db8d8f873f38f198910d0227c..99b4707158fbc9d8d2ff3119826dfbed8075d951 100644
--- a/indra/newview/llmediactrl.cpp
+++ b/indra/newview/llmediactrl.cpp
@@ -564,32 +564,13 @@ void LLMediaCtrl::navigateTo( std::string url_in, std::string mime_type)
 //
 void LLMediaCtrl::navigateToLocalPage( const std::string& subdir, const std::string& filename_in )
 {
-	std::string language = LLUI::getLanguage();
-	std::string delim = gDirUtilp->getDirDelimiter();
-	std::string filename;
+	std::string filename(gDirUtilp->add(subdir, filename_in));
+	std::string expanded_filename = gDirUtilp->findSkinnedFilename("html", filename);
 
-	filename += subdir;
-	filename += delim;
-	filename += filename_in;
-
-	std::string expanded_filename = gDirUtilp->findSkinnedFilename("html", language, filename);
-
-	if (! gDirUtilp->fileExists(expanded_filename))
+	if (expanded_filename.empty())
 	{
-		if (language != "en")
-		{
-			expanded_filename = gDirUtilp->findSkinnedFilename("html", "en", filename);
-			if (! gDirUtilp->fileExists(expanded_filename))
-			{
-				llwarns << "File " << subdir << delim << filename_in << "not found" << llendl;
-				return;
-			}
-		}
-		else
-		{
-			llwarns << "File " << subdir << delim << filename_in << "not found" << llendl;
-			return;
-		}
+		llwarns << "File " << filename << "not found" << llendl;
+		return;
 	}
 	if (ensureMediaSourceExists())
 	{
@@ -597,7 +578,6 @@ void LLMediaCtrl::navigateToLocalPage( const std::string& subdir, const std::str
 		mMediaSource->setSize(mTextureWidth, mTextureHeight);
 		mMediaSource->navigateTo(expanded_filename, "text/html", false);
 	}
-
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp
index 88727bf59b4fa58b46150072a5df4e4beb6188bf..9c25e69db006b15297970ad54fe84ce4e63fadd0 100644
--- a/indra/newview/llpreviewscript.cpp
+++ b/indra/newview/llpreviewscript.cpp
@@ -815,7 +815,7 @@ void LLScriptEdCore::onBtnDynamicHelp()
 	if (!live_help_floater)
 	{
 		live_help_floater = new LLFloater(LLSD());
-		live_help_floater->buildFromFile("floater_lsl_guide.xml", NULL);
+		live_help_floater->buildFromFile("floater_lsl_guide.xml");
 		LLFloater* parent = dynamic_cast<LLFloater*>(getParent());
 		llassert(parent);
 		if (parent)
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 42648b82c2df2c8aa3f64f535f0b5222590a05f9..3d1fd74ba68722a013bc5e18c9c17134178186a4 100755
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -3307,11 +3307,8 @@ bool process_login_success_response()
 	{
 		// replace the default help URL format
 		gSavedSettings.setString("HelpURLFormat",text);
-		
-		// don't fall back to Standalone's pre-connection static help
-		gSavedSettings.setBOOL("HelpUseLocal", false);
 	}
-			
+
 	std::string home_location = response["home"];
 	if(!home_location.empty())
 	{
diff --git a/indra/newview/llsyswellwindow.cpp b/indra/newview/llsyswellwindow.cpp
index 0cb6c850122e88091e7d9f5047217b48e6a46dd5..2002647fef1797cb734b08f0de8c57bddf5c661b 100644
--- a/indra/newview/llsyswellwindow.cpp
+++ b/indra/newview/llsyswellwindow.cpp
@@ -242,7 +242,7 @@ LLIMWellWindow::RowPanel::RowPanel(const LLSysWellWindow* parent, const LLUUID&
 		S32 chicletCounter, const std::string& name, const LLUUID& otherParticipantId) :
 		LLPanel(LLPanel::Params()), mChiclet(NULL), mParent(parent)
 {
-	buildFromFile( "panel_activeim_row.xml", NULL);
+	buildFromFile( "panel_activeim_row.xml");
 
 	// Choose which of the pre-created chiclets (IM/group) to use.
 	// The other one gets hidden.
@@ -356,7 +356,7 @@ LLIMWellWindow::ObjectRowPanel::ObjectRowPanel(const LLUUID& notification_id, bo
  : LLPanel()
  , mChiclet(NULL)
 {
-	buildFromFile( "panel_active_object_row.xml", NULL);
+	buildFromFile( "panel_active_object_row.xml");
 
 	initChiclet(notification_id);
 
diff --git a/indra/newview/lltoast.cpp b/indra/newview/lltoast.cpp
index 0eec7f0afd153f50cbdf9f76c7a175131e7a74c1..9dfb29b905c96df4f4882f4a5f1ab503a1e7758e 100644
--- a/indra/newview/lltoast.cpp
+++ b/indra/newview/lltoast.cpp
@@ -118,7 +118,7 @@ LLToast::LLToast(const LLToast::Params& p)
 {
 	mTimer.reset(new LLToastLifeTimer(this, p.lifetime_secs));
 
-	buildFromFile("panel_toast.xml", NULL);
+	buildFromFile("panel_toast.xml");
 
 	setCanDrag(FALSE);
 
diff --git a/indra/newview/llviewerhelp.cpp b/indra/newview/llviewerhelp.cpp
index a8a918f259b62a54d66e09e79a3dd1a20ae15652..04c2e27c9d0bde394c9bdfc95778b999e9e2f484 100644
--- a/indra/newview/llviewerhelp.cpp
+++ b/indra/newview/llviewerhelp.cpp
@@ -71,12 +71,6 @@ LLHelpHandler gHelpHandler;
 
 std::string LLViewerHelp::getURL(const std::string &topic)
 {
-	// allow overriding the help server with a local help file
-	if( gSavedSettings.getBOOL("HelpUseLocal") )
-	{
-		return "__local";
-	}
-
 	// if the help topic is empty, use the default topic
 	std::string help_topic = topic;
 	if (help_topic.empty())
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 1eb4bedfaf9b47114be4f6c85a0962d7a61eef32..47059b0b8c3f9bad975f76dce650f7e935c5d3a5 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -1184,12 +1184,9 @@ void LLViewerMedia::clearAllCookies()
 	LLDirIterator dir_iter(base_dir, "*_*");
 	while (dir_iter.next(filename))
 	{
-		target = base_dir;
-		target += filename;
-		target += gDirUtilp->getDirDelimiter();
-		target += "browser_profile";
-		target += gDirUtilp->getDirDelimiter();
-		target += "cookies";
+		target = gDirUtilp->add(base_dir, filename);
+		gDirUtilp->append(target, "browser_profile");
+		gDirUtilp->append(target, "cookies");
 		lldebugs << "target = " << target << llendl;
 		if(LLFile::isfile(target))
 		{	
@@ -1197,10 +1194,8 @@ void LLViewerMedia::clearAllCookies()
 		}
 		
 		// Other accounts may have new-style cookie files too -- delete them as well
-		target = base_dir;
-		target += filename;
-		target += gDirUtilp->getDirDelimiter();
-		target += PLUGIN_COOKIE_FILE_NAME;
+		target = gDirUtilp->add(base_dir, filename);
+		gDirUtilp->append(target, PLUGIN_COOKIE_FILE_NAME);
 		lldebugs << "target = " << target << llendl;
 		if(LLFile::isfile(target))
 		{	
diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp
index e4669cde345f0b89a34c0d32679181a386fb9467..49d7e8b8424d20d1167bc9944c7e061313a9423b 100644
--- a/indra/newview/llviewertexturelist.cpp
+++ b/indra/newview/llviewertexturelist.cpp
@@ -1686,49 +1686,43 @@ struct UIImageDeclarations : public LLInitParam::Block<UIImageDeclarations>
 
 bool LLUIImageList::initFromFile()
 {
-	// construct path to canonical textures.xml in default skin dir
-	std::string base_file_path = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "default", "textures", "textures.xml");
+	// Look for textures.xml in all the right places. Pass
+	// constraint=LLDir::ALL_SKINS because we want to overlay textures.xml
+	// from all the skins directories.
+	std::vector<std::string> textures_paths =
+		gDirUtilp->findSkinnedFilenames(LLDir::TEXTURES, "textures.xml", LLDir::ALL_SKINS);
+	std::vector<std::string>::const_iterator pi(textures_paths.begin()), pend(textures_paths.end());
+	if (pi == pend)
+	{
+		llwarns << "No textures.xml found in skins directories" << llendl;
+		return false;
+	}
 
+	// The first (most generic) file gets special validations
 	LLXMLNodePtr root;
-
-	if (!LLXMLNode::parseFile(base_file_path, root, NULL))
+	if (!LLXMLNode::parseFile(*pi, root, NULL))
 	{
-		llwarns << "Unable to parse UI image list file " << base_file_path << llendl;
+		llwarns << "Unable to parse UI image list file " << *pi << llendl;
 		return false;
 	}
 	if (!root->hasAttribute("version"))
 	{
-		llwarns << "No valid version number in UI image list file " << base_file_path << llendl;
+		llwarns << "No valid version number in UI image list file " << *pi << llendl;
 		return false;
 	}
 
 	UIImageDeclarations images;
 	LLXUIParser parser;
-	parser.readXUI(root, images, base_file_path);
-
-	// add components defined in current skin
-	std::string skin_update_path = gDirUtilp->getSkinDir() 
-									+ gDirUtilp->getDirDelimiter() 
-									+ "textures"
-									+ gDirUtilp->getDirDelimiter()
-									+ "textures.xml";
-	LLXMLNodePtr update_root;
-	if (skin_update_path != base_file_path
-		&& LLXMLNode::parseFile(skin_update_path, update_root, NULL))
-	{
-		parser.readXUI(update_root, images, skin_update_path);
-	}
-
-	// add components defined in user override of current skin
-	skin_update_path = gDirUtilp->getUserSkinDir() 
-						+ gDirUtilp->getDirDelimiter() 
-						+ "textures"
-						+ gDirUtilp->getDirDelimiter()
-						+ "textures.xml";
-	if (skin_update_path != base_file_path
-		&& LLXMLNode::parseFile(skin_update_path, update_root, NULL))
-	{
-		parser.readXUI(update_root, images, skin_update_path);
+	parser.readXUI(root, images, *pi);
+
+	// add components defined in the rest of the skin paths
+	while (++pi != pend)
+	{
+		LLXMLNodePtr update_root;
+		if (LLXMLNode::parseFile(*pi, update_root, NULL))
+		{
+			parser.readXUI(update_root, images, *pi);
+		}
 	}
 
 	if (!images.validateBlock()) return false;
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index c3af0f6a511f4152eec88a4210d0c9a1a05d2853..44e6d70546af0cfebec26ba1ecf0f86e6895bead 100755
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -1692,8 +1692,7 @@ LLViewerWindow::LLViewerWindow(const Params& p)
 	LLFontGL::initClass( gSavedSettings.getF32("FontScreenDPI"),
 								mDisplayScale.mV[VX],
 								mDisplayScale.mV[VY],
-								gDirUtilp->getAppRODataDir(),
-								LLUI::getXUIPaths());
+								gDirUtilp->getAppRODataDir());
 	
 	// Create container for all sub-views
 	LLView::Params rvp;
@@ -4764,8 +4763,7 @@ void LLViewerWindow::initFonts(F32 zoom_factor)
 	LLFontGL::initClass( gSavedSettings.getF32("FontScreenDPI"),
 								mDisplayScale.mV[VX] * zoom_factor,
 								mDisplayScale.mV[VY] * zoom_factor,
-								gDirUtilp->getAppRODataDir(),
-								LLUI::getXUIPaths());
+								gDirUtilp->getAppRODataDir());
 	// Force font reloads, which can be very slow
 	LLFontGL::loadDefaultFonts();
 }
diff --git a/indra/newview/llwaterparammanager.cpp b/indra/newview/llwaterparammanager.cpp
index e38611233486a25385695bfea34cf5f420eb3608..4f52ff977829a7132433d046556443121c9fe9f4 100644
--- a/indra/newview/llwaterparammanager.cpp
+++ b/indra/newview/llwaterparammanager.cpp
@@ -100,7 +100,7 @@ void LLWaterParamManager::loadPresetsFromDir(const std::string& dir)
 			break; // no more files
 		}
 
-		std::string path = dir + file;
+		std::string path = gDirUtilp->add(dir, file);
 		if (!loadPreset(path))
 		{
 			llwarns << "Error loading water preset from " << path << llendl;
diff --git a/indra/newview/llwlparammanager.cpp b/indra/newview/llwlparammanager.cpp
index 49d9d44d74995b46d8c26b1cfc873ede63d9fbba..6077208799a654e663a974166b31f0386fcb7b92 100644
--- a/indra/newview/llwlparammanager.cpp
+++ b/indra/newview/llwlparammanager.cpp
@@ -283,7 +283,7 @@ void LLWLParamManager::loadPresetsFromDir(const std::string& dir)
 			break; // no more files
 		}
 
-		std::string path = dir + file;
+		std::string path = gDirUtilp->add(dir, file);
 		if (!loadPreset(path))
 		{
 			llwarns << "Error loading sky preset from " << path << llendl;
diff --git a/indra/newview/skins/paths.xml b/indra/newview/skins/paths.xml
deleted file mode 100644
index 3c0da041c770566a813f364829177d42da85e1bb..0000000000000000000000000000000000000000
--- a/indra/newview/skins/paths.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<paths> 
-	<directory>
-    <subdir>xui</subdir>
-    <subdir>en</subdir>
-  </directory>
-	<directory>
-    <subdir>xui</subdir>
-    <subdir>[LANGUAGE]</subdir>
-  </directory>
-</paths>
\ No newline at end of file
diff --git a/indra/newview/tests/lldir_stub.cpp b/indra/newview/tests/lldir_stub.cpp
index 18cf4e7419e02ded99b3ee73d619d6db2ca20533..3c0a4377d80012d4bebbd22ec78ccf9c82d769a7 100644
--- a/indra/newview/tests/lldir_stub.cpp
+++ b/indra/newview/tests/lldir_stub.cpp
@@ -32,7 +32,7 @@ BOOL LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask
 void LLDir::setChatLogsDir(const std::string &path) {}
 void LLDir::setPerAccountChatLogsDir(const std::string &first, const std::string &last) {}
 void LLDir::setLindenUserDir(const std::string &first, const std::string &last) {}
-void LLDir::setSkinFolder(const std::string &skin_folder) {}
+void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& language) {}
 bool LLDir::setCacheDir(const std::string &path) { return true; }
 void LLDir::dumpCurrentDirectories() {}
 
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 99dcc90f8f2a59423fa38e9ee3735cc691ab42aa..8bf2eef155a88fa76ac0ae2dde4b85c1f23c644c 100644
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -114,7 +114,6 @@ class ViewerManifest(LLManifest):
 
             # skins
             if self.prefix(src="skins"):
-                    self.path("paths.xml")
                     # include the entire textures directory recursively
                     if self.prefix(src="*/textures"):
                             self.path("*/*.tga")
@@ -132,11 +131,18 @@ class ViewerManifest(LLManifest):
                     self.path("*/*.xml")
 
                     # Local HTML files (e.g. loading screen)
-                    if self.prefix(src="*/html"):
+                    # The claim is that we never use local html files any
+                    # longer. But rather than commenting out this block, let's
+                    # rename every html subdirectory as html.old. That way, if
+                    # we're wrong, a user actually does have the relevant
+                    # files; s/he just needs to rename every html.old
+                    # directory back to html to recover them.
+                    if self.prefix(src="*/html", dst="*/html.old"):
                             self.path("*.png")
                             self.path("*/*/*.html")
                             self.path("*/*/*.gif")
                             self.end_prefix("*/html")
+
                     self.end_prefix("skins")
 
             # local_assets dir (for pre-cached textures)
diff --git a/indra/test/lluuidhashmap_tut.cpp b/indra/test/lluuidhashmap_tut.cpp
index 0544e832cedaeaa9ec650f738dbd53de859062a3..408bc3faf18aad8613adc708f0f510fe8479e198 100644
--- a/indra/test/lluuidhashmap_tut.cpp
+++ b/indra/test/lluuidhashmap_tut.cpp
@@ -30,6 +30,10 @@
 #include "linden_common.h"
 #include "lluuidhashmap.h"
 #include "llsdserialize.h"
+#include "lldir.h"
+#include "stringize.h"
+#include <iostream>
+#include <fstream>
 
 namespace tut
 {
@@ -79,40 +83,133 @@ namespace tut
 	template<> template<>
 	void hash_index_object_t::test<1>()
 	{
-		LLUUIDHashMap<UUIDTableEntry, 32>	hashTable(UUIDTableEntry::uuidEq, UUIDTableEntry());
+		set_test_name("stress test");
+		// As of 2012-10-10, I (nat) have observed sporadic failures of this
+		// test: "set/get did not work." The trouble is that since test data
+		// are randomly generated with every run, it is impossible to debug a
+		// test failure. One is left with the uneasy suspicion that
+		// LLUUID::generate() can sometimes produce duplicates even within the
+		// moderately small number requested here. Since rerunning the test
+		// generally allows it to pass, it's too easy to shrug and forget it.
+		// The following code is intended to support reproducing such test
+		// failures. The idea is that, on test failure, we save the generated
+		// data to a canonical filename in a temp directory. Then on every
+		// subsequent run, we check for that filename. If it exists, we reload
+		// that specific data rather than generating fresh data -- which
+		// should presumably reproduce the same test failure. But we inform
+		// the user that to resume normal (random) test runs, s/he need only
+		// delete that file. And since it's in a temp directory, sooner or
+		// later the system will clean it up anyway.
+		const char* tempvar = "TEMP";
+		const char* tempdir = getenv(tempvar); // Windows convention
+		if (! tempdir)
+		{
+			tempvar = "TMPDIR";
+			tempdir = getenv(tempvar); // Mac convention
+		}
+		if (! tempdir)
+		{
+			// reset tempvar to the first var we check; it's just a
+			// recommendation
+			tempvar = "TEMP";
+			tempdir = "/tmp";		// Posix in general
+		}
+		std::string savefile(gDirUtilp->add(tempdir, "lluuidhashmap_tut.save.txt"));
 		const int numElementsToCheck = 32*256*32;
-		std::vector<LLUUID> idList(numElementsToCheck);
-		int i;
-		
-		for (i = 0; i < numElementsToCheck; i++)
+		std::vector<LLUUID> idList;
+		if (gDirUtilp->fileExists(savefile))
 		{
-			LLUUID id;
-			id.generate();
-			UUIDTableEntry entry(id, i);
-			hashTable.set(id, entry);
-			idList[i] = id;
+			// We have saved data from a previous failed run. Reload that data.
+			std::ifstream inf(savefile.c_str());
+			if (! inf.is_open())
+			{
+				fail(STRINGIZE("Although save file '" << savefile << "' exists, it cannot be opened"));
+			}
+			std::string item;
+			while (std::getline(inf, item))
+			{
+				idList.push_back(LLUUID(item));
+			}
+			std::cout << "Reloaded " << idList.size() << " items from '" << savefile << "'";
+			if (idList.size() != numElementsToCheck)
+			{
+				std::cout << " (expected " << numElementsToCheck << ")";
+			}
+			std::cout << " -- delete this file to generate new data" << std::endl;
+		}
+		else
+		{
+			// savefile does not exist (normal case): regenerate idList from
+			// scratch.
+			for (int i = 0; i < numElementsToCheck; ++i)
+			{
+				LLUUID id;
+				id.generate();
+				idList.push_back(id);
+			}
 		}
 
-		for (i = 0; i < numElementsToCheck; i++)
+		LLUUIDHashMap<UUIDTableEntry, 32>	hashTable(UUIDTableEntry::uuidEq, UUIDTableEntry());
+		int i;
+		
+		for (i = 0; i < idList.size(); ++i)
 		{
-			LLUUID idToCheck = idList[i];
-			UUIDTableEntry entryToCheck = hashTable.get(idToCheck);
-			ensure("set/get did not work", entryToCheck.getID() == idToCheck && entryToCheck.getValue() == (size_t)i);
+			UUIDTableEntry entry(idList[i], i);
+			hashTable.set(idList[i], entry);
 		}
 
-		for (i = 0; i < numElementsToCheck; i++)
+		try
 		{
-			LLUUID idToCheck = idList[i];
-			if (i % 2 != 0)
+			for (i = 0; i < idList.size(); i++)
 			{
-				hashTable.remove(idToCheck);
+				LLUUID idToCheck = idList[i];
+				UUIDTableEntry entryToCheck = hashTable.get(idToCheck);
+				ensure_equals(STRINGIZE("set/get ID (entry " << i << ")").c_str(),
+							  entryToCheck.getID(), idToCheck);
+				ensure_equals(STRINGIZE("set/get value (ID " << idToCheck << ")").c_str(),
+							  entryToCheck.getValue(), (size_t)i);
 			}
-		}
 
-		for (i = 0; i < numElementsToCheck; i++)
+			for (i = 0; i < idList.size(); i++)
+			{
+				LLUUID idToCheck = idList[i];
+				if (i % 2 != 0)
+				{
+					hashTable.remove(idToCheck);
+				}
+			}
+
+			for (i = 0; i < idList.size(); i++)
+			{
+				LLUUID idToCheck = idList[i];
+				ensure("remove or check did not work", (i % 2 == 0 && hashTable.check(idToCheck)) || (i % 2 != 0 && !hashTable.check(idToCheck)));
+			}
+		}
+		catch (const failure&)
 		{
-			LLUUID idToCheck = idList[i];
-			ensure("remove or check did not work", (i % 2 == 0 && hashTable.check(idToCheck)) || (i % 2 != 0 && !hashTable.check(idToCheck)));
+			// One of the above tests failed. Try to save idList to repro with
+			// a later run.
+			std::ofstream outf(savefile.c_str());
+			if (! outf.is_open())
+			{
+				// Sigh, don't use fail() here because we want to preserve
+				// the original test failure.
+				std::cout << "Cannot open file '" << savefile
+						  << "' to save data -- check and fix " << tempvar << std::endl;
+			}
+			else
+			{
+				// outf.is_open()
+				for (int i = 0; i < idList.size(); ++i)
+				{
+					outf << idList[i] << std::endl;
+				}
+				std::cout << "Saved " << idList.size() << " entries to '" << savefile
+						  << "' -- rerun test to debug with these" << std::endl;
+			}
+			// re-raise the same exception -- we WANT this test failure to
+			// be reported! We just needed to save the data on the way out.
+			throw;
 		}
 	}
 
diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
index 7c016fecf9253b98a2319fb2732bcf508ae39957..db52e6c55f81e2bee0ca04541fac5ac1198ef431 100644
--- a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
+++ b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
@@ -78,7 +78,9 @@ S32 LLDir::deleteFilesInDir(const std::string &dirname,
 void LLDir::setChatLogsDir(const std::string &path){}		
 void LLDir::setPerAccountChatLogsDir(const std::string &username){}
 void LLDir::setLindenUserDir(const std::string &username){}		
-void LLDir::setSkinFolder(const std::string &skin_folder){}
+void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& language){}
+std::string LLDir::getSkinFolder() const { return "default"; }
+std::string LLDir::getLanguage() const { return "en"; }
 bool LLDir::setCacheDir(const std::string &path){ return true; }
 void LLDir::dumpCurrentDirectories() {}