diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp
index e5089f028fe1cb9f12c4eaf72b76e9634a3b7d9a..60359ca304df1aceedb38150dc52e5f149e8ffb0 100644
--- a/indra/llappearance/llavatarappearance.cpp
+++ b/indra/llappearance/llavatarappearance.cpp
@@ -1566,9 +1566,10 @@ BOOL LLAvatarAppearance::allocateCollisionVolumes( U32 num )
         delete_and_clear_array(mCollisionVolumes);
         mNumCollisionVolumes = 0;
 
-        mCollisionVolumes = new LLAvatarJointCollisionVolume[num];
+        mCollisionVolumes = new(std::nothrow) LLAvatarJointCollisionVolume[num];
         if (!mCollisionVolumes)
         {
+            LL_WARNS() << "Failed to allocate collision volumes" << LL_ENDL;
             return FALSE;
         }
         
diff --git a/indra/llappearance/llwearabletype.cpp b/indra/llappearance/llwearabletype.cpp
index 207e0c4011f2e9c9496ea6c34b487d991b92f8a8..cd602b43b4452d968d8db0069d20638dc9bb1697 100644
--- a/indra/llappearance/llwearabletype.cpp
+++ b/indra/llappearance/llwearabletype.cpp
@@ -94,7 +94,7 @@ LLWearableDictionary::LLWearableDictionary()
 
 	addEntry(LLWearableType::WT_PHYSICS,      new WearableEntry("physics",     "New Physics",		LLAssetType::AT_CLOTHING, 	LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, TRUE));
 
-	addEntry(LLWearableType::WT_INVALID,      new WearableEntry("invalid",     "Invalid Wearable", 	LLAssetType::AT_NONE, 		LLInventoryType::ICONNAME_NONE, FALSE, FALSE));
+	addEntry(LLWearableType::WT_INVALID,      new WearableEntry("invalid",     "Invalid Wearable", 	LLAssetType::AT_NONE, 		LLInventoryType::ICONNAME_UNKNOWN, FALSE, FALSE));
 	addEntry(LLWearableType::WT_NONE,      	  new WearableEntry("none",        "Invalid Wearable", 	LLAssetType::AT_NONE, 		LLInventoryType::ICONNAME_NONE, FALSE, FALSE));
 }
 
diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp
index 77e57b14f5d05d0979ff965cd95456422dfb4780..6ab61689fd0532a8f67c65576ed2cd0c7bfcda94 100644
--- a/indra/llaudio/llaudiodecodemgr.cpp
+++ b/indra/llaudio/llaudiodecodemgr.cpp
@@ -265,9 +265,19 @@ BOOL LLVorbisDecodeState::initDecode()
 		mInFilep = NULL;
 		return FALSE;
 	}
-	
-	mWAVBuffer.reserve(size_guess);
-	mWAVBuffer.resize(WAV_HEADER_SIZE);
+
+	try
+	{
+		mWAVBuffer.reserve(size_guess);
+		mWAVBuffer.resize(WAV_HEADER_SIZE);
+	}
+	catch (std::bad_alloc)
+	{
+		LL_WARNS("AudioEngine") << "Out of memory when trying to alloc buffer: " << size_guess << LL_ENDL;
+		delete mInFilep;
+		mInFilep = NULL;
+		return FALSE;
+	}
 
 	{
 		// write the .wav format header
diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp
index f12c64023ab66a9be6013fd294f879d484439169..330d81298565d08fb5689571af6d17e2a0cebdf8 100644
--- a/indra/llcharacter/llkeyframemotion.cpp
+++ b/indra/llcharacter/llkeyframemotion.cpp
@@ -579,8 +579,15 @@ LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *charact
 	else
 	{
 		anim_file_size = anim_file->getSize();
-		anim_data = new U8[anim_file_size];
-		success = anim_file->read(anim_data, anim_file_size);	/*Flawfinder: ignore*/
+		anim_data = new(std::nothrow) U8[anim_file_size];
+		if (anim_data)
+		{
+			success = anim_file->read(anim_data, anim_file_size);	/*Flawfinder: ignore*/
+		}
+		else
+		{
+			LL_WARNS() << "Failed to allocate buffer: " << anim_file_size << mID << LL_ENDL;
+		}
 		delete anim_file;
 		anim_file = NULL;
 	}
diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp
index 4304db36bea26b30380a85184f47cc5f45dbd8f7..7e5a157cdfedd1281067df86504ac5f191daa586 100644
--- a/indra/llcommon/llassettype.cpp
+++ b/indra/llcommon/llassettype.cpp
@@ -95,6 +95,7 @@ LLAssetDictionary::LLAssetDictionary()
 	addEntry(LLAssetType::AT_MESH,              new AssetEntry("MESH",              "mesh",     "mesh",             false,      false,      false));
 	addEntry(LLAssetType::AT_WIDGET,            new AssetEntry("WIDGET",            "widget",   "widget",           false,      false,      false));
 	addEntry(LLAssetType::AT_PERSON,            new AssetEntry("PERSON",            "person",   "person",           false,      false,      false));
+	addEntry(LLAssetType::AT_UNKNOWN,           new AssetEntry("UNKNOWN",           "invalid",  NULL,               false,      false,      false));
 	addEntry(LLAssetType::AT_NONE, 				new AssetEntry("NONE",				"-1",		NULL,		  		FALSE,		FALSE,		FALSE));
 
 };
@@ -156,7 +157,7 @@ LLAssetType::EType LLAssetType::lookup(const std::string& type_name)
 			return iter->first;
 		}
 	}
-	return AT_NONE;
+	return AT_UNKNOWN;
 }
 
 // static
diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h
index b849be9f16ad3a02474c7334631cf418f2b286e2..79ab3d7efec66391b3d643407c1b049de8cadda6 100644
--- a/indra/llcommon/llassettype.h
+++ b/indra/llcommon/llassettype.h
@@ -130,7 +130,7 @@ class LL_COMMON_API LLAssetType
 			// | 4. ADD TO LLViewerAssetType.cpp                         |
 			// | 5. ADD TO DEFAULT_ASSET_FOR_INV in LLInventoryType.cpp  |
 			// +*********************************************************+
-
+		AT_UNKNOWN = 255,
 		AT_NONE = -1
 	};
 
diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index dca03cfe04fb7c279040e72a52ec38e1fdace852..1a4dd2ca9943c67b2383580972cd3883147b75dd 100644
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -889,7 +889,7 @@ void LLImageRaw::setDataAndSize(U8 *data, S32 width, S32 height, S8 components)
 
 bool LLImageRaw::resize(U16 width, U16 height, S8 components)
 {
-	if ((getWidth() == width) && (getHeight() == height) && (getComponents() == components))
+	if ((getWidth() == width) && (getHeight() == height) && (getComponents() == components) && !isBufferInvalid())
 	{
 		return true;
 	}
@@ -898,7 +898,7 @@ bool LLImageRaw::resize(U16 width, U16 height, S8 components)
 
 	allocateDataSize(width,height,components);
 
-	return true;
+	return !isBufferInvalid();
 }
 
 bool LLImageRaw::setSubImage(U32 x_pos, U32 y_pos, U32 width, U32 height,
diff --git a/indra/llimage/llimagebmp.cpp b/indra/llimage/llimagebmp.cpp
index 2cdd26c22b4616c07a212821f9d9a632b4222403..867b2bb47bac2311de1ea1c6a24be455163c9ce2 100644
--- a/indra/llimage/llimagebmp.cpp
+++ b/indra/llimage/llimagebmp.cpp
@@ -318,7 +318,7 @@ bool LLImageBMP::updateData()
 
 	if( 0 != mColorPaletteColors )
 	{
-		mColorPalette = new U8[color_palette_size];
+		mColorPalette = new(std::nothrow) U8[color_palette_size];
 		if (!mColorPalette)
 		{
 			LL_ERRS() << "Out of memory in LLImageBMP::updateData()" << LL_ENDL;
@@ -344,7 +344,11 @@ bool LLImageBMP::decode(LLImageRaw* raw_image, F32 decode_time)
 		return false;
 	}
 	
-	raw_image->resize(getWidth(), getHeight(), 3);
+	if (!raw_image->resize(getWidth(), getHeight(), 3))
+	{
+		setLastError("llimagebmp failed to resize image!");
+		return false;
+	}
 
 	U8* src = mdata + mBitmapOffset;
 	U8* dst = raw_image->getData();
diff --git a/indra/llimage/llimagedxt.cpp b/indra/llimage/llimagedxt.cpp
index 3a7319d765d674d20592370931a9086ee764efce..36317a5ba883c39cd973549c308ba66a658e7f73 100644
--- a/indra/llimage/llimagedxt.cpp
+++ b/indra/llimage/llimagedxt.cpp
@@ -289,7 +289,11 @@ bool LLImageDXT::decode(LLImageRaw* raw_image, F32 time)
 		return false;
 	}
 
-	raw_image->resize(width, height, ncomponents);
+	if (!raw_image->resize(width, height, ncomponents))
+	{
+		setLastError("llImageDXT failed to resize image!");
+		return false;
+	}
 	memcpy(raw_image->getData(), data, image_size);	/* Flawfinder: ignore */
 
 	return true;
diff --git a/indra/llimage/llimagejpeg.cpp b/indra/llimage/llimagejpeg.cpp
index 60b2d0faa53fc2c11c90c24a36b6c23d18c6efdc..3b1b060c023b4ffe4146bcb8f972bf20644d5852 100644
--- a/indra/llimage/llimagejpeg.cpp
+++ b/indra/llimage/llimagejpeg.cpp
@@ -29,6 +29,7 @@
 #include "llimagejpeg.h"
 
 #include "llerror.h"
+#include "llexception.h"
 
 jmp_buf	LLImageJPEG::sSetjmpBuffer ;
 LLImageJPEG::LLImageJPEG(S32 quality) 
@@ -256,7 +257,10 @@ bool LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time)
 
 		setSize(cinfo.image_width, cinfo.image_height, 3); // Force to 3 components (RGB)
 
-		raw_image->resize(getWidth(), getHeight(), getComponents());
+		if (!raw_image->resize(getWidth(), getHeight(), getComponents()))
+		{
+			throw std::bad_alloc();
+		}
 		raw_image_data = raw_image->getData();
 		
 		
@@ -311,6 +315,13 @@ bool LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time)
 		jpeg_destroy_decompress(&cinfo);
 	}
 
+	catch (std::bad_alloc)
+	{
+		setLastError( "Out of memory");
+		jpeg_destroy_decompress(&cinfo);
+		return true; // done
+	}
+
 	catch (int)
 	{
 		jpeg_destroy_decompress(&cinfo);
@@ -370,10 +381,11 @@ boolean LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )
   
   // Double the buffer size;
   S32 new_buffer_size = self->mOutputBufferSize * 2;
-  U8* new_buffer = new U8[ new_buffer_size ];
+  U8* new_buffer = new(std::nothrow) U8[ new_buffer_size ];
   if (!new_buffer)
   {
-  	LL_ERRS() << "Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )" << LL_ENDL;
+    self->setLastError("Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )");
+    LLTHROW(LLContinueError("Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )"));
   	return false;
   }
   memcpy( new_buffer, self->mOutputBuffer, self->mOutputBufferSize );	/* Flawfinder: ignore */
@@ -493,7 +505,14 @@ bool LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time )
 	disclaimMem(mOutputBufferSize);
 	mOutputBufferSize = getWidth() * getHeight() * getComponents() + 1024;
 	claimMem(mOutputBufferSize);
-	mOutputBuffer = new U8[ mOutputBufferSize ];
+	mOutputBuffer = new(std::nothrow) U8[ mOutputBufferSize ];
+	if (mOutputBuffer == NULL)
+	{
+		disclaimMem(mOutputBufferSize);
+		mOutputBufferSize = 0;
+		setLastError("Failed to allocate output buffer");
+		return false;
+	}
 
 	const U8* raw_image_data = NULL;
 	S32 row_stride = 0;
diff --git a/indra/llimage/llimagepng.cpp b/indra/llimage/llimagepng.cpp
index a4823ed859d7a1e4fced1f677bece0cf9b379697..c4b98d82603780527a2d0fc24e8d5969b3087ec9 100644
--- a/indra/llimage/llimagepng.cpp
+++ b/indra/llimage/llimagepng.cpp
@@ -125,7 +125,12 @@ bool LLImagePNG::encode(const LLImageRaw* raw_image, F32 encode_time)
 	// Temporary buffer to hold the encoded image. Note: the final image
 	// size should be much smaller due to compression.
 	U32 bufferSize = getWidth() * getHeight() * getComponents() + 8192;
-    U8* tmpWriteBuffer = new U8[ bufferSize ];
+	U8* tmpWriteBuffer = new(std::nothrow) U8[ bufferSize ];
+	if (!tmpWriteBuffer)
+	{
+		setLastError("LLImagePNG::out of memory");
+		return false;
+	}
 
 	// Delegate actual encoding work to wrapper
 	LLPngWrapper pngWrapper;
diff --git a/indra/llimage/llimagetga.cpp b/indra/llimage/llimagetga.cpp
index 7c75aa1e2a7791ffdf83cd4e4ce6d6cd049d6486..88bdae9b80f4fe3f1247759bb4a2f98a51a275ac 100644
--- a/indra/llimage/llimagetga.cpp
+++ b/indra/llimage/llimagetga.cpp
@@ -263,7 +263,7 @@ bool LLImageTGA::updateData()
 		// only allocate memory for one if _we_ intend to use it.
 		if ( (1 == mImageType) || (9 == mImageType)  )
 		{
-			mColorMap = new U8[ color_map_bytes ];  
+			mColorMap = new(std::nothrow) U8[ color_map_bytes ];  
 			if (!mColorMap)
 			{
 				LL_ERRS() << "Out of Memory in bool LLImageTGA::updateData()" << LL_ENDL;
@@ -336,7 +336,11 @@ bool LLImageTGA::decode(LLImageRaw* raw_image, F32 decode_time)
 
 	// Copy everything after the header.
 
-	raw_image->resize(getWidth(), getHeight(), getComponents());
+	if( !raw_image->resize(getWidth(), getHeight(), getComponents()))
+	{
+		setLastError("LLImageTGA::out of memory");
+		return false;
+	}
 
 	if( (getComponents() != 1) &&
 		(getComponents() != 3) &&
@@ -346,6 +350,11 @@ bool LLImageTGA::decode(LLImageRaw* raw_image, F32 decode_time)
 		return false;
 	}
 
+	if( raw_image->isBufferInvalid())
+	{
+		setLastError("LLImageTGA::out of memory");
+		return false;
+	}
 
 	if( mOriginRightBit )
 	{
@@ -395,6 +404,11 @@ bool LLImageTGA::decodeTruecolor( LLImageRaw* raw_image, bool rle, bool flipped
 				// alpha was entirely opaque
 				// convert to 24 bit image
 				LLPointer<LLImageRaw> compacted_image = new LLImageRaw(raw_image->getWidth(), raw_image->getHeight(), 3);
+				if (compacted_image->isBufferInvalid())
+				{
+					success = false;
+					break;
+				}
 				compacted_image->copy(raw_image);
 				raw_image->resize(raw_image->getWidth(), raw_image->getHeight(), 3);
 				raw_image->copy(compacted_image);
@@ -411,9 +425,16 @@ bool LLImageTGA::decodeTruecolor( LLImageRaw* raw_image, bool rle, bool flipped
 			// alpha was entirely opaque
 			// convert to 24 bit image
 			LLPointer<LLImageRaw> compacted_image = new LLImageRaw(raw_image->getWidth(), raw_image->getHeight(), 3);
-			compacted_image->copy(raw_image);
-			raw_image->resize(raw_image->getWidth(), raw_image->getHeight(), 3);
-			raw_image->copy(compacted_image);
+			if (compacted_image->isBufferInvalid())
+			{
+				success = false;
+			}
+			else
+			{
+				compacted_image->copy(raw_image);
+				raw_image->resize(raw_image->getWidth(), raw_image->getHeight(), 3);
+				raw_image->copy(compacted_image);
+			}
 		}
 	}
 	
@@ -1053,7 +1074,11 @@ bool LLImageTGA::decodeAndProcess( LLImageRaw* raw_image, F32 domain, F32 weight
 		return false;
 	}
 
-	raw_image->resize(getWidth(), getHeight(), getComponents());
+	if( !raw_image->resize(getWidth(), getHeight(), getComponents()) )
+	{
+		LL_ERRS() << "LLImageTGA: Failed to resize image" << LL_ENDL;
+		return false;
+	}
 
 	U8* dst = raw_image->getData();
 	U8* src = getData() + mDataOffset;
diff --git a/indra/llimage/llpngwrapper.cpp b/indra/llimage/llpngwrapper.cpp
index eb70b78a362ff104e4e4e78e88bfafa2dfd2703a..f298764cc0743a8a5856c741a7645673e920961f 100644
--- a/indra/llimage/llpngwrapper.cpp
+++ b/indra/llimage/llpngwrapper.cpp
@@ -173,8 +173,11 @@ BOOL LLPngWrapper::readPng(U8* src, S32 dataSize, LLImageRaw* rawImage, ImageInf
 		// data space
 		if (rawImage != NULL)
 		{
-			rawImage->resize(static_cast<U16>(mWidth),
-				static_cast<U16>(mHeight), mChannels);
+			if (!rawImage->resize(static_cast<U16>(mWidth),
+				static_cast<U16>(mHeight), mChannels))
+			{
+				LLTHROW(PngError("Failed to resize image"));
+			}
 			U8 *dest = rawImage->getData();
 			int offset = mWidth * mChannels;
 
@@ -207,6 +210,12 @@ BOOL LLPngWrapper::readPng(U8* src, S32 dataSize, LLImageRaw* rawImage, ImageInf
 		releaseResources();
 		return (FALSE);
 	}
+	catch (std::bad_alloc)
+	{
+		mErrorMessage = "LLPngWrapper";
+		releaseResources();
+		return (FALSE);
+	}
 
 	// Clean up and return
 	releaseResources();
diff --git a/indra/llinventory/llinventorytype.cpp b/indra/llinventory/llinventorytype.cpp
index d1e6807f52138a3fa5fd2ed6a64b5433d6a337b9..7399e1bca4d0be8e71823914619e2faba8e23ca3 100644
--- a/indra/llinventory/llinventorytype.cpp
+++ b/indra/llinventory/llinventorytype.cpp
@@ -181,7 +181,7 @@ LLInventoryType::EType LLInventoryType::defaultForAssetType(LLAssetType::EType a
 	}
 	else
 	{
-		return IT_NONE;
+		return IT_UNKNOWN;
 	}
 }
 
diff --git a/indra/llinventory/llinventorytype.h b/indra/llinventory/llinventorytype.h
index fc3c78cf5062c7c0333a68585733ab2aa1320c1e..891ab217fd7f740b4574e645463ff6a0e1ce52f9 100644
--- a/indra/llinventory/llinventorytype.h
+++ b/indra/llinventory/llinventorytype.h
@@ -66,6 +66,7 @@ class LLInventoryType
 		IT_PERSON = 24,
 		IT_COUNT = 25,
 
+		IT_UNKNOWN = 255,
 		IT_NONE = -1
 	};
 
@@ -111,6 +112,7 @@ class LLInventoryType
 		ICONNAME_MESH,
 
 		ICONNAME_INVALID,
+		ICONNAME_UNKNOWN,
 		ICONNAME_COUNT,
 		ICONNAME_NONE = -1
 	};
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 89500dcc040939f052afba9bc447ca8353e268ed..40217b2e80ee017d44d16c9760c8032fffd0ffc1 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -1262,7 +1262,8 @@ BOOL LLImageGL::createGLTexture()
 	stop_glerror();
 	if (!mTexName)
 	{
-		LL_ERRS() << "LLImageGL::createGLTexture failed to make an empty texture" << LL_ENDL;
+		LL_WARNS() << "LLImageGL::createGLTexture failed to make an empty texture" << LL_ENDL;
+		return FALSE;
 	}
 
 	return TRUE ;
@@ -1395,7 +1396,16 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
 	}
 	if (!mTexName)
 	{
-		LL_ERRS() << "LLImageGL::createGLTexture failed to make texture" << LL_ENDL;
+		if (old_name)
+		{
+			sGlobalTextureMemory -= mTextureMemory;
+			LLImageGL::deleteTextures(1, &old_name);
+			disclaimMem(mTextureMemory);
+			stop_glerror();
+		}
+
+		LL_WARNS() << "LLImageGL::createGLTexture failed to make texture" << LL_ENDL;
+		return FALSE;
 	}
 
 	if (mUseMipMaps)
diff --git a/indra/mac_crash_logger/CMakeLists.txt b/indra/mac_crash_logger/CMakeLists.txt
index ab2038826101bf3e1bb1f1fce3fbe9267e711c8e..f6c4dfb59da8ef3723a2b1ada85821cb45bf1e0a 100644
--- a/indra/mac_crash_logger/CMakeLists.txt
+++ b/indra/mac_crash_logger/CMakeLists.txt
@@ -85,7 +85,7 @@ add_custom_command(
   COMMAND ${CMAKE_COMMAND}
   ARGS
     -E
-    copy_directory
+    copy_if_different
     ${CMAKE_CURRENT_SOURCE_DIR}/CrashReporter.nib
     ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/mac-crash-logger.app/Contents/Resources/CrashReporter.nib
   )
diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp
index 487496df9a3691a16d809525d721ccc2a2a555c4..6cf93b547e22dbd5f758eea0b5c9b4485681c01a 100644
--- a/indra/newview/llfeaturemanager.cpp
+++ b/indra/newview/llfeaturemanager.cpp
@@ -377,6 +377,43 @@ bool LLFeatureManager::parseFeatureTable(std::string filename)
 
 F32 gpu_benchmark();
 
+#if LL_WINDOWS
+
+static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
+
+U32 exception_benchmark_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop)
+{
+    if (code == STATUS_MSC_EXCEPTION)
+    {
+        // C++ exception, go on
+        return EXCEPTION_CONTINUE_SEARCH;
+    }
+    else
+    {
+        // handle it
+        return EXCEPTION_EXECUTE_HANDLER;
+    }
+}
+
+F32 logExceptionBenchmark()
+{
+    // Todo: make a wrapper/class for SEH exceptions
+    F32 gbps = -1;
+    __try
+    {
+        gbps = gpu_benchmark();
+    }
+    __except (exception_benchmark_filter(GetExceptionCode(), GetExceptionInformation()))
+    {
+        // convert to C++ styled exception
+        char integer_string[32];
+        sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode());
+        throw std::exception(integer_string);
+    }
+    return gbps;
+}
+#endif
+
 bool LLFeatureManager::loadGPUClass()
 {
 	if (!gSavedSettings.getBOOL("SkipBenchmark"))
@@ -385,7 +422,11 @@ bool LLFeatureManager::loadGPUClass()
 		F32 gbps;
 		try
 		{
+#if LL_WINDOWS
+			gbps = logExceptionBenchmark();
+#else
 			gbps = gpu_benchmark();
+#endif
 		}
 		catch (const std::exception& e)
 		{
@@ -400,11 +441,11 @@ bool LLFeatureManager::loadGPUClass()
 		LL_WARNS("RenderInit") << "Unable to get an accurate benchmark; defaulting to class 3" << LL_ENDL;
 		mGPUClass = GPU_CLASS_3;
 	#else
-			if (gGLManager.mGLVersion < 2.f)
+			if (gGLManager.mGLVersion <= 2.f)
 			{
 				mGPUClass = GPU_CLASS_0;
 			}
-			else if (gGLManager.mGLVersion < 3.f)
+			else if (gGLManager.mGLVersion <= 3.f)
 			{
 				mGPUClass = GPU_CLASS_1;
 			}
diff --git a/indra/newview/llfloaternotificationstabbed.cpp b/indra/newview/llfloaternotificationstabbed.cpp
index 4b5fe4989ac413b8fc7dc68dfbaf92c39af19a17..d1679fd936de4032183f028d3203b5aaba86c4b4 100644
--- a/indra/newview/llfloaternotificationstabbed.cpp
+++ b/indra/newview/llfloaternotificationstabbed.cpp
@@ -387,7 +387,8 @@ void LLFloaterNotificationsTabbed::onStoreToast(LLPanel* info_panel, LLUUID id)
     p.notification_name = notify->getName();
     p.transaction_id = payload["transaction_id"];
     p.group_id = payload["group_id"];
-    p.fee =  payload["fee"];
+    p.fee = payload["fee"];
+    p.use_offline_cap = payload["use_offline_cap"].asInteger();
     p.subject = payload["subject"].asString();
     p.message = payload["message"].asString();
     p.sender = payload["sender_name"].asString();
diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp
index 491671c46f8f3c1f79fc7b6b6ae48d4651499806..e76b3d118e324d90363e783bdbce877a4a1cd54d 100644
--- a/indra/newview/llimprocessing.cpp
+++ b/indra/newview/llimprocessing.cpp
@@ -814,10 +814,11 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
 
                 LLSD payload;
                 payload["transaction_id"] = session_id;
-                payload["group_id"] = from_id;
+                payload["group_id"] = from_group ? from_id : aux_id;
                 payload["name"] = name;
                 payload["message"] = message;
                 payload["fee"] = membership_fee;
+                payload["use_offline_cap"] = session_id.isNull() && (offline == IM_OFFLINE);
 
                 LLSD args;
                 args["MESSAGE"] = message;
@@ -1459,8 +1460,12 @@ void LLIMProcessing::requestOfflineMessages()
         // Auto-accepted inventory items may require the avatar object
         // to build a correct name.  Likewise, inventory offers from
         // muted avatars require the mute list to properly mute.
-        if (cap_url.empty())
+        if (cap_url.empty()
+            || gAgent.getRegionCapability("AcceptFriendship").empty()
+            || gAgent.getRegionCapability("AcceptGroupInvite").empty())
         {
+            // Offline messages capability provides no session/transaction ids for message AcceptFriendship and IM_GROUP_INVITATION to work
+            // So make sure we have the caps before using it.
             requestOfflineMessagesLegacy();
         }
         else
@@ -1561,7 +1566,7 @@ void LLIMProcessing::requestOfflineMessagesCoro(std::string url)
             message_data["to_agent_id"].asUUID(),
             IM_OFFLINE,
             (EInstantMessage)message_data["dialog"].asInteger(),
-            LLUUID::null, // session id, fix this for friendship offers to work
+            LLUUID::null, // session id, since there is none we can only use frienship/group invite caps
             message_data["timestamp"].asInteger(),
             message_data["from_agent_name"].asString(),
             message_data["message"].asString(),
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index 90a000c19669bba192ee0deb0136b43633e8dce0..e4b6c3b5540cbe5178476972b07b3ff8083c5dec 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -1406,7 +1406,9 @@ LLInvFVBridge* LLInvFVBridge::createBridge(LLAssetType::EType asset_type,
 			}
 			new_listener = new LLMeshBridge(inventory, root, uuid);
 			break;
-
+		case LLAssetType::AT_UNKNOWN:
+			new_listener = new LLUnknownItemBridge(inventory, root, uuid);
+			break;
 		case LLAssetType::AT_IMAGE_TGA:
 		case LLAssetType::AT_IMAGE_JPEG:
 			//LL_WARNS() << LLAssetType::lookup(asset_type) << " asset type is unhandled for uuid " << uuid << LL_ENDL;
@@ -6949,6 +6951,21 @@ const LLUUID &LLLinkFolderBridge::getFolderID() const
 	return LLUUID::null;
 }
 
+void LLUnknownItemBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
+{
+	menuentry_vec_t items;
+	menuentry_vec_t disabled_items;
+	items.push_back(std::string("Properties"));
+	items.push_back(std::string("Rename"));
+	hide_context_entries(menu, items, disabled_items);
+}
+
+LLUIImagePtr LLUnknownItemBridge::getIcon() const
+{
+	return LLInventoryIcon::getIcon(LLAssetType::AT_UNKNOWN, mInvType);
+}
+
+
 /********************************************************************************
  **
  **                    BRIDGE ACTIONS
@@ -7310,7 +7327,7 @@ void LLFolderViewGroupedItemBridge::groupFilterContextMenu(folder_view_item_dequ
 	menuentry_vec_t disabled_items;
     if (get_selection_item_uuids(selected_items, ids))
     {
-        if (!LLAppearanceMgr::instance().canAddWearables(ids))
+		if (!LLAppearanceMgr::instance().canAddWearables(ids) && canWearSelected(ids))
         {
 			disabled_items.push_back(std::string("Wearable Add"));
         }
@@ -7318,4 +7335,17 @@ void LLFolderViewGroupedItemBridge::groupFilterContextMenu(folder_view_item_dequ
 	disable_context_entries_if_present(menu, disabled_items);
 }
 
+bool LLFolderViewGroupedItemBridge::canWearSelected(uuid_vec_t item_ids)
+{
+	for (uuid_vec_t::const_iterator it = item_ids.begin(); it != item_ids.end(); ++it)
+	{
+		LLViewerInventoryItem* item = gInventory.getItem(*it);
+		LLAssetType::EType asset_type = item->getType();
+		if (!item || (asset_type >= LLAssetType::AT_COUNT) || (asset_type <= LLAssetType::AT_NONE))
+		{
+			return false;
+		}
+	}
+	return true;
+}
 // EOF
diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h
index fd5c0433b1d3682d2d64abc92122fbeb10a76bcb..ce06e8fffc81ece7d6909a42fde56d9acc4fae83 100644
--- a/indra/newview/llinventorybridge.h
+++ b/indra/newview/llinventorybridge.h
@@ -575,6 +575,17 @@ class LLLinkItemBridge : public LLItemBridge
 	static std::string sPrefix;
 };
 
+class LLUnknownItemBridge : public LLItemBridge
+{
+public:
+	LLUnknownItemBridge(LLInventoryPanel* inventory,
+		LLFolderView* root,
+		const LLUUID& uuid) :
+		LLItemBridge(inventory, root, uuid) {}
+	virtual LLUIImagePtr getIcon() const;
+	virtual void buildContextMenu(LLMenuGL& menu, U32 flags);
+};
+
 class LLLinkFolderBridge : public LLItemBridge
 {
 public:
@@ -747,6 +758,7 @@ class LLFolderViewGroupedItemBridge: public LLFolderViewGroupedItemModel
 public:
     LLFolderViewGroupedItemBridge();
     virtual void groupFilterContextMenu(folder_view_item_deque& selected_items, LLMenuGL& menu);
+    bool canWearSelected(uuid_vec_t item_ids);
 };
 
 #endif // LL_LLINVENTORYBRIDGE_H
diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp
index f64c39c3adf0299c17297ae915b58276e0b2925f..9193613e9f2cd5cb5fbff8f6b4a33f96dbf84112 100644
--- a/indra/newview/llinventoryfilter.cpp
+++ b/indra/newview/llinventoryfilter.cpp
@@ -153,7 +153,7 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const
 	// we're showing all folders, overriding filter
 	if (mFilterOps.mShowFolderState == LLInventoryFilter::SHOW_ALL_FOLDERS)
 	{
-		return !gInventory.isCategoryHidden(folder_id);
+		return true;
 	}
 
 	// when applying a filter, matching folders get their contents downloaded first
diff --git a/indra/newview/llinventoryicon.cpp b/indra/newview/llinventoryicon.cpp
index 495180f087a50097dfb5ba052085baf71a1e5938..d0df1c94d58df4b04c4ac294c4b673fff543394d 100644
--- a/indra/newview/llinventoryicon.cpp
+++ b/indra/newview/llinventoryicon.cpp
@@ -93,6 +93,7 @@ LLIconDictionary::LLIconDictionary()
 	addEntry(LLInventoryType::ICONNAME_MESH,	 				new IconEntry("Inv_Mesh"));
 
 	addEntry(LLInventoryType::ICONNAME_INVALID, 				new IconEntry("Inv_Invalid"));
+	addEntry(LLInventoryType::ICONNAME_UNKNOWN, 				new IconEntry("Inv_Unknown"));
 
 	addEntry(LLInventoryType::ICONNAME_NONE, 					new IconEntry("NONE"));
 }
@@ -166,6 +167,8 @@ const std::string& LLInventoryIcon::getIconName(LLAssetType::EType asset_type,
 			break;
 		case LLAssetType::AT_MESH:
 			idx = LLInventoryType::ICONNAME_MESH;
+		case LLAssetType::AT_UNKNOWN:
+			idx = LLInventoryType::ICONNAME_UNKNOWN;
 		default:
 			break;
 	}
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index 76bf87cfe596d8d89f00211fb8f638bc2ed0869c..2b9607f7a25c1e66f9e11f5a2e308bbc8d70bb06 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -342,27 +342,6 @@ LLViewerInventoryCategory* LLInventoryModel::getCategory(const LLUUID& id) const
 	return category;
 }
 
-bool LLInventoryModel::isCategoryHidden(const LLUUID& id) const
-{
-	bool res = false;
-	const LLViewerInventoryCategory* category = getCategory(id);
-	if (category)
-	{
-		LLFolderType::EType cat_type = category->getPreferredType();
-		switch (cat_type)
-		{
-			case LLFolderType::FT_INBOX:
-			case LLFolderType::FT_OUTBOX:
-			case LLFolderType::FT_MARKETPLACE_LISTINGS:
-				res = true;
-				break;
-			default:
-				break;
-		}
-	}
-	return res;
-}
-
 S32 LLInventoryModel::getItemCount() const
 {
 	return mItemMap.size();
@@ -1831,11 +1810,7 @@ void LLInventoryModel::addItem(LLViewerInventoryItem* item)
 	llassert(item);
 	if(item)
 	{
-		// This can happen if assettype enums from llassettype.h ever change.
-		// For example, there is a known backwards compatibility issue in some viewer prototypes prior to when 
-		// the AT_LINK enum changed from 23 to 24.
-		if ((item->getType() == LLAssetType::AT_NONE)
-		    || LLAssetType::lookup(item->getType()) == LLAssetType::badLookup())
+		if (item->getType() <= LLAssetType::AT_NONE)
 		{
 			LL_WARNS(LOG_INV) << "Got bad asset type for item [ name: " << item->getName()
 							  << " type: " << item->getType()
@@ -1843,6 +1818,13 @@ void LLInventoryModel::addItem(LLViewerInventoryItem* item)
 			return;
 		}
 
+		if (LLAssetType::lookup(item->getType()) == LLAssetType::badLookup())
+		{
+			LL_WARNS(LOG_INV) << "Got unknown asset type for item [ name: " << item->getName()
+				<< " type: " << item->getType()
+				<< " inv-type: " << item->getInventoryType() << " ]." << LL_ENDL;
+		}
+
 		// This condition means that we tried to add a link without the baseobj being in memory.
 		// The item will show up as a broken link.
 		if (item->getIsBrokenLink())
@@ -2051,6 +2033,7 @@ bool LLInventoryModel::loadSkeleton(
 		update_map_t child_counts;
 		cat_array_t categories;
 		item_array_t items;
+		changed_items_t categories_to_update;
 		item_array_t possible_broken_links;
 		cat_set_t invalid_categories; // Used to mark categories that weren't successfully loaded.
 		std::string inventory_filename = getInvCacheAddres(owner_id);
@@ -2076,7 +2059,7 @@ bool LLInventoryModel::loadSkeleton(
 			}
 		}
 		bool is_cache_obsolete = false;
-		if(loadFromFile(inventory_filename, categories, items, is_cache_obsolete))
+		if (loadFromFile(inventory_filename, categories, items, categories_to_update, is_cache_obsolete))
 		{
 			// We were able to find a cache of files. So, use what we
 			// found to generate a set of categories we should add. We
@@ -2094,6 +2077,12 @@ bool LLInventoryModel::loadSkeleton(
 					continue; // cache corruption?? not sure why this happens -SJB
 				}
 				LLViewerInventoryCategory* tcat = *cit;
+
+				if (categories_to_update.find(tcat->getUUID()) != categories_to_update.end())
+				{
+					tcat->setVersion(NO_VERSION);
+					LL_WARNS() << "folder to update: " << tcat->getName() << LL_ENDL;
+				}
 				
 				// we can safely ignore anything loaded from file, but
 				// not sent down in the skeleton. Must have been removed from inventory.
@@ -2642,6 +2631,7 @@ bool LLUUIDAndName::operator>(const LLUUIDAndName& rhs) const
 bool LLInventoryModel::loadFromFile(const std::string& filename,
 									LLInventoryModel::cat_array_t& categories,
 									LLInventoryModel::item_array_t& items,
+									LLInventoryModel::changed_items_t& cats_to_update,
 									bool &is_cache_obsolete)
 {
 	if(filename.empty())
@@ -2716,7 +2706,14 @@ bool LLInventoryModel::loadFromFile(const std::string& filename,
 				}
 				else
 				{
-					items.push_back(inv_item);
+					if (inv_item->getType() == LLAssetType::AT_UNKNOWN)
+					{
+						cats_to_update.insert(inv_item->getParentUUID());
+					}
+					else
+					{
+						items.push_back(inv_item);
+					}
 				}
 			}
 			else
diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h
index 576c5e9e20c54ccbc8a73abcfbb141cf4f44f527..a4326aaeed9708a41e836f05f4f6798f96ed89d3 100644
--- a/indra/newview/llinventorymodel.h
+++ b/indra/newview/llinventorymodel.h
@@ -316,9 +316,7 @@ class LLInventoryModel
     // Copy content of all folders of type "type" into folder "id" and delete/purge the empty folders
     // Note : This method has been designed for FT_OUTBOX (aka Merchant Outbox) but can be used for other categories
     void consolidateForType(const LLUUID& id, LLFolderType::EType type);
-
-    bool isCategoryHidden(const LLUUID& id) const;
-
+    
 private:
 	mutable LLPointer<LLViewerInventoryItem> mLastItem; // cache recent lookups	
 
@@ -615,6 +613,7 @@ class LLInventoryModel
 	static bool loadFromFile(const std::string& filename,
 							 cat_array_t& categories,
 							 item_array_t& items,
+							 changed_items_t& cats_to_update,
 							 bool& is_cache_obsolete); 
 	static bool saveToFile(const std::string& filename,
 						   const cat_array_t& categories,
diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp
index 702675ad4958ad1c7317a0c164731b1cff481565..1481992f43b5cad147c34d20ce56c36531f70888 100644
--- a/indra/newview/llinventorypanel.cpp
+++ b/indra/newview/llinventorypanel.cpp
@@ -861,13 +861,37 @@ LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id)
 
  	if (!folder_view_item && parent_folder)
   		{
-  			if (objectp->getType() <= LLAssetType::AT_NONE ||
-  				objectp->getType() >= LLAssetType::AT_COUNT)
+			if (objectp->getType() <= LLAssetType::AT_NONE)
+			{
+				LL_WARNS() << "LLInventoryPanel::buildNewViews called with invalid objectp->mType : "
+					<< ((S32)objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID()
+					<< LL_ENDL;
+				return NULL;
+			}
+			
+			if (objectp->getType() >= LLAssetType::AT_COUNT)
   			{
-  				LL_WARNS() << "LLInventoryPanel::buildNewViews called with invalid objectp->mType : "
-                << ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID()
-                << LL_ENDL;
-  				return NULL;
+  				LL_WARNS() << "LLInventoryPanel::buildNewViews called with unknown objectp->mType : "
+				<< ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID()
+				<< LL_ENDL;
+
+				LLInventoryItem* item = (LLInventoryItem*)objectp;
+				if (item)
+				{
+					LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(LLAssetType::AT_UNKNOWN,
+						LLAssetType::AT_UNKNOWN,
+						LLInventoryType::IT_UNKNOWN,
+						this,
+						&mInventoryViewModel,
+						mFolderRoot.get(),
+						item->getUUID(),
+						item->getFlags());
+
+					if (new_listener)
+					{
+						folder_view_item = createFolderViewItem(new_listener);
+					}
+				}
   			}
   		
   			if ((objectp->getType() == LLAssetType::AT_CATEGORY) &&
diff --git a/indra/newview/llnotificationlistitem.cpp b/indra/newview/llnotificationlistitem.cpp
index f2de8e54a0e5c2a3835110a064a6a543123035d8..a5bc75e6bd51dfb4608249f3a6bbcdcee54d6a06 100644
--- a/indra/newview/llnotificationlistitem.cpp
+++ b/indra/newview/llnotificationlistitem.cpp
@@ -312,38 +312,15 @@ void LLGroupInviteNotificationListItem::onClickJoinBtn()
 		return;
 	}
 
-	if(mParams.fee > 0)
-	{
-		LLSD args;
-		args["COST"] = llformat("%d", mParams.fee);
-		// Set the fee for next time to 0, so that we don't keep
-		// asking about a fee.
-		LLSD next_payload;
-		next_payload["group_id"]=  mParams.group_id;
-		next_payload["transaction_id"]= mParams.transaction_id;
-		next_payload["fee"] = 0;
-		LLNotificationsUtil::add("JoinGroupCanAfford", args, next_payload);
-	}
-	else
-	{
-		send_improved_im(mParams.group_id,
-						std::string("name"),
-						std::string("message"),
-						IM_ONLINE,
-						IM_GROUP_INVITATION_ACCEPT,
-						mParams.transaction_id);
-	}
+	send_join_group_response(mParams.group_id, mParams.transaction_id, true, mParams.fee, mParams.use_offline_cap);
+
 	LLNotificationListItem::onClickCloseBtn();
 }
 
 void LLGroupInviteNotificationListItem::onClickDeclineBtn()
 {
-	send_improved_im(mParams.group_id,
-					std::string("name"),
-					std::string("message"),
-					IM_ONLINE,
-					IM_GROUP_INVITATION_DECLINE,
-					mParams.transaction_id);
+	send_join_group_response(mParams.group_id, mParams.transaction_id, false, mParams.fee, mParams.use_offline_cap);
+
 	LLNotificationListItem::onClickCloseBtn();
 }
 
diff --git a/indra/newview/llnotificationlistitem.h b/indra/newview/llnotificationlistitem.h
index 3dd52986b07d18a44c0a8be75f0e04c771af9304..3d564fed0e6b75e542204d01fb45301998877ead 100644
--- a/indra/newview/llnotificationlistitem.h
+++ b/indra/newview/llnotificationlistitem.h
@@ -56,6 +56,7 @@ class LLNotificationListItem : public LLPanel
         std::string     message;
         std::string     sender;
         S32             fee;
+        U8              use_offline_cap;
         LLDate          time_stamp;
         LLDate          received_time;
         LLSD            inventory_offer;
diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp
index a90a29a7310f07c5a73e94d8dad1286b16457dc8..b2b6de94b3bedf231fbedfebb19c5d11371875df 100644
--- a/indra/newview/lloutfitgallery.cpp
+++ b/indra/newview/lloutfitgallery.cpp
@@ -1090,11 +1090,6 @@ void LLOutfitGallery::refreshOutfit(const LLUUID& category_id)
                     updates["name"] = new_name;
                     update_inventory_item(inv_id, updates, NULL);
                     mOutfitRenamePending.setNull();
-                    LLFloater* inv_floater = LLFloaterReg::getInstance("inventory");
-                    if (inv_floater)
-                    {
-                        inv_floater->closeFloater();
-                    }
                     LLFloater* appearance_floater = LLFloaterReg::getInstance("appearance");
                     if (appearance_floater)
                     {
@@ -1228,7 +1223,7 @@ void LLOutfitGallery::uploadOutfitImage(const std::vector<std::string>& filename
             LLFloaterPerms::getNextOwnerPerms("Uploads"),
             LLFloaterPerms::getGroupPerms("Uploads"),
             LLFloaterPerms::getEveryonePerms("Uploads"),
-            upload_pending_name, callback, expected_upload_cost, nruserdata);
+            upload_pending_name, callback, expected_upload_cost, nruserdata, false);
         mOutfitLinkPending = outfit_id;
     }
     delete unit;
diff --git a/indra/newview/llpanelgroupnotices.cpp b/indra/newview/llpanelgroupnotices.cpp
index 178b5db6c24fa722f50d9048aef7f76976c70aa1..ce6834b4b3fddd3ef0a8f587d68958fdb58304cd 100644
--- a/indra/newview/llpanelgroupnotices.cpp
+++ b/indra/newview/llpanelgroupnotices.cpp
@@ -303,6 +303,8 @@ void LLPanelGroupNotices::activate()
 {
 	if(mNoticesList)
 		mNoticesList->deleteAllItems();
+
+	mPrevSelectedNotice = LLUUID();
 	
 	BOOL can_send = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND);
 	BOOL can_receive = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_RECEIVE);
@@ -454,12 +456,18 @@ void LLPanelGroupNotices::refreshNotices()
 	
 }
 
+void LLPanelGroupNotices::clearNoticeList()
+{
+	mPrevSelectedNotice = mNoticesList->getStringUUIDSelectedItem();
+	mNoticesList->deleteAllItems();
+}
+
 void LLPanelGroupNotices::onClickRefreshNotices(void* data)
 {
 	LL_DEBUGS() << "LLPanelGroupNotices::onClickGetPastNotices" << LL_ENDL;
 	LLPanelGroupNotices* self = (LLPanelGroupNotices*)data;
 	
-	self->mNoticesList->deleteAllItems();
+	self->clearNoticeList();
 
 	LLMessageSystem* msg = gMessageSystem;
 	msg->newMessage("GroupNoticesListRequest");
@@ -547,7 +555,6 @@ void LLPanelGroupNotices::processNotices(LLMessageSystem* msg)
 
 		LLSD row;
 		row["id"] = id;
-		
 		row["columns"][0]["column"] = "icon";
 		if (has_attachment)
 		{
@@ -575,7 +582,13 @@ void LLPanelGroupNotices::processNotices(LLMessageSystem* msg)
 
 	mNoticesList->setNeedsSort(save_sort);
 	mNoticesList->updateSort();
-	mNoticesList->selectFirstItem();
+	if (mPanelViewNotice->getVisible())
+	{
+		if (!mNoticesList->selectByID(mPrevSelectedNotice))
+		{
+			mNoticesList->selectFirstItem();
+		}
+	}
 }
 
 void LLPanelGroupNotices::onSelectNotice(LLUICtrl* ctrl, void* data)
diff --git a/indra/newview/llpanelgroupnotices.h b/indra/newview/llpanelgroupnotices.h
index 04629b5f6bfb7312ba810301c097305f66fabc05..46c8c241c657fcb61142abeaf5fd3098a2fb82b9 100644
--- a/indra/newview/llpanelgroupnotices.h
+++ b/indra/newview/llpanelgroupnotices.h
@@ -65,6 +65,8 @@ class LLPanelGroupNotices : public LLPanelGroupTab
 
 	void refreshNotices();
 
+	void clearNoticeList();
+
 	virtual void setGroupID(const LLUUID& id);
 
 private:
@@ -113,6 +115,8 @@ class LLPanelGroupNotices : public LLPanelGroupTab
 
 	LLOfferInfo* mInventoryOffer;
 
+	LLUUID mPrevSelectedNotice;
+
 	static std::map<LLUUID,LLPanelGroupNotices*>	sInstances;
 };
 
diff --git a/indra/newview/llpanelobject.cpp b/indra/newview/llpanelobject.cpp
index 25cfb598e79b4bc0b0bcee95fd81a74fa7187ee7..3665910c633effc7954561d97c5f1e04c3dda797 100644
--- a/indra/newview/llpanelobject.cpp
+++ b/indra/newview/llpanelobject.cpp
@@ -341,21 +341,17 @@ void LLPanelObject::getState( )
 		return;
 	}
 
-	// can move or rotate only linked group with move permissions, or sub-object with move and modify perms
-	BOOL enable_move	= objectp->permMove() && !objectp->isPermanentEnforced() && ((root_objectp == NULL) || !root_objectp->isPermanentEnforced()) && (objectp->permModify() || !gSavedSettings.getBOOL("EditLinkedParts"));
-	BOOL enable_scale	= objectp->permMove() && !objectp->isPermanentEnforced() && ((root_objectp == NULL) || !root_objectp->isPermanentEnforced()) && objectp->permModify();
-	BOOL enable_rotate	= objectp->permMove() && !objectp->isPermanentEnforced() && ((root_objectp == NULL) || !root_objectp->isPermanentEnforced()) && (objectp->permModify() || !gSavedSettings.getBOOL("EditLinkedParts"));
-
 	S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount();
 	BOOL single_volume = (LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ))
 						 && (selected_count == 1);
 
-	if (LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() > 1)
-	{
-		enable_move = FALSE;
-		enable_scale = FALSE;
-		enable_rotate = FALSE;
-	}
+	bool enable_move;
+	bool enable_modify;
+
+	LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify);
+
+	BOOL enable_scale = enable_modify;
+	BOOL enable_rotate = enable_move; // already accounts for a case of children, which needs permModify() as well
 
 	LLVector3 vec;
 	if (enable_move)
diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp
index ddae109030c53d4218b72ae17b6993e205becc0d..fce21fa30af67a48fd131e7bf0ab458c7fee7146 100644
--- a/indra/newview/llselectmgr.cpp
+++ b/indra/newview/llselectmgr.cpp
@@ -3676,6 +3676,39 @@ void LLSelectMgr::selectForceDelete()
 		SEND_ONLY_ROOTS);
 }
 
+BOOL LLSelectMgr::selectGetEditMoveLinksetPermissions(bool &move, bool &modify)
+{
+    move = true;
+    modify = true;
+    bool selecting_linked_set = !gSavedSettings.getBOOL("EditLinkedParts");
+
+    for (LLObjectSelection::iterator iter = getSelection()->begin();
+        iter != getSelection()->end(); iter++)
+    {
+        LLSelectNode* nodep = *iter;
+        LLViewerObject* object = nodep->getObject();
+        if (!object || !nodep->mValid)
+        {
+            move = false;
+            modify = false;
+            return FALSE;
+        }
+
+        LLViewerObject *root_object = object->getRootEdit();
+        bool this_object_movable = false;
+        if (object->permMove() && !object->isPermanentEnforced() &&
+            ((root_object == NULL) || !root_object->isPermanentEnforced()) &&
+            (object->permModify() || selecting_linked_set))
+        {
+            this_object_movable = true;
+        }
+        move = move && this_object_movable;
+        modify = modify && object->permModify();
+    }
+
+    return TRUE;
+}
+
 void LLSelectMgr::selectGetAggregateSaleInfo(U32 &num_for_sale,
 											 BOOL &is_for_sale_mixed, 
 											 BOOL &is_sale_price_mixed,
diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h
index e965dd80d5859135ed09f96d5601ae68e995e700..87ac8993251787f3513c913b995453505a81f2bd 100644
--- a/indra/newview/llselectmgr.h
+++ b/indra/newview/llselectmgr.h
@@ -675,6 +675,11 @@ class LLSelectMgr : public LLEditMenuHandler, public LLSingleton<LLSelectMgr>
 	// returns TRUE if all the nodes are valid. Accumulates
 	// permissions in the parameter.
 	BOOL selectGetPermissions(LLPermissions& perm);
+
+	// returns TRUE if all the nodes are valid. Depends onto "edit linked" state
+	// Children in linksets are a bit special - they require not only move permission
+	// but also modify if "edit linked" is set, since you move them relative to parent
+	BOOL selectGetEditMoveLinksetPermissions(bool &move, bool &modify);
 	
 	// Get a bunch of useful sale information for the object(s) selected.
 	// "_mixed" is true if not all objects have the same setting.
diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp
index a486a29aa2981ecabda6aa3d4392504e20703f58..2503e2a5e299d45d964dd5752b682577853b6ffd 100644
--- a/indra/newview/llsidepaneliteminfo.cpp
+++ b/indra/newview/llsidepaneliteminfo.cpp
@@ -613,7 +613,7 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item)
 		LLCheckBoxCtrl* ctl = getChild<LLCheckBoxCtrl>("CheckShareWithGroup");
 		if(ctl)
 		{
-			ctl->setTentative(TRUE);
+			ctl->setTentative(!ctl->getEnabled());
 			ctl->set(TRUE);
 		}
 	}
diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp
index d0cff1464b5eaed7bcf58295f644c3f4892f751a..5a40af14a3c8239485915626280fa58aef838361 100644
--- a/indra/newview/llsnapshotlivepreview.cpp
+++ b/indra/newview/llsnapshotlivepreview.cpp
@@ -1050,7 +1050,7 @@ void LLSnapshotLivePreview::saveTexture(BOOL outfit_snapshot, std::string name)
             tid, LLAssetType::AT_TEXTURE, res_name, res_desc, 0,
             folder_type, inv_type,
             PERM_ALL, LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"),
-            expected_upload_cost));
+            expected_upload_cost, !outfit_snapshot));
 
         upload_new_resource(assetUploadInfo);
 
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 7a4c41779a62820b17fff86caf808960462bbc1a..cc02642203af48b2967ffca1b05e930aff716fd7 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -246,6 +246,7 @@ static bool mLoginStatePastUI = false;
 
 const S32 DEFAULT_MAX_AGENT_GROUPS = 42;
 const S32 ALLOWED_MAX_AGENT_GROUPS = 500;
+const F32 STATE_AGENT_WAIT_TIMEOUT = 240; //seconds
 
 boost::scoped_ptr<LLEventPump> LLStartUp::sStateWatcher(new LLEventStream("StartupState"));
 boost::scoped_ptr<LLStartupListener> LLStartUp::sListener(new LLStartupListener());
@@ -1615,6 +1616,13 @@ bool idle_startup()
 			LLStartUp::setStartupState( STATE_INVENTORY_SEND );
 		}
 		display_startup();
+
+		if (!gAgentMovementCompleted && timeout.getElapsedTimeF32() > STATE_AGENT_WAIT_TIMEOUT)
+		{
+			LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL;
+			LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status);
+			reset_login();
+		}
 		return FALSE;
 	}
 
diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp
index 6a8843cb4470ca38c41dfc973b1f326752bb0930..5082e166857b33187351cac15bf6c8c7d9f2d888 100644
--- a/indra/newview/lltoolpie.cpp
+++ b/indra/newview/lltoolpie.cpp
@@ -609,8 +609,8 @@ BOOL LLToolPie::handleHover(S32 x, S32 y, MASK mask)
 			gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB);
 			LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL;
 		}
-		else if ( (object && object->flagHandleTouch()) 
-				  || (parent && parent->flagHandleTouch()))
+		else if ((!object || !object->isAttachment() || object->getClickAction() != CLICK_ACTION_DISABLED)
+				 && ((object && object->flagHandleTouch()) || (parent && parent->flagHandleTouch())))
 		{
 			show_highlight = true;
 			gViewerWindow->setCursor(UI_CURSOR_HAND);
diff --git a/indra/newview/llviewerassettype.cpp b/indra/newview/llviewerassettype.cpp
index ad0c1734f9fd132b12eb91f39e5434d5cefdf06f..f07c14d01f8108e7a32b26924a58dcecd661c680 100644
--- a/indra/newview/llviewerassettype.cpp
+++ b/indra/newview/llviewerassettype.cpp
@@ -84,6 +84,8 @@ LLViewerAssetDictionary::LLViewerAssetDictionary()
 	
 	addEntry(LLViewerAssetType::AT_PERSON, 				new ViewerAssetEntry(DAD_PERSON));
 
+	addEntry(LLViewerAssetType::AT_UNKNOWN,				new ViewerAssetEntry(DAD_NONE));
+
 	addEntry(LLViewerAssetType::AT_NONE, 				new ViewerAssetEntry(DAD_NONE));
 };
 
diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp
index 4e13eceb5572bd47e0d33bec406c627b8403a4ad..97fbb8c601fafd3efe4c24e0b6141a41ef0a6089 100644
--- a/indra/newview/llviewerassetupload.cpp
+++ b/indra/newview/llviewerassetupload.cpp
@@ -60,7 +60,7 @@ LLResourceUploadInfo::LLResourceUploadInfo(LLTransactionID transactId,
         LLAssetType::EType assetType, std::string name, std::string description,
         S32 compressionInfo, LLFolderType::EType destinationType,
         LLInventoryType::EType inventoryType, U32 nextOWnerPerms,
-        U32 groupPerms, U32 everyonePerms, S32 expectedCost) :
+        U32 groupPerms, U32 everyonePerms, S32 expectedCost, bool showInventory) :
     mTransactionId(transactId),
     mAssetType(assetType),
     mName(name),
@@ -72,6 +72,7 @@ LLResourceUploadInfo::LLResourceUploadInfo(LLTransactionID transactId,
     mGroupPerms(groupPerms),
     mEveryonePerms(everyonePerms),
     mExpectedUploadCost(expectedCost),
+    mShowInventory(showInventory),
     mFolderId(LLUUID::null),
     mItemId(LLUUID::null),
     mAssetId(LLAssetID::null)
@@ -81,7 +82,7 @@ LLResourceUploadInfo::LLResourceUploadInfo(LLTransactionID transactId,
 LLResourceUploadInfo::LLResourceUploadInfo(std::string name, 
         std::string description, S32 compressionInfo, 
         LLFolderType::EType destinationType, LLInventoryType::EType inventoryType, 
-        U32 nextOWnerPerms, U32 groupPerms, U32 everyonePerms, S32 expectedCost):
+        U32 nextOWnerPerms, U32 groupPerms, U32 everyonePerms, S32 expectedCost, bool showInventory) :
     mName(name),
     mDescription(description),
     mCompressionInfo(compressionInfo),
@@ -91,6 +92,7 @@ LLResourceUploadInfo::LLResourceUploadInfo(std::string name,
     mGroupPerms(groupPerms),
     mEveryonePerms(everyonePerms),
     mExpectedUploadCost(expectedCost),
+    mShowInventory(showInventory),
     mTransactionId(),
     mAssetType(LLAssetType::AT_NONE),
     mFolderId(LLUUID::null),
@@ -112,6 +114,7 @@ LLResourceUploadInfo::LLResourceUploadInfo(LLAssetID assetId, LLAssetType::EType
     mGroupPerms(0),
     mEveryonePerms(0),
     mExpectedUploadCost(0),
+    mShowInventory(true),
     mTransactionId(),
     mFolderId(LLUUID::null),
     mItemId(LLUUID::null)
@@ -331,10 +334,11 @@ LLNewFileResourceUploadInfo::LLNewFileResourceUploadInfo(
     U32 nextOWnerPerms,
     U32 groupPerms,
     U32 everyonePerms,
-    S32 expectedCost) :
+    S32 expectedCost,
+    bool show_inventory) :
     LLResourceUploadInfo(name, description, compressionInfo,
     destinationType, inventoryType,
-    nextOWnerPerms, groupPerms, everyonePerms, expectedCost),
+    nextOWnerPerms, groupPerms, everyonePerms, expectedCost, show_inventory),
     mFileName(fileName)
 {
 }
diff --git a/indra/newview/llviewerassetupload.h b/indra/newview/llviewerassetupload.h
index 43e23a0d42ff36315dafd90e986ac0b2b9ee4832..ee1806b782372184b57c50273cb7da36155fb197 100644
--- a/indra/newview/llviewerassetupload.h
+++ b/indra/newview/llviewerassetupload.h
@@ -53,7 +53,8 @@ class LLResourceUploadInfo
         U32 nextOWnerPerms,
         U32 groupPerms,
         U32 everyonePerms,
-        S32 expectedCost);
+        S32 expectedCost,
+        bool showInventory = true);
 
     virtual ~LLResourceUploadInfo()
     { }
@@ -79,7 +80,7 @@ class LLResourceUploadInfo
     S32                 getExpectedUploadCost() const { return mExpectedUploadCost; };
 
     virtual bool        showUploadDialog() const { return true; }
-    virtual bool        showInventoryPanel() const { return true; }
+    virtual bool        showInventoryPanel() const { return mShowInventory; }
 
     virtual std::string getDisplayName() const;
 
@@ -97,7 +98,8 @@ class LLResourceUploadInfo
         U32 nextOWnerPerms,
         U32 groupPerms,
         U32 everyonePerms,
-        S32 expectedCost);
+        S32 expectedCost,
+        bool showInventory = true);
 
     LLResourceUploadInfo(
         LLAssetID assetId,
@@ -130,6 +132,7 @@ class LLResourceUploadInfo
     LLUUID              mFolderId;
     LLUUID              mItemId;
     LLAssetID           mAssetId;
+    bool                mShowInventory;
 };
 
 //-------------------------------------------------------------------------
@@ -146,7 +149,8 @@ class LLNewFileResourceUploadInfo : public LLResourceUploadInfo
         U32 nextOWnerPerms,
         U32 groupPerms,
         U32 everyonePerms,
-        S32 expectedCost);
+        S32 expectedCost,
+        bool show_inventory = true);
 
     virtual LLSD        prepareUpload();
 
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index f7250ffb66b7264bd5ba1fcc628b3fad94f5a7c5..ec851ddaf95b82d409d0ed332dff190d0adcc5cf 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -134,6 +134,7 @@
 #include "llpathfindingmanager.h"
 #include "llstartup.h"
 #include "boost/unordered_map.hpp"
+#include <boost/regex.hpp>
 #include "llcleanup.h"
 
 using namespace LLAvatarAppearanceDefines;
@@ -8025,7 +8026,12 @@ void handle_report_bug(const LLSD& param)
 	LLUIString url(param.asString());
 	
 	LLStringUtil::format_map_t replace;
-	replace["[ENVIRONMENT]"] = LLURI::escape(LLAppViewer::instance()->getViewerInfoString(true));
+	std::string environment = LLAppViewer::instance()->getViewerInfoString(true);
+	boost::regex regex;
+	regex.assign("</?nolink>");
+	std::string stripped_env = boost::regex_replace(environment, regex, "");
+
+	replace["[ENVIRONMENT]"] = LLURI::escape(stripped_env);
 	LLSLURL location_url;
 	LLAgentUI::buildSLURL(location_url);
 	replace["[LOCATION]"] = LLURI::escape(location_url.getSLURLString());
diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp
index cf1c442ce970c9deef8bda0535d332da0afc978d..d2a55785684d2890a12c5fdca055610926fc53c7 100644
--- a/indra/newview/llviewermenufile.cpp
+++ b/indra/newview/llviewermenufile.cpp
@@ -689,7 +689,8 @@ LLUUID upload_new_resource(
 	const std::string& display_name,
 	LLAssetStorage::LLStoreAssetCallback callback,
 	S32 expected_upload_cost,
-	void *userdata)
+	void *userdata,
+	bool show_inventory)
 {	
 
     LLResourceUploadInfo::ptr_t uploadInfo(new LLNewFileResourceUploadInfo(
@@ -697,7 +698,7 @@ LLUUID upload_new_resource(
         name, desc, compression_info,
         destination_folder_type, inv_type,
         next_owner_perms, group_perms, everyone_perms,
-        expected_upload_cost));
+        expected_upload_cost, show_inventory));
     upload_new_resource(uploadInfo, callback, userdata);
 
     return LLUUID::null;
diff --git a/indra/newview/llviewermenufile.h b/indra/newview/llviewermenufile.h
index 35f86f606b42673bab61c88940e003fdd90a66ed..4e8348b5e5254eccc9975cb7d381943869ec2f1c 100644
--- a/indra/newview/llviewermenufile.h
+++ b/indra/newview/llviewermenufile.h
@@ -55,7 +55,8 @@ LLUUID upload_new_resource(
     const std::string& display_name,
     LLAssetStorage::LLStoreAssetCallback callback,
     S32 expected_upload_cost,
-    void *userdata);
+    void *userdata,
+    bool show_inventory = true);
 
 void upload_new_resource(
     LLResourceUploadInfo::ptr_t &uploadInfo,
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 850d455c3659366fdd61a44a1701936b9e29c869..a2b4a6a91dda66bf87b8b5ceafd7c7cf82563a95 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -169,7 +169,8 @@ void accept_friendship_coro(std::string url, LLSD notification)
     url += "?from=" + payload["from_id"].asString();
     url += "&agent_name=\"" + LLURI::escape(gAgentAvatarp->getFullname()) + "\"";
 
-    LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
+    LLSD data;
+    LLSD result = httpAdapter->postAndSuspend(httpRequest, url, data);
 
     LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
     LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
@@ -199,20 +200,20 @@ void accept_friendship_coro(std::string url, LLSD notification)
 
 void decline_friendship_coro(std::string url, LLSD notification, S32 option)
 {
-    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
-    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
-        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("friendshipResponceErrorProcessing", httpPolicy));
-    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
     if (url.empty())
     {
         LL_WARNS("Friendship") << "Empty capability!" << LL_ENDL;
         return;
     }
+    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("friendshipResponceErrorProcessing", httpPolicy));
+    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
 
     LLSD payload = notification["payload"];
     url += "?from=" + payload["from_id"].asString();
 
-    LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
+    LLSD result = httpAdapter->deleteAndSuspend(httpRequest, url);
 
     LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
     LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
@@ -732,6 +733,122 @@ void send_sound_trigger(const LLUUID& sound_id, F32 gain)
 static LLSD sSavedGroupInvite;
 static LLSD sSavedResponse;
 
+void response_group_invitation_coro(std::string url, LLUUID group_id, bool notify_and_update)
+{
+    if (url.empty())
+    {
+        LL_WARNS("GroupInvite") << "Empty capability!" << LL_ENDL;
+        return;
+    }
+
+    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("responseGroupInvitation", httpPolicy));
+    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+
+    LLSD payload;
+    payload["group"] = group_id;
+
+    LLSD result = httpAdapter->postAndSuspend(httpRequest, url, payload);
+
+    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+    if (!status)
+    {
+        LL_WARNS("GroupInvite") << "HTTP status, " << status.toTerseString() <<
+            ". Group " << group_id << " invitation response processing failed." << LL_ENDL;
+    }
+    else
+    {
+        if (!result.has("success") || result["success"].asBoolean() == false)
+        {
+            LL_WARNS("GroupInvite") << "Server failed to process group " << group_id << " invitation response. " << httpResults << LL_ENDL;
+        }
+        else
+        {
+            LL_DEBUGS("GroupInvite") << "Successfully sent response to group " << group_id << " invitation" << LL_ENDL;
+            if (notify_and_update)
+            {
+                LLNotificationsUtil::add("JoinGroupSuccess");
+                gAgent.sendAgentDataUpdateRequest();
+
+                LLGroupMgr::getInstance()->clearGroupData(group_id);
+                // refresh the floater for this group, if any.
+                LLGroupActions::refresh(group_id);
+            }
+        }
+    }
+}
+
+void send_join_group_response(LLUUID group_id, LLUUID transaction_id, bool accept_invite, S32 fee, bool use_offline_cap, LLSD &payload)
+{
+    if (accept_invite && fee > 0)
+    {
+        // If there is a fee to join this group, make
+        // sure the user is sure they want to join.
+            LLSD args;
+            args["COST"] = llformat("%d", fee);
+            // Set the fee for next time to 0, so that we don't keep
+            // asking about a fee.
+            LLSD next_payload = payload;
+            next_payload["fee"] = 0;
+            LLNotificationsUtil::add("JoinGroupCanAfford",
+                args,
+                next_payload);
+    }
+    else if (use_offline_cap)
+    {
+        std::string url;
+        if (accept_invite)
+        {
+            url = gAgent.getRegionCapability("AcceptGroupInvite");
+        }
+        else
+        {
+            url = gAgent.getRegionCapability("DeclineGroupInvite");
+        }
+
+        if (!url.empty())
+        {
+            LL_DEBUGS("GroupInvite") << "Capability url: " << url << LL_ENDL;
+            LLCoros::instance().launch("LLMessageSystem::acceptGroupInvitation",
+                boost::bind(response_group_invitation_coro, url, group_id, accept_invite));
+        }
+        else
+        {
+            // if sim has no this cap, we can do nothing - regular request will fail
+            LL_WARNS("GroupInvite") << "No capability, can't reply to offline invitation!" << LL_ENDL;
+        }
+    }
+    else
+    {
+        LL_DEBUGS("GroupInvite") << "Replying to group invite via IM message" << LL_ENDL;
+
+        EInstantMessage type = accept_invite ? IM_GROUP_INVITATION_ACCEPT : IM_GROUP_INVITATION_DECLINE;
+
+        send_improved_im(group_id,
+            std::string("name"),
+            std::string("message"),
+            IM_ONLINE,
+            type,
+            transaction_id);
+    }
+}
+
+void send_join_group_response(LLUUID group_id, LLUUID transaction_id, bool accept_invite, S32 fee, bool use_offline_cap)
+{
+    LLSD payload;
+    if (accept_invite)
+    {
+        payload["group_id"] = group_id;
+        payload["transaction_id"] =  transaction_id;
+        payload["fee"] =  fee;
+        payload["use_offline_cap"] = use_offline_cap;
+    }
+    send_join_group_response(group_id, transaction_id, accept_invite, fee, use_offline_cap, payload);
+}
+
 bool join_group_response(const LLSD& notification, const LLSD& response)
 {
 //	A bit of variable saving and restoring is used to deal with the case where your group list is full and you
@@ -770,6 +887,7 @@ bool join_group_response(const LLSD& notification, const LLSD& response)
 	std::string name = notification_adjusted["payload"]["name"].asString();
 	std::string message = notification_adjusted["payload"]["message"].asString();
 	S32 fee = notification_adjusted["payload"]["fee"].asInteger();
+	U8 use_offline_cap = notification_adjusted["payload"]["use_offline_cap"].asInteger();
 
 	if (option == 2 && !group_id.isNull())
 	{
@@ -798,42 +916,7 @@ bool join_group_response(const LLSD& notification, const LLSD& response)
 			return false;
 		}
 	}
-
-	if (accept_invite)
-	{
-		// If there is a fee to join this group, make
-		// sure the user is sure they want to join.
-		if (fee > 0)
-		{
-			LLSD args;
-			args["COST"] = llformat("%d", fee);
-			// Set the fee for next time to 0, so that we don't keep
-			// asking about a fee.
-			LLSD next_payload = notification_adjusted["payload"];
-			next_payload["fee"] = 0;
-			LLNotificationsUtil::add("JoinGroupCanAfford",
-									args,
-									next_payload);
-		}
-		else
-		{
-			send_improved_im(group_id,
-							 std::string("name"),
-							 std::string("message"),
-							IM_ONLINE,
-							IM_GROUP_INVITATION_ACCEPT,
-							transaction_id);
-		}
-	}
-	else
-	{
-		send_improved_im(group_id,
-						 std::string("name"),
-						 std::string("message"),
-						IM_ONLINE,
-						IM_GROUP_INVITATION_DECLINE,
-						transaction_id);
-	}
+	send_join_group_response(group_id, transaction_id, accept_invite, fee, use_offline_cap, notification_adjusted["payload"]);
 
 	sSavedGroupInvite[id] = LLSD::emptyMap();
 	sSavedResponse[id] = LLSD::emptyMap();
@@ -1740,7 +1823,7 @@ bool LLOfferInfo::inventory_offer_callback(const LLSD& notification, const LLSD&
 		//don't spam them if they are getting flooded
 		if (check_offer_throttle(mFromName, true))
 		{
-			log_message = chatHistory_string + " " + LLTrans::getString("InvOfferGaveYou") + " " + mDesc + LLTrans::getString(".");
+			log_message = "<nolink>" + chatHistory_string + "</nolink> " + LLTrans::getString("InvOfferGaveYou") + " " + getSanitizedDescription() + LLTrans::getString(".");
 			LLSD args;
 			args["MESSAGE"] = log_message;
 			LLNotificationsUtil::add("SystemMessageTip", args);
@@ -1925,7 +2008,7 @@ bool LLOfferInfo::inventory_task_offer_callback(const LLSD& notification, const
 			//don't spam them if they are getting flooded
 			if (check_offer_throttle(mFromName, true))
 			{
-				log_message = chatHistory_string + " " + LLTrans::getString("InvOfferGaveYou") + " " + mDesc + LLTrans::getString(".");
+				log_message = "<nolink>" + chatHistory_string + "</nolink> " + LLTrans::getString("InvOfferGaveYou") + " " + getSanitizedDescription() + LLTrans::getString(".");
 				LLSD args;
 				args["MESSAGE"] = log_message;
 				LLNotificationsUtil::add("SystemMessageTip", args);
@@ -1998,6 +2081,23 @@ bool LLOfferInfo::inventory_task_offer_callback(const LLSD& notification, const
 	return false;
 }
 
+std::string LLOfferInfo::getSanitizedDescription()
+{
+	// currently we get description from server as: 'Object' ( Location )
+	// object name shouldn't be shown as a hyperlink
+	std::string description = mDesc;
+
+	std::size_t start = mDesc.find_first_of("'");
+	std::size_t end = mDesc.find_last_of("'");
+	if ((start != std::string::npos) && (end != std::string::npos))
+	{
+		description.insert(start, "<nolink>");
+		description.insert(end + 8, "</nolink>");
+	}
+	return description;
+}
+
+
 void LLOfferInfo::initRespondFunctionMap()
 {
 	if(mRespondFunctions.empty())
diff --git a/indra/newview/llviewermessage.h b/indra/newview/llviewermessage.h
index b0eaa37541db4af72b8eb4c7ccda1bf30e0de7a3..913abef2be5dc1ab5f8bc22a0ea52a2d17da5721 100644
--- a/indra/newview/llviewermessage.h
+++ b/indra/newview/llviewermessage.h
@@ -66,6 +66,11 @@ enum InventoryOfferResponse
 BOOL can_afford_transaction(S32 cost);
 void give_money(const LLUUID& uuid, LLViewerRegion* region, S32 amount, BOOL is_group = FALSE,
 				S32 trx_type = TRANS_GIFT, const std::string& desc = LLStringUtil::null);
+void send_join_group_response(LLUUID group_id,
+							  LLUUID transaction_id,
+							  bool accept_invite,
+							  S32 fee,
+							  bool use_offline_cap);
 
 void process_logout_reply(LLMessageSystem* msg, void**);
 void process_layer_data(LLMessageSystem *mesgsys, void **user_data);
@@ -257,6 +262,7 @@ class LLOfferInfo : public LLNotificationResponderInterface
 private:
 
 	void initRespondFunctionMap();
+	std::string getSanitizedDescription();
 
 	typedef boost::function<bool (const LLSD&, const LLSD&)> respond_function_t;
 	typedef std::map<std::string, respond_function_t> respond_function_map_t;
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index b759c2a3ab4ae2070a78158c106580cf7ce873f4..4f0460da29710c479a79cc369bc21ed98a138edb 100644
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -2817,7 +2817,8 @@ void LLViewerRegion::unpackRegionHandshake()
 void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
 {
 	capabilityNames.append("AbuseCategories");
-	//capabilityNames.append("AcceptFriendship");
+	capabilityNames.append("AcceptFriendship");
+	capabilityNames.append("AcceptGroupInvite"); // ReadOfflineMsgs recieved messages only!!!
 	capabilityNames.append("AgentPreferences");
 	capabilityNames.append("AgentState");
 	capabilityNames.append("AttachmentResources");
@@ -2827,7 +2828,8 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
 	capabilityNames.append("ChatSessionRequest");
 	capabilityNames.append("CopyInventoryFromNotecard");
 	capabilityNames.append("CreateInventoryCategory");
-	//capabilityNames.append("DeclineFriendship");
+	capabilityNames.append("DeclineFriendship");
+	capabilityNames.append("DeclineGroupInvite"); // ReadOfflineMsgs recieved messages only!!!
 	capabilityNames.append("DispatchRegionInfo");
 	capabilityNames.append("DirectDelivery");
 	capabilityNames.append("EnvironmentSettings");
@@ -2878,7 +2880,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
 	capabilityNames.append("ParcelVoiceInfoRequest");
 	capabilityNames.append("ProductInfoRequest");
 	capabilityNames.append("ProvisionVoiceAccountRequest");
-	//capabilityNames.append("ReadOfflineMsgs");
+	capabilityNames.append("ReadOfflineMsgs"); // Requires to respond reliably: AcceptFriendship, AcceptGroupInvite, DeclineFriendship, DeclineGroupInvite
 	capabilityNames.append("RemoteParcelRequest");
 	capabilityNames.append("RenderMaterials");
 	capabilityNames.append("RequestTextureDownload");
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index cef19c9c2d6d5513d8916fbb1fc295d568904baf..01ec703fe673297cad61ce18d6db3e46b980d86b 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -3826,37 +3826,22 @@ void LLViewerWindow::renderSelections( BOOL for_gl_pick, BOOL pick_parcel_walls,
 			{
 				if( !LLSelectMgr::getInstance()->getSelection()->isEmpty() )
 				{
-					BOOL moveable_object_selected = FALSE;
-					BOOL all_selected_objects_move = TRUE;
-					BOOL all_selected_objects_modify = TRUE;
-					BOOL selecting_linked_set = !gSavedSettings.getBOOL("EditLinkedParts");
-
-					for (LLObjectSelection::iterator iter = LLSelectMgr::getInstance()->getSelection()->begin();
-						 iter != LLSelectMgr::getInstance()->getSelection()->end(); iter++)
-					{
-						LLSelectNode* nodep = *iter;
-						LLViewerObject* object = nodep->getObject();
-						LLViewerObject *root_object = (object == NULL) ? NULL : object->getRootEdit();
-						BOOL this_object_movable = FALSE;
-						if (object->permMove() && !object->isPermanentEnforced() &&
-							((root_object == NULL) || !root_object->isPermanentEnforced()) &&
-							(object->permModify() || selecting_linked_set))
-						{
-							moveable_object_selected = TRUE;
-							this_object_movable = TRUE;
-						}
-						all_selected_objects_move = all_selected_objects_move && this_object_movable;
-						all_selected_objects_modify = all_selected_objects_modify && object->permModify();
-					}
+					bool all_selected_objects_move;
+					bool all_selected_objects_modify;
+					// Note: This might be costly to do on each frame and when a lot of objects are selected
+					// we might be better off with some kind of memory for selection and/or states, consider
+					// optimizing, perhaps even some kind of selection generation at level of LLSelectMgr to
+					// make whole viewer benefit.
+					LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(all_selected_objects_move, all_selected_objects_modify);
 
 					BOOL draw_handles = TRUE;
 
-					if (tool == LLToolCompTranslate::getInstance() && (!moveable_object_selected || !all_selected_objects_move))
+					if (tool == LLToolCompTranslate::getInstance() && !all_selected_objects_move)
 					{
 						draw_handles = FALSE;
 					}
 
-					if (tool == LLToolCompRotate::getInstance() && (!moveable_object_selected || !all_selected_objects_move))
+					if (tool == LLToolCompRotate::getInstance() && !all_selected_objects_move)
 					{
 						draw_handles = FALSE;
 					}
diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp
index c58e98b3fb4f97b66eccd436a4b8c499acf99694..f971554c9dec325563225c1c4d109fb27769d245 100644
--- a/indra/newview/llvoicechannel.cpp
+++ b/indra/newview/llvoicechannel.cpp
@@ -74,10 +74,18 @@ LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& sess
 
 LLVoiceChannel::~LLVoiceChannel()
 {
-	// Must check instance exists here, the singleton MAY have already been destroyed.
-	if(LLVoiceClient::instanceExists())
+	if (sSuspendedVoiceChannel == this)
 	{
-		LLVoiceClient::getInstance()->removeObserver(this);
+		sSuspendedVoiceChannel = NULL;
+	}
+	if (sCurrentVoiceChannel == this)
+	{
+		sCurrentVoiceChannel = NULL;
+		// Must check instance exists here, the singleton MAY have already been destroyed.
+		if(LLVoiceClient::instanceExists())
+		{
+			LLVoiceClient::getInstance()->removeObserver(this);
+		}
 	}
 	
 	sVoiceChannelMap.erase(mSessionID);
diff --git a/indra/newview/llweb.cpp b/indra/newview/llweb.cpp
index b816225b072517bc51de2ba1392be260be4575e5..768db047a421bb7cb76276038b8dffcf415c3342 100644
--- a/indra/newview/llweb.cpp
+++ b/indra/newview/llweb.cpp
@@ -268,6 +268,12 @@ bool LLWeb::useExternalBrowser(const std::string &url)
 		boost::match_results<std::string::const_iterator> matches;
 		return !(boost::regex_search(uri_string, matches, pattern));
 	}
+	else
+	{
+		boost::regex pattern = boost::regex("^mailto:", boost::regex::perl | boost::regex::icase);
+		boost::match_results<std::string::const_iterator> matches;
+		return boost::regex_search(url, matches, pattern);
+	}
 	return false;
 #endif
 }
diff --git a/indra/newview/skins/default/textures/icons/Inv_UnknownObject.png b/indra/newview/skins/default/textures/icons/Inv_UnknownObject.png
new file mode 100644
index 0000000000000000000000000000000000000000..10f2b31cb570e74ef1f445d9ddf2c43fadf05bed
Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Inv_UnknownObject.png differ
diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml
index d757e393669b1b582f2d1644bd4bff0eef43e419..2540ee148dd6e878cabc627b2719f1b9c9cb589a 100644
--- a/indra/newview/skins/default/textures/textures.xml
+++ b/indra/newview/skins/default/textures/textures.xml
@@ -324,6 +324,7 @@ with the same filename but different name
   <texture name="Inv_Undershirt" file_name="icons/Inv_Undershirt.png" preload="false" />
   <texture name="Inv_Link" file_name="icons/Inv_Link.png" preload="false" />
   <texture name="Inv_Invalid" file_name="icons/Inv_Invalid.png" preload="false" />
+  <texture name="Inv_Unknown" file_name="icons/Inv_UnknownObject.png" preload="false" />
   <texture name="Inv_VersionFolderClosed" file_name="icons/Inv_VersionFolderClosed.png" preload="false" />
   <texture name="Inv_VersionFolderOpen" file_name="icons/Inv_VersionFolderOpen.png" preload="false" />
   
diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml
index 3e6600762724651ed71e71f07a098146fa79d315..45f14a1192a5017d4b2f9d8ef7c2c71ccd231100 100644
--- a/indra/newview/skins/default/xui/de/strings.xml
+++ b/indra/newview/skins/default/xui/de/strings.xml
@@ -70,7 +70,7 @@ UI-Skalierung: [UI_SCALE]
 Sichtweite: [DRAW_DISTANCE] m
 Bandbreite: [NET_BANDWITH] kbit/s
 LOD-Faktor: [LOD_FACTOR]
-Darstellungsqualität: [RENDER_QUALITY] / 7
+Darstellungsqualität: [RENDER_QUALITY]
 Erweitertes Beleuchtungsmodell: [GPU_SHADERS]
 Texturspeicher: [TEXTURE_MEMORY] MB
 Erstellungszeit VFS (Cache): [VFS_TIME]
diff --git a/indra/newview/skins/default/xui/en/floater_outgoing_call.xml b/indra/newview/skins/default/xui/en/floater_outgoing_call.xml
index d714cc613ef444f4157a3f65b0fb3807cd3607b2..ae1fb4cccd24261f286b6f211880cb130027630a 100644
--- a/indra/newview/skins/default/xui/en/floater_outgoing_call.xml
+++ b/indra/newview/skins/default/xui/en/floater_outgoing_call.xml
@@ -57,7 +57,8 @@
      top="27"
      visible="false"
      width="315"
-     word_wrap="true">
+     word_wrap="true"
+     parse_urls="false">
 Connecting to [CALLEE_NAME]
     </text>
     <text
@@ -68,7 +69,8 @@ Connecting to [CALLEE_NAME]
      name="calling"
      top="27"
      width="315"
-     word_wrap="true">
+     word_wrap="true"
+     parse_urls="false">
 Calling [CALLEE_NAME]
     </text>
     <text
@@ -90,7 +92,8 @@ No Answer.  Please try again later.
    name="nearby"
    top="27"
    width="315"
-   word_wrap="true">
+   word_wrap="true"
+   parse_urls="false">
     You have been disconnected from [VOICE_CHANNEL_NAME].  [RECONNECT_NEARBY]
   </text>
   <text
@@ -101,7 +104,8 @@ No Answer.  Please try again later.
    name="nearby_P2P_by_other"
    top="27"
    width="315"
-   word_wrap="true">
+   word_wrap="true"
+   parse_urls="false">
     Your call has ended.  [RECONNECT_NEARBY]
   </text>
   <text
@@ -112,7 +116,8 @@ No Answer.  Please try again later.
    name="nearby_P2P_by_agent"
    top="27"
    width="315"
-   word_wrap="true">
+   word_wrap="true"
+   parse_urls="false">
     You have ended the call.  [RECONNECT_NEARBY]
   </text>
   <text
@@ -123,7 +128,8 @@ No Answer.  Please try again later.
      name="leaving"
      top="62"
      width="315"
-     word_wrap="true">
+     word_wrap="true"
+     parse_urls="false">
 Leaving [CURRENT_CHAT].
     </text>
   <button
diff --git a/indra/newview/skins/default/xui/en/panel_group_notify.xml b/indra/newview/skins/default/xui/en/panel_group_notify.xml
index 4121acdfb01cee98572f4ffd2ace08749065416f..60e5a03d5183ca711b53db37c0af727d5e5a6fb2 100644
--- a/indra/newview/skins/default/xui/en/panel_group_notify.xml
+++ b/indra/newview/skins/default/xui/en/panel_group_notify.xml
@@ -43,6 +43,7 @@
          layout="topleft"
          left_pad="10"
          name="title"
+         parse_urls="false"
          text_color="GroupNotifyTextColor"
          top="5"
          use_ellipses="true"
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 882fbaf6345ff239e5a5e5e158290c9192cc51f8..f5f4b4acab64ad46b2eed27a0b5bdf3cc373709f 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -53,7 +53,7 @@ UI Scaling: [UI_SCALE]
 Draw distance: [DRAW_DISTANCE]m
 Bandwidth: [NET_BANDWITH]kbit/s
 LOD factor: [LOD_FACTOR]
-Render quality: [RENDER_QUALITY] / 7
+Render quality: [RENDER_QUALITY]
 Advanced Lighting Model: [GPU_SHADERS]
 Texture memory: [TEXTURE_MEMORY]MB
 VFS (cache) creation time: [VFS_TIME]
diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml
index 341c6d2fe83ca8aeb2fa5ae884556571d4804fb1..8e795de2d63d2acea8bbd2f88aa7aea977179411 100644
--- a/indra/newview/skins/default/xui/es/strings.xml
+++ b/indra/newview/skins/default/xui/es/strings.xml
@@ -62,7 +62,7 @@ Ajuste de escala de IU: [UI_SCALE]
 Distancia de dibujo: [DRAW_DISTANCE]m
 Ancho de banda: [NET_BANDWITH]kbit/s
 Factor de LOD: [LOD_FACTOR]
-Calidad de renderizado: [RENDER_QUALITY] / 7
+Calidad de renderizado: [RENDER_QUALITY]
 Modelo de iluminación avanzado: [GPU_SHADERS]
 Memoria de textura: [TEXTURE_MEMORY]MB
 Tiempo de creación de VFS (caché): [VFS_TIME]
diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml
index d76beee93d29959032d4de93d511c8e894b549ca..23d93e57bfbeb61e1e06c0ea14e5b061bd730bc0 100644
--- a/indra/newview/skins/default/xui/fr/strings.xml
+++ b/indra/newview/skins/default/xui/fr/strings.xml
@@ -71,7 +71,7 @@ Ajustement de la taille de la police : [FONT_SIZE_ADJUSTMENT] pts
 Limite d’affichage : [DRAW_DISTANCE] m
 Bande passante : [NET_BANDWITH] kbit/s
 Facteur LOD (niveau de détail) : [LOD_FACTOR]
-Qualité de rendu : [RENDER_QUALITY] / 7
+Qualité de rendu : [RENDER_QUALITY]
 Modèle d’éclairage avancé : [GPU_SHADERS]
 Mémoire textures : [TEXTURE_MEMORY] Mo
 Durée de création VFS (cache) : [VFS_TIME]
diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml
index ad74e161708ef8e40b1c3e5190bfdaba89abc718..e2610ef0579eb94400ffbd1fb6656ab5b54c31a9 100644
--- a/indra/newview/skins/default/xui/it/strings.xml
+++ b/indra/newview/skins/default/xui/it/strings.xml
@@ -68,7 +68,7 @@ Scala UI: [UI_SCALE]
 Distanza visualizzazione: [DRAW_DISTANCE] m
 Larghezza banda: [NET_BANDWITH] kbit/s
 Fattore livello di dettaglio: [LOD_FACTOR]
-Qualità di rendering: [RENDER_QUALITY] / 7
+Qualità di rendering: [RENDER_QUALITY]
 Modello illuminazione avanzato: [GPU_SHADERS]
 Memoria texture: [TEXTURE_MEMORY] MB
 Data/ora creazione VFS (cache): [VFS_TIME]
@@ -1505,7 +1505,7 @@ Se continui a ricevere questo messaggio, contatta l&apos;assistenza Second Life
 		Trascina le cartelle in questa area per metterle in vendita su [[MARKETPLACE_DASHBOARD_URL] Marketplace].
 	</string>
 	<string name="InventoryItemsCount">
-		( [ITEM_COUNT] oggetti )
+		( [ITEMS_COUNT] oggetti )
 	</string>
 	<string name="Marketplace Validation Warning Stock">
 		la cartella di magazzino deve essere inclusa in una cartella di versione
diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml
index 5ca7ddd92c31b36b30c12a6baf143836102644c4..7faf463fd4a7e8ddcacde9a350462ce2020b90ea 100644
--- a/indra/newview/skins/default/xui/ja/strings.xml
+++ b/indra/newview/skins/default/xui/ja/strings.xml
@@ -71,7 +71,7 @@ UI スケーリング: [UI_SCALE]
 描画距離:[DRAW_DISTANCE]m
 帯域幅:[NET_BANDWITH]kbit/s
 LOD 係数: [LOD_FACTOR]
-表示品質: [RENDER_QUALITY] / 7
+表示品質: [RENDER_QUALITY]
 高度なライティングモデル: [GPU_SHADERS]
 テクスチャメモリ: [TEXTURE_MEMORY]MB
 VFS(キャッシュ)作成時間: [VFS_TIME]
diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml
index ee048e28e3c600c5ffb60faef94519bedbf40600..046e7db47c8249d66b9df78b020a097a5a5aeff0 100644
--- a/indra/newview/skins/default/xui/pt/strings.xml
+++ b/indra/newview/skins/default/xui/pt/strings.xml
@@ -62,7 +62,7 @@ Escala de interface: [UI_SCALE]
 Dist. máxima: [DRAW_DISTANCE]m
 Largura de banda: [NET_BANDWITH]kbit/s
 Fator LOD: [LOD_FACTOR]
-Qualidade de renderização: [RENDER_QUALITY] / 7
+Qualidade de renderização: [RENDER_QUALITY]
 Modelo de iluminação avançado: [GPU_SHADERS]
 Memória de textura: [TEXTURE_MEMORY]MB
 Tempo de criação de VFS (cache): [VFS_TIME]
diff --git a/indra/newview/skins/default/xui/ru/floater_tos.xml b/indra/newview/skins/default/xui/ru/floater_tos.xml
index 3f2b5747d58eb1e2713b2de663a50382b5c0966e..7196a04de1a4da4f3a724b0606c50173bb550e70 100644
--- a/indra/newview/skins/default/xui/ru/floater_tos.xml
+++ b/indra/newview/skins/default/xui/ru/floater_tos.xml
@@ -16,6 +16,6 @@
 	<text name="agree_list">
 		Я прочитал и согласен с Условиями и положениями по конфиденциальности Пользовательского соглашения, включая требования по разрешению разногласий Second Life.
 	</text>
-	<button label="Продолжить" label_selected="Продолжить" name="Continue"/>
+	<button label="Продолжить" label_selected="Продолжить" name="Continue" top_delta="45"/>
 	<button label="Отмена" label_selected="Отмена" name="Cancel"/>
 </floater>
diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml
index 95225da7d0b1b8bbdeb5de98f18b7f83d29ebae7..267c7171898972982994c6a5adafaed95818f0e2 100644
--- a/indra/newview/skins/default/xui/ru/strings.xml
+++ b/indra/newview/skins/default/xui/ru/strings.xml
@@ -71,7 +71,7 @@ SLURL: &lt;nolink&gt;[SLURL]&lt;/nolink&gt;
 Дальность отрисовки: [DRAW_DISTANCE] м
 Ширина канала: [NET_BANDWITH] кбит/с
 Коэффициент детализации: [LOD_FACTOR]
-Качество визуализации: [RENDER_QUALITY] / 7
+Качество визуализации: [RENDER_QUALITY]
 Расширенная модель освещения: [GPU_SHADERS]
 Память текстур: [TEXTURE_MEMORY] МБ
 Время создания VFS (кэш): [VFS_TIME]
diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml
index 6850c67df3353a8a8a7745ee1b3bae653871f268..6542e61475a51aaee2f260e224f8ba1644ee2e12 100644
--- a/indra/newview/skins/default/xui/tr/strings.xml
+++ b/indra/newview/skins/default/xui/tr/strings.xml
@@ -71,7 +71,7 @@ Kullanıcı Arayüzü Ölçekleme: [UI_SCALE]
 Çizme mesafesi: [DRAW_DISTANCE] m
 Bant geniÅŸliÄŸi: [NET_BANDWITH] kbit/sn
 Ayrıntı seviyesi faktörü: [LOD_FACTOR]
-Ä°ÅŸleme kalitesi: [RENDER_QUALITY] / 7
+Ä°ÅŸleme kalitesi: [RENDER_QUALITY]
 Gelişmiş Aydınlatma Modeli: [GPU_SHADERS]
 Doku belleÄŸi: [TEXTURE_MEMORY]MB
 VFS (önbellek) oluşturma zamanı: [VFS_TIME]
diff --git a/indra/newview/skins/default/xui/zh/strings.xml b/indra/newview/skins/default/xui/zh/strings.xml
index e4f9c5d4330dcdeb5086c4392cc469752f570e21..24d8dc60cba341e96115a6ddedc04c4249796d8e 100644
--- a/indra/newview/skins/default/xui/zh/strings.xml
+++ b/indra/newview/skins/default/xui/zh/strings.xml
@@ -71,7 +71,7 @@
 描繪距離:[DRAW_DISTANCE]公尺
 頻寬:[NET_BANDWITH]千位元/秒
 細節層次率:[LOD_FACTOR]
-呈像品質:[RENDER_QUALITY] / 7
+呈像品質:[RENDER_QUALITY]
 進階照明模型:[GPU_SHADERS]
 材質記憶體:[TEXTURE_MEMORY]MB
 VFS(快取)建立時間:[VFS_TIME]
diff --git a/scripts/content_tools/anim_tool.py b/scripts/content_tools/anim_tool.py
index 77bf731ae60e8be463565eba3bbcc5bcb463304d..3496617b21404b7a06dff73dbe546e5b42582abe 100644
--- a/scripts/content_tools/anim_tool.py
+++ b/scripts/content_tools/anim_tool.py
@@ -1,14 +1,22 @@
-#!runpy.sh
-
+#!/usr/bin/python
 """\
-
-This module contains tools for manipulating the .anim files supported
-for Second Life animation upload. Note that this format is unrelated
-to any non-Second Life formats of the same name.
-
-$LicenseInfo:firstyear=2016&license=viewerlgpl$
+@file   anim_tool.py
+@author Brad Payne, Nat Goodspeed
+@date   2015-09-15
+@brief  This module contains tools for manipulating the .anim files supported
+        for Second Life animation upload. Note that this format is unrelated
+        to any non-Second Life formats of the same name.
+
+        This code is a Python translation of the logic in
+        LLKeyframeMotion::serialize() and deserialize():
+        https://bitbucket.org/lindenlab/viewer-release/src/827a910542a9af0a39b0ca03663c02e5c83869ea/indra/llcharacter/llkeyframemotion.cpp?at=default&fileviewer=file-view-default#llkeyframemotion.cpp-1864
+        https://bitbucket.org/lindenlab/viewer-release/src/827a910542a9af0a39b0ca03663c02e5c83869ea/indra/llcharacter/llkeyframemotion.cpp?at=default&fileviewer=file-view-default#llkeyframemotion.cpp-1220
+        save that there is no support for old-style .anim files, permitting
+        simpler code.
+
+$LicenseInfo:firstyear=2015&license=viewerlgpl$
 Second Life Viewer Source Code
-Copyright (C) 2016, Linden Research, Inc.
+Copyright (C) 2015, Linden Research, Inc.
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -28,63 +36,85 @@
 $/LicenseInfo$
 """
 
-import sys
-import os
-import struct
-import StringIO
 import math
-import argparse
+import os
 import random
-from lxml import etree
+from cStringIO import StringIO
+import struct
+import sys
+from xml.etree import ElementTree
+
+class Error(Exception):
+    pass
+
+class BadFormat(Error):
+    """
+    Something went wrong trying to read the specified .anim file.
+    """
+    pass
+
+class ExtraneousData(BadFormat):
+    """
+    Specifically, the .anim file in question contains more data than needed.
+    This could happen if the file isn't a .anim at all, and it 'just happens'
+    to read properly otherwise -- e.g. a block of all zero bytes could look
+    like empty name strings, empty arrays etc. That could be a legitimate
+    error -- or it could be due to a sloppy tool. Break this exception out
+    separately so caller can distinguish if desired.
+    """
+    pass
 
 U16MAX = 65535
-OOU16MAX = 1.0/(float)(U16MAX)
+# One Over U16MAX, for scaling
+OOU16MAX = 1.0/float(U16MAX)
 
 LL_MAX_PELVIS_OFFSET = 5.0
 
 class FilePacker(object):
     def __init__(self):
-        self.data = StringIO.StringIO()
-        self.offset = 0
+        self.buffer = StringIO()
 
     def write(self,filename):
-        f = open(filename,"wb")
-        f.write(self.data.getvalue())
-        f.close()
+        with open(filename,"wb") as f:
+            f.write(self.buffer.getvalue())
 
     def pack(self,fmt,*args):
         buf = struct.pack(fmt, *args)
-        self.offset += struct.calcsize(fmt)
-        self.data.write(buf)
+        self.buffer.write(buf)
 
     def pack_string(self,str,size=0):
-        buf = str + "\000"
-        if size and (len(buf) < size):
-            buf += "\000" * (size-len(buf))
-        self.data.write(buf)
+        # If size == 0, caller doesn't care, just wants a terminating nul byte
+        size = size or (len(str) + 1)
+        # Nonzero size means a fixed-length field. If the passed string (plus
+        # its terminating nul) exceeds that fixed length, we'll have to
+        # truncate. But make sure we still leave room for the final nul byte!
+        str = str[:size-1]
+        # Now pad what's left of str out to 'size' with nul bytes.
+        buf = str + ("\000" * (size-len(str)))
+        self.buffer.write(buf)
         
 class FileUnpacker(object):
     def __init__(self, filename):
-        f = open(filename,"rb")
-        self.data = f.read()
+        with open(filename,"rb") as f:
+            self.buffer = f.read()
         self.offset = 0
 
     def unpack(self,fmt):
-        result = struct.unpack_from(fmt, self.data, self.offset)
+        result = struct.unpack_from(fmt, self.buffer, self.offset)
         self.offset += struct.calcsize(fmt)
         return result
     
     def unpack_string(self, size=0):
-        result = ""
-        i = 0
-        while (self.data[self.offset+i] != "\000"):
-            result += self.data[self.offset+i]
-            i += 1
-        i += 1
+        # Nonzero size means we must consider exactly the next 'size'
+        # characters in self.buffer.
         if size:
-            # fixed-size field for the string
-            i = size
-        self.offset += i
+            self.offset += size
+            # but stop at the first nul byte
+            return self.buffer[self.offset-size:self.offset].split("\000", 1)[0]
+        # Zero size means consider everything until the next nul character.
+        result = self.buffer[self.offset:].split("\000", 1)[0]
+        # don't forget to skip the nul byte too
+        self.offset += len(result) + 1
         return result
 
 # translated from the C++ version in lldefs.h
@@ -108,7 +138,7 @@ def F32_to_U16(val, lower, upper):
 # translated from the C++ version in llquantize.h
 def U16_to_F32(ival, lower, upper):
     if ival < 0 or ival > U16MAX:
-        raise Exception("U16 out of range: "+ival)
+        raise ValueError("U16 out of range: %s" % ival)
     val = ival*OOU16MAX
     delta = (upper - lower)
     val *= delta
@@ -121,71 +151,100 @@ def U16_to_F32(ival, lower, upper):
         val = 0.0
     return val; 
 
-class BadFormat(Exception):
-    pass
-
 class RotKey(object):
-    def __init__(self):
-        pass
-
-    def unpack(self, anim, fup):
-        (self.time_short, ) = fup.unpack("<H")
-        self.time = U16_to_F32(self.time_short, 0.0, anim.duration)
+    def __init__(self, time, duration, rot):
+        """
+        This constructor instantiates a RotKey object from scratch, as it
+        were, converting from float time to time_short.
+        """
+        self.time = time
+        self.time_short = F32_to_U16(time, 0.0, duration) \
+                          if time is not None else None
+        self.rotation = rot
+
+    @staticmethod
+    def unpack(duration, fup):
+        """
+        This staticmethod constructs a RotKey by loadingfrom a FileUnpacker.
+        """
+        # cheat the other constructor
+        this = RotKey(None, None, None)
+        # load time_short directly from the file
+        (this.time_short, ) = fup.unpack("<H")
+        # then convert to float time
+        this.time = U16_to_F32(this.time_short, 0.0, duration)
+        # convert each coordinate of the rotation from short to float
         (x,y,z) = fup.unpack("<HHH")
-        self.rotation = [U16_to_F32(i, -1.0, 1.0) for i in (x,y,z)]
+        this.rotation = [U16_to_F32(i, -1.0, 1.0) for i in (x,y,z)]
+        return this
 
     def dump(self, f):
-        print >>f, "    rot_key: t",self.time,"st",self.time_short,"rot",",".join([str(f) for f in self.rotation])
+        print >>f, "    rot_key: t %.3f" % self.time,"st",self.time_short,"rot",",".join("%.3f" % f for f in self.rotation)
 
-    def pack(self, anim, fp):
-        if not hasattr(self,"time_short"):
-            self.time_short = F32_to_U16(self.time, 0.0, anim.duration)
+    def pack(self, fp):
         fp.pack("<H",self.time_short)
         (x,y,z) = [F32_to_U16(v, -1.0, 1.0) for v in self.rotation]
         fp.pack("<HHH",x,y,z)
         
 class PosKey(object):
-    def __init__(self):
-        pass
-
-    def unpack(self, anim, fup):
-        (self.time_short, ) = fup.unpack("<H")
-        self.time = U16_to_F32(self.time_short, 0.0, anim.duration)
+    def __init__(self, time, duration, pos):
+        """
+        This constructor instantiates a PosKey object from scratch, as it
+        were, converting from float time to time_short.
+        """
+        self.time = time
+        self.time_short = F32_to_U16(time, 0.0, duration) \
+                          if time is not None else None
+        self.position = pos
+
+    @staticmethod
+    def unpack(duration, fup):
+        """
+        This staticmethod constructs a PosKey by loadingfrom a FileUnpacker.
+        """
+        # cheat the other constructor
+        this = PosKey(None, None, None)
+        # load time_short directly from the file
+        (this.time_short, ) = fup.unpack("<H")
+        # then convert to float time
+        this.time = U16_to_F32(this.time_short, 0.0, duration)
+        # convert each coordinate of the rotation from short to float
         (x,y,z) = fup.unpack("<HHH")
-        self.position = [U16_to_F32(i, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET) for i in (x,y,z)]
+        this.position = [U16_to_F32(i, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET)
+                         for i in (x,y,z)]
+        return this
 
     def dump(self, f):
-        print >>f, "    pos_key: t",self.time,"pos ",",".join([str(f) for f in self.position])
+        print >>f, "    pos_key: t %.3f" % self.time,"pos ",",".join("%.3f" % f for f in self.position)
         
-    def pack(self, anim, fp):
-        if not hasattr(self,"time_short"):
-            self.time_short = F32_to_U16(self.time, 0.0, anim.duration)
+    def pack(self, fp):
         fp.pack("<H",self.time_short)
         (x,y,z) = [F32_to_U16(v, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET) for v in self.position]
         fp.pack("<HHH",x,y,z)
 
 class Constraint(object):
-    def __init__(self):
-        pass
-
-    def unpack(self, anim, fup):
-        (self.chain_length, self.constraint_type) = fup.unpack("<BB")
-        self.source_volume = fup.unpack_string(16)
-        self.source_offset = fup.unpack("<fff")
-        self.target_volume = fup.unpack_string(16)
-        self.target_offset = fup.unpack("<fff")
-        self.target_dir = fup.unpack("<fff")
-        fmt = "<ffff"
-        (self.ease_in_start, self.ease_in_stop, self.ease_out_start, self.ease_out_stop) = fup.unpack("<ffff")
-
-    def pack(self, anim, fp):
+    @staticmethod
+    def unpack(duration, fup):
+        this = Constraint()
+        (this.chain_length, this.constraint_type) = fup.unpack("<BB")
+        this.source_volume = fup.unpack_string(16)
+        this.source_offset = fup.unpack("<fff")
+        this.target_volume = fup.unpack_string(16)
+        this.target_offset = fup.unpack("<fff")
+        this.target_dir = fup.unpack("<fff")
+        (this.ease_in_start, this.ease_in_stop, this.ease_out_start, this.ease_out_stop) = \
+                             fup.unpack("<ffff")
+        return this
+
+    def pack(self, fp):
         fp.pack("<BB", self.chain_length, self.constraint_type)
         fp.pack_string(self.source_volume, 16)
         fp.pack("<fff", *self.source_offset)
         fp.pack_string(self.target_volume, 16)
         fp.pack("<fff", *self.target_offset)
         fp.pack("<fff", *self.target_dir)
-        fp.pack("<ffff", self.ease_in_start, self.ease_in_stop, self.ease_out_start, self.ease_out_stop)
+        fp.pack("<ffff", self.ease_in_start, self.ease_in_stop,
+                self.ease_out_start, self.ease_out_stop)
 
     def dump(self, f):
         print >>f, "  constraint:"
@@ -202,30 +261,26 @@ def dump(self, f):
         print >>f, "    ease_out_stop",self.ease_out_stop
         
 class Constraints(object):
-    def __init__(self):
-        pass
-
-    def unpack(self, anim, fup):
-        (self.num_constraints, ) = fup.unpack("<i")
-        self.constraints = []
-        for i in xrange(self.num_constraints):
-            constraint = Constraint()
-            constraint.unpack(anim, fup)
-            self.constraints.append(constraint)
-
-    def pack(self, anim, fp):
-        fp.pack("<i",self.num_constraints)
+    @staticmethod
+    def unpack(duration, fup):
+        this = Constraints()
+        (num_constraints, ) = fup.unpack("<i")
+        this.constraints = [Constraint.unpack(duration, fup)
+                            for i in xrange(num_constraints)]
+        return this
+
+    def pack(self, fp):
+        fp.pack("<i",len(self.constraints))
         for c in self.constraints:
-            c.pack(anim,fp)
+            c.pack(fp)
 
     def dump(self, f):
-        print >>f, "constraints:",self.num_constraints
+        print >>f, "constraints:",len(self.constraints)
         for c in self.constraints:
             c.dump(f)
 
 class PositionCurve(object):
     def __init__(self):
-        self.num_pos_keys = 0
         self.keys = []
 
     def is_static(self):
@@ -236,28 +291,27 @@ def is_static(self):
                     return False
         return True
 
-    def unpack(self, anim, fup):
-        (self.num_pos_keys, ) = fup.unpack("<i")
-        self.keys = []
-        for k in xrange(0,self.num_pos_keys):
-            pos_key = PosKey()
-            pos_key.unpack(anim, fup)
-            self.keys.append(pos_key)
+    @staticmethod
+    def unpack(duration, fup):
+        this = PositionCurve()
+        (num_pos_keys, ) = fup.unpack("<i")
+        this.keys = [PosKey.unpack(duration, fup)
+                     for k in xrange(num_pos_keys)]
+        return this
 
-    def pack(self, anim, fp):
-        fp.pack("<i",self.num_pos_keys)
+    def pack(self, fp):
+        fp.pack("<i",len(self.keys))
         for k in self.keys:
-            k.pack(anim, fp)
+            k.pack(fp)
 
     def dump(self, f):
         print >>f, "  position_curve:"
-        print >>f, "    num_pos_keys", self.num_pos_keys
-        for k in xrange(0,self.num_pos_keys):
-            self.keys[k].dump(f)
+        print >>f, "    num_pos_keys", len(self.keys)
+        for k in self.keys:
+            k.dump(f)
 
 class RotationCurve(object):
     def __init__(self):
-        self.num_rot_keys = 0
         self.keys = []
 
     def is_static(self):
@@ -268,42 +322,46 @@ def is_static(self):
                     return False
         return True
 
-    def unpack(self, anim, fup):
-        (self.num_rot_keys, ) = fup.unpack("<i")
-        self.keys = []
-        for k in xrange(0,self.num_rot_keys):
-            rot_key = RotKey()
-            rot_key.unpack(anim, fup)
-            self.keys.append(rot_key)
+    @staticmethod
+    def unpack(duration, fup):
+        this = RotationCurve()
+        (num_rot_keys, ) = fup.unpack("<i")
+        this.keys = [RotKey.unpack(duration, fup)
+                     for k in xrange(num_rot_keys)]
+        return this
 
-    def pack(self, anim, fp):
-        fp.pack("<i",self.num_rot_keys)
+    def pack(self, fp):
+        fp.pack("<i",len(self.keys))
         for k in self.keys:
-            k.pack(anim, fp)
+            k.pack(fp)
 
     def dump(self, f):
         print >>f, "  rotation_curve:"
-        print >>f, "    num_rot_keys", self.num_rot_keys
-        for k in xrange(0,self.num_rot_keys):
-            self.keys[k].dump(f)
+        print >>f, "    num_rot_keys", len(self.keys)
+        for k in self.keys:
+            k.dump(f)
             
 class JointInfo(object):
-    def __init__(self):
-        pass
-
-    def unpack(self, anim, fup):
-        self.joint_name = fup.unpack_string()
-        (self.joint_priority, ) = fup.unpack("<i")
+    def __init__(self, name, priority):
+        self.joint_name = name
+        self.joint_priority = priority
         self.rotation_curve = RotationCurve()
-        self.rotation_curve.unpack(anim, fup)
         self.position_curve = PositionCurve()
-        self.position_curve.unpack(anim, fup)
 
-    def pack(self, anim, fp):
+    @staticmethod
+    def unpack(duration, fup):
+        this = JointInfo(None, None)
+        this.joint_name = fup.unpack_string()
+        (this.joint_priority, ) = fup.unpack("<i")
+        this.rotation_curve = RotationCurve.unpack(duration, fup)
+        this.position_curve = PositionCurve.unpack(duration, fup)
+        return this
+
+    def pack(self, fp):
         fp.pack_string(self.joint_name)
         fp.pack("<i", self.joint_priority)
-        self.rotation_curve.pack(anim, fp)
-        self.position_curve.pack(anim, fp)
+        self.rotation_curve.pack(fp)
+        self.position_curve.pack(fp)
 
     def dump(self, f):
         print >>f, "joint:"
@@ -313,13 +371,26 @@ def dump(self, f):
         self.position_curve.dump(f)
 
 class Anim(object):
-    def __init__(self, filename=None):
+    def __init__(self, filename=None, verbose=False):
+        # set this FIRST as it's consulted by read() and unpack()
+        self.verbose = verbose
         if filename:
             self.read(filename)
 
     def read(self, filename):
         fup = FileUnpacker(filename)
-        self.unpack(fup)
+        try:
+            self.unpack(fup)
+        except struct.error as err:
+            raise BadFormat("error reading %s: %s" % (filename, err))
+        # By the end of streaming data in from our FileUnpacker, we should
+        # have consumed the entire thing. If there's excess data, it's
+        # entirely possible that this is a garbage file that happens to
+        # resemble a valid degenerate .anim file, e.g. with zero counts of
+        # things.
+        if fup.offset != len(fup.buffer):
+            raise ExtraneousData("extraneous data in %s; is it really a Linden .anim file?" %
+                                 filename)
 
     # various validity checks could be added - see LLKeyframeMotion::deserialize()
     def unpack(self,fup):
@@ -333,27 +404,57 @@ def unpack(self,fup):
         else:
             raise BadFormat("Bad combination of version, sub_version: %d %d" % (self.version, self.sub_version))
 
+        # Also consult BVH conversion code for stricter checks
+
+        # C++ deserialize() checks self.base_priority against
+        # LLJoint::ADDITIVE_PRIORITY and LLJoint::USE_MOTION_PRIORITY,
+        # possibly sets self.max_priority
+        # checks self.duration against MAX_ANIM_DURATION !!
+        # checks self.emote_name != str(self.ID)
+        # checks self.hand_pose against LLHandMotion::NUM_HAND_POSES !!
+        # checks 0 < num_joints <= LL_CHARACTER_MAX_JOINTS (no need --
+        # validate names)
+        # checks each joint_name neither "mScreen" nor "mRoot" ("attempted to
+        # animate special joint") !!
+        # checks each joint_name can be found in mCharacter
+        # checks each joint_priority >= LLJoint::USE_MOTION_PRIORITY
+        # tracks max observed joint_priority, excluding USE_MOTION_PRIORITY
+        # checks each 0 <= RotKey.time <= self.duration !!
+        # checks each RotKey.rotation.isFinite() !!
+        # checks each PosKey.position.isFinite() !!
+        # checks 0 <= num_constraints <= MAX_CONSTRAINTS  !!
+        # checks each Constraint.chain_length <= num_joints
+        # checks each Constraint.constraint_type < NUM_CONSTRAINT_TYPES !!
+        # checks each Constraint.source_offset.isFinite() !!
+        # checks each Constraint.target_offset.isFinite() !!
+        # checks each Constraint.target_dir.isFinite() !!
+        # from https://bitbucket.org/lindenlab/viewer-release/src/827a910542a9af0a39b0ca03663c02e5c83869ea/indra/llcharacter/llkeyframemotion.cpp?at=default&fileviewer=file-view-default#llkeyframemotion.cpp-1812 :
+        # find joint to which each Constraint's collision volume is attached;
+        # for each link in Constraint.chain_length, walk to joint's parent,
+        # find that parent in list of joints, set its index in index list
+
         self.emote_name = fup.unpack_string()
         
-        (self.loop_in_point, self.loop_out_point, self.loop, self.ease_in_duration, self.ease_out_duration, self.hand_pose, self.num_joints) = fup.unpack("@ffiffII")
+        (self.loop_in_point, self.loop_out_point, self.loop,
+         self.ease_in_duration, self.ease_out_duration, self.hand_pose, num_joints) = \
+            fup.unpack("@ffiffII")
         
-        self.joints = []
-        for j in xrange(0,self.num_joints):
-            joint_info = JointInfo()
-            joint_info.unpack(self, fup)
-            self.joints.append(joint_info)
-            print "unpacked joint",joint_info.joint_name
-        self.constraints = Constraints()
-        self.constraints.unpack(self, fup)
-        self.data = fup.data
+        self.joints = [JointInfo.unpack(self.duration, fup)
+                       for j in xrange(num_joints)]
+        if self.verbose:
+            for joint_info in self.joints:
+                print "unpacked joint",joint_info.joint_name
+        self.constraints = Constraints.unpack(self.duration, fup)
+        self.buffer = fup.buffer
         
     def pack(self, fp):
         fp.pack("@HHhf", self.version, self.sub_version, self.base_priority, self.duration)
         fp.pack_string(self.emote_name, 0)
-        fp.pack("@ffiffII", self.loop_in_point, self.loop_out_point, self.loop, self.ease_in_duration, self.ease_out_duration, self.hand_pose, self.num_joints)
+        fp.pack("@ffiffII", self.loop_in_point, self.loop_out_point, self.loop,
+                self.ease_in_duration, self.ease_out_duration, self.hand_pose, len(self.joints))
         for j in self.joints:
-            j.pack(anim, fp)
-        self.constraints.pack(anim, fp)
+            j.pack(fp)
+        self.constraints.pack(fp)
 
     def dump(self, filename="-"):
         if filename=="-":
@@ -370,7 +471,7 @@ def dump(self, filename="-"):
         print >>f, "ease_in_duration: ", self.ease_in_duration
         print >>f, "ease_out_duration: ", self.ease_out_duration
         print >>f, "hand_pose", self.hand_pose
-        print >>f, "num_joints", self.num_joints
+        print >>f, "num_joints", len(self.joints)
         for j in self.joints:
             j.dump(f)
         self.constraints.dump(f)
@@ -382,10 +483,9 @@ def write(self, filename):
 
     def write_src_data(self, filename):
         print "write file",filename
-        f = open(filename,"wb")
-        f.write(self.data)
-        f.close()
-        
+        with open(filename,"wb") as f:
+            f.write(self.buffer)
+
     def find_joint(self, name):
         joints = [j for j in self.joints if j.joint_name == name]
         if joints:
@@ -395,91 +495,71 @@ def find_joint(self, name):
 
     def add_joint(self, name, priority):
         if not self.find_joint(name):
-            j = JointInfo()
-            j.joint_name = name
-            j.joint_priority = priority
-            j.rotation_curve = RotationCurve()
-            j.position_curve = PositionCurve()
-            self.joints.append(j)
-            self.num_joints = len(self.joints)
+            self.joints.append(JointInfo(name, priority))
 
     def delete_joint(self, name):
         j = self.find_joint(name)
         if j:
-            if args.verbose:
+            if self.verbose:
                 print "removing joint", name
-            anim.joints.remove(j)
-            anim.num_joints = len(self.joints)
+            self.joints.remove(j)
         else:
-            if args.verbose:
+            if self.verbose:
                 print "joint not found to remove", name
 
     def summary(self):
         nj = len(self.joints)
         nz = len([j for j in self.joints if j.joint_priority > 0])
-        nstatic = len([j for j in self.joints if j.rotation_curve.is_static() and j.position_curve.is_static()])
+        nstatic = len([j for j in self.joints
+                       if j.rotation_curve.is_static()
+                       and j.position_curve.is_static()])
         print "summary: %d joints, non-zero priority %d, static %d" % (nj, nz, nstatic)
 
     def add_pos(self, joint_names, positions):
         js = [joint for joint in self.joints if joint.joint_name in joint_names]
         for j in js:
-            if args.verbose:
+            if self.verbose:
                 print "adding positions",j.joint_name,positions
             j.joint_priority = 4
-            j.position_curve.num_pos_keys = len(positions)
-            j.position_curve.keys = []
-            for i,pos in enumerate(positions):
-                key = PosKey()
-                key.time = self.duration * i / (len(positions) - 1)
-                key.time_short = F32_to_U16(key.time, 0.0, self.duration)
-                key.position = pos
-                j.position_curve.keys.append(key)
+            j.position_curve.keys = [PosKey(self.duration * i / (len(positions) - 1),
+                                            self.duration,
+                                            pos)
+                                     for i,pos in enumerate(positions)]
 
     def add_rot(self, joint_names, rotations):
         js = [joint for joint in self.joints if joint.joint_name in joint_names]
         for j in js:
             print "adding rotations",j.joint_name
             j.joint_priority = 4
-            j.rotation_curve.num_rot_keys = len(rotations)
-            j.rotation_curve.keys = []
-            for i,pos in enumerate(rotations):
-                key = RotKey()
-                key.time = self.duration * i / (len(rotations) - 1)
-                key.time_short = F32_to_U16(key.time, 0.0, self.duration)
-                key.rotation = pos
-                j.rotation_curve.keys.append(key)
+            j.rotation_curve.keys = [RotKey(self.duration * i / (len(rotations) - 1),
+                                            self.duration,
+                                            rot)
+                                     for i,rot in enumerate(rotations)]
 
 def twistify(anim, joint_names, rot1, rot2):
     js = [joint for joint in anim.joints if joint.joint_name in joint_names]
     for j in js:
         print "twisting",j.joint_name
-        print j.rotation_curve.num_rot_keys
+        print len(j.rotation_curve.keys)
         j.joint_priority = 4
-        j.rotation_curve.num_rot_keys = 2
-        j.rotation_curve.keys = []
-        key1 = RotKey()
-        key1.time_short = 0
-        key1.time = U16_to_F32(key1.time_short, 0.0, anim.duration)
-        key1.rotation = rot1
-        key2 = RotKey()
-        key2.time_short = U16MAX
-        key2.time = U16_to_F32(key2.time_short, 0.0, anim.duration)
-        key2.rotation = rot2
-        j.rotation_curve.keys.append(key1)
-        j.rotation_curve.keys.append(key2)
+        # Set the joint(s) to rot1 at time 0, rot2 at the full duration.
+        j.rotation_curve.keys = [
+            RotKey(0.0, anim.duration, rot1),
+            RotKey(anim.duration, anim.duration, rot2)]
 
 def float_triple(arg):
     vals = arg.split()
     if len(vals)==3:
         return [float(x) for x in vals]
     else:
-        raise Exception("arg %s does not resolve to a float triple" % arg)
+        raise ValueError("arg %s does not resolve to a float triple" % arg)
 
 def get_joint_by_name(tree,name):
     if tree is None:
         return None
-    matches = [elt for elt in tree.getroot().iter() if \
-                   elt.get("name")==name and elt.tag in ["bone", "collision_volume", "attachment_point"]]
+    matches = [elt for elt in tree.getroot().iter()
+               if elt.get("name")==name
+               and elt.tag in ["bone", "collision_volume", "attachment_point"]]
     if len(matches)==1:
         return matches[0]
     elif len(matches)>1:
@@ -496,121 +576,135 @@ def get_elt_pos(elt):
     else:
         return (0.0, 0.0, 0.0)
 
-def resolve_joints(names, skel_tree, lad_tree):
-    print "resolve joints, no_hud is",args.no_hud
+def resolve_joints(names, skel_tree, lad_tree, no_hud=False):
+    print "resolve joints, no_hud is",no_hud
     if skel_tree and lad_tree:
         all_elts = [elt for elt in skel_tree.getroot().iter()]
         all_elts.extend([elt for elt in lad_tree.getroot().iter()])
-        matches = []
+        matches = set()
         for elt in all_elts:
             if elt.get("name") is None:
                 continue
             #print elt.get("name"),"hud",elt.get("hud")
-            if args.no_hud and elt.get("hud"):
+            if no_hud and elt.get("hud"):
                 #print "skipping hud joint", elt.get("name")
                 continue
             if elt.get("name") in names or elt.tag in names:
-                matches.append(elt.get("name"))
-        return list(set(matches))
+                matches.add(elt.get("name"))
+        return list(matches)
     else:
         return names
 
-if __name__ == "__main__":
+def main(*argv):
+    import argparse
 
     # default search location for config files is defined relative to
     # the script location; assuming they live in the same viewer repo
+    # Use sys.argv[0] because (a) this script lives where it lives regardless
+    # of what our caller passes and (b) we don't expect our caller to pass the
+    # script name anyway.
     pathname = os.path.dirname(sys.argv[0])        
-    path_to_skel = os.path.join(os.path.abspath(pathname),"..","..","indra","newview","character")
+    # we're in scripts/content_tools; hop back to base of repository clone
+    path_to_skel = os.path.join(os.path.abspath(pathname),os.pardir,os.pardir,
+                                "indra","newview","character")
 
     parser = argparse.ArgumentParser(description="process SL animations")
     parser.add_argument("--verbose", help="verbose flag", action="store_true")
-    parser.add_argument("--dump", help="dump to specified file")
+    parser.add_argument("--dump", metavar="FILEPATH", help="dump to specified file")
     parser.add_argument("--rot", help="specify sequence of rotations", type=float_triple, nargs="+")
-    parser.add_argument("--rand_pos", help="request random positions", action="store_true")
+    parser.add_argument("--rand_pos", help="request NUM random positions (default %(default)s)",
+                        metavar="NUM", type=int, default=2)
     parser.add_argument("--reset_pos", help="request original positions", action="store_true")
     parser.add_argument("--pos", help="specify sequence of positions", type=float_triple, nargs="+")
-    parser.add_argument("--num_pos", help="number of positions to create", type=int, default=2)
-    parser.add_argument("--delete_joints", help="specify joints to be deleted", nargs="+")
-    parser.add_argument("--joints", help="specify joints to be added or modified", nargs="+")
+    parser.add_argument("--delete_joints", help="specify joints to be deleted", nargs="+",
+                        metavar="JOINT")
+    parser.add_argument("--joints", help="specify joints to be added or modified", nargs="+",
+                        metavar="JOINT")
     parser.add_argument("--summary", help="print summary of the output animation", action="store_true")
-    parser.add_argument("--skel", help="name of the avatar_skeleton file", default= os.path.join(path_to_skel,"avatar_skeleton.xml"))
-    parser.add_argument("--lad", help="name of the avatar_lad file", default= os.path.join(path_to_skel,"avatar_lad.xml"))
-    parser.add_argument("--set_version", nargs=2, type=int, help="set version and sub-version to specified values")
+    parser.add_argument("--skel", help="name of the avatar_skeleton file (default %(default)s)",
+                        default=os.path.join(path_to_skel,"avatar_skeleton.xml"),
+                        metavar="FILEPATH")
+    parser.add_argument("--lad", help="name of the avatar_lad file (default %(default)s)",
+                        default=os.path.join(path_to_skel,"avatar_lad.xml"),
+                        metavar="FILEPATH")
+    parser.add_argument("--set_version", nargs=2, type=int,
+                        help="set version and sub-version to specified values",
+                        metavar=("VERSION", "SUB-VERSION"))
     parser.add_argument("--no_hud", help="omit hud joints from list of attachments", action="store_true")
     parser.add_argument("--base_priority", help="set base priority", type=int)
     parser.add_argument("--joint_priority", help="set joint priority for all joints", type=int)
     parser.add_argument("infilename", help="name of a .anim file to input")
     parser.add_argument("outfilename", nargs="?", help="name of a .anim file to output")
-    args = parser.parse_args()
+    args = parser.parse_args(argv)
 
-    print "anim_tool.py: " + " ".join(sys.argv)
+    print "anim_tool.py: " + " ".join(argv)
     print "dump is", args.dump
     print "infilename",args.infilename,"outfilename",args.outfilename
     print "rot",args.rot
     print "pos",args.pos
     print "joints",args.joints
 
-    try:
-        anim = Anim(args.infilename)
-        skel_tree = None
-        lad_tree = None
-        joints = []
-        if args.skel:
-            skel_tree = etree.parse(args.skel)
-            if skel_tree is None:
-                print "failed to parse",args.skel
-                exit(1)
-        if args.lad:
-            lad_tree = etree.parse(args.lad)
-            if lad_tree is None:
-                print "failed to parse",args.lad
-                exit(1)
-        if args.joints:
-            joints = resolve_joints(args.joints, skel_tree, lad_tree)
-            if args.verbose:
-                print "joints resolved to",joints
-            for name in joints:
-                anim.add_joint(name,0)
-        if args.delete_joints:
-            for name in args.delete_joints:
-                anim.delete_joint(name)
-        if joints and args.rot:
-            anim.add_rot(joints, args.rot)
-        if joints and args.pos:
-            anim.add_pos(joints, args.pos)
-        if joints and args.rand_pos:
-            for joint in joints:
-                pos_array = list(tuple(random.uniform(-1,1) for i in xrange(3)) for j in xrange(args.num_pos))
-                pos_array.append(pos_array[0])
-                anim.add_pos([joint], pos_array)
-        if joints and args.reset_pos:
-            for joint in joints:
-                elt = get_joint_by_name(skel_tree,joint)
-                if elt is None:
-                    elt = get_joint_by_name(lad_tree,joint)
-                if elt is not None:
-                    pos_array = []
-                    pos_array.append(get_elt_pos(elt))
-                    pos_array.append(pos_array[0])
-                    anim.add_pos([joint], pos_array)
-                else:
-                    print "no elt or no pos data for",joint
-        if args.set_version:
-            anim.version = args.set_version[0]
-            anim.sub_version = args.set_version[1]
-        if args.base_priority is not None:
-            print "set base priority",args.base_priority
-            anim.base_priority = args.base_priority
-        if args.joint_priority is not None:
-            print "set joint priority",args.joint_priority
-            for joint in anim.joints:
-                joint.joint_priority = args.joint_priority
-        if args.dump:
-            anim.dump(args.dump)
-        if args.summary:
-            anim.summary()
-        if args.outfilename:
-            anim.write(args.outfilename)
-    except:
-        raise
+    anim = Anim(args.infilename, args.verbose)
+    skel_tree = None
+    lad_tree = None
+    joints = []
+    if args.skel:
+        skel_tree = ElementTree.parse(args.skel)
+        if skel_tree is None:
+            raise Error("failed to parse " + args.skel)
+    if args.lad:
+        lad_tree = ElementTree.parse(args.lad)
+        if lad_tree is None:
+            raise Error("failed to parse " + args.lad)
+    if args.joints:
+        joints = resolve_joints(args.joints, skel_tree, lad_tree, args.no_hud)
+        if args.verbose:
+            print "joints resolved to",joints
+        for name in joints:
+            anim.add_joint(name,0)
+    if args.delete_joints:
+        for name in args.delete_joints:
+            anim.delete_joint(name)
+    if joints and args.rot:
+        anim.add_rot(joints, args.rot)
+    if joints and args.pos:
+        anim.add_pos(joints, args.pos)
+    if joints and args.rand_pos:
+        # pick a random sequence of positions for each joint specified
+        for joint in joints:
+            # generate a list of rand_pos triples
+            pos_array = [tuple(random.uniform(-1,1) for i in xrange(3))
+                         for j in xrange(args.rand_pos)]
+            # close the loop by cycling back to the first entry
+            pos_array.append(pos_array[0])
+            anim.add_pos([joint], pos_array)
+    if joints and args.reset_pos:
+        for joint in joints:
+            elt = get_joint_by_name(skel_tree,joint) or get_joint_by_name(lad_tree,joint)
+            if elt is not None:
+                anim.add_pos([joint], 2*[get_elt_pos(elt)])
+            else:
+                print "no elt or no pos data for",joint
+    if args.set_version:
+        anim.version, anim.sub_version = args.set_version
+    if args.base_priority is not None:
+        print "set base priority",args.base_priority
+        anim.base_priority = args.base_priority
+    # --joint_priority sets priority for ALL joints, not just the explicitly-
+    # specified ones
+    if args.joint_priority is not None:
+        print "set joint priority",args.joint_priority
+        for joint in anim.joints:
+            joint.joint_priority = args.joint_priority
+    if args.dump:
+        anim.dump(args.dump)
+    if args.summary:
+        anim.summary()
+    if args.outfilename:
+        anim.write(args.outfilename)
 
+if __name__ == "__main__":
+    try:
+        sys.exit(main(*sys.argv[1:]))
+    except Error as err:
+        sys.exit("%s: %s" % (err.__class__.__name__, err))