From 5106e509cd129b7d31147bce47084f862603fa8c Mon Sep 17 00:00:00 2001
From: Rye Mutt <rye@alchemyviewer.org>
Date: Tue, 5 Jan 2021 03:17:13 -0500
Subject: [PATCH] Port in Catznip's Asset Recovery system to prevent lost
 scripts and notecards on crash

---
 indra/llui/lltexteditor.cpp                   |  60 +++
 indra/llui/lltexteditor.h                     |   6 +
 indra/newview/CMakeLists.txt                  |   2 +
 indra/newview/llappviewer.cpp                 |   7 +
 indra/newview/llfloaterassetrecovery.cpp      | 389 ++++++++++++++++++
 indra/newview/llfloaterassetrecovery.h        |  93 +++++
 indra/newview/llpreview.cpp                   |  97 +++++
 indra/newview/llpreview.h                     |  22 +
 indra/newview/llpreviewnotecard.cpp           |  57 +++
 indra/newview/llpreviewnotecard.h             |   8 +
 indra/newview/llpreviewscript.cpp             | 178 +++++---
 indra/newview/llpreviewscript.h               |   4 +
 indra/newview/llviewerassetupload.cpp         |   4 +
 indra/newview/llviewerassetupload.h           |   9 +
 indra/newview/llviewerfloaterreg.cpp          |   6 +
 indra/newview/llviewerinventory.cpp           |  31 ++
 indra/newview/llviewerinventory.h             |   4 +
 .../default/xui/en/floater_asset_recovery.xml | 111 +++++
 18 files changed, 1037 insertions(+), 51 deletions(-)
 create mode 100644 indra/newview/llfloaterassetrecovery.cpp
 create mode 100644 indra/newview/llfloaterassetrecovery.h
 create mode 100644 indra/newview/skins/default/xui/en/floater_asset_recovery.xml

diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 21d6b84ec70..2764c3561ca 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -2740,6 +2740,66 @@ BOOL LLTextEditor::exportBuffer(std::string &buffer )
 	return TRUE;
 }
 
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+
+// Copy/paste from LLScriptEdCore
+bool LLTextEditor::loadFromFile(const std::string& filename)
+{
+	if (filename.empty())
+	{
+		LL_WARNS() << "Empty file name" << LL_ENDL;
+		return false;
+	}
+
+	LLFILE* file = LLFile::fopen(filename, "rb");		/*Flawfinder: ignore*/
+	if (!file)
+	{
+		LL_WARNS() << "Error opening " << filename << LL_ENDL;
+		return false;
+	}
+
+	// read in the whole file
+	fseek(file, 0L, SEEK_END);
+	size_t file_length = (size_t) ftell(file);
+	fseek(file, 0L, SEEK_SET);
+	char* buffer = new char[file_length+1];
+	size_t nread = fread(buffer, 1, file_length, file);
+	if (nread < file_length)
+	{
+		LL_WARNS() << "Short read" << LL_ENDL;
+	}
+	buffer[nread] = '\0';
+	fclose(file);
+
+	setText(LLStringExplicit(buffer));
+	delete[] buffer;
+
+	return true;
+}
+
+// Copy/paste from LLScriptEdCore
+bool LLTextEditor::writeToFile(const std::string& filename)
+{
+	LLFILE* fp = LLFile::fopen(filename, "wb");
+	if (!fp)
+	{
+		return false;
+	}
+
+	std::string utf8text = getText();
+
+	// Special case for a completely empty script - stuff in one space so it can store properly.  See SL-46889
+	if (utf8text.size() == 0)
+	{
+		utf8text = " ";
+	}
+
+	fputs(utf8text.c_str(), fp);
+	fclose(fp);
+	return true;
+}
+// [/SL:KB]
+
 void LLTextEditor::updateAllowingLanguageInput()
 {
 	LLWindow* window = getWindow();
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index 5a38d55c476..c7c024cdb47 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -198,6 +198,12 @@ class LLTextEditor :
 	virtual BOOL	importBuffer(const char* buffer, S32 length );
 	virtual BOOL	exportBuffer(std::string& buffer );
 
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+	// NOTE-Catznip: doesn't save embedded items, text only 
+	bool			loadFromFile(const std::string& filename);
+	bool			writeToFile(const std::string& filename);
+// [/SL:KB]
+
 	const LLUUID&	getSourceID() const						{ return mSourceID; }
 
 	const LLTextSegmentPtr	getPreviousSegment() const;
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index be5950a5941..3a41831e5ec 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -228,6 +228,7 @@ set(viewer_SOURCE_FILES
     llfirstuse.cpp
     llflexibleobject.cpp
     llfloaterabout.cpp
+    llfloaterassetrecovery.cpp
     llfloaterbvhpreview.cpp
     llfloaterauction.cpp
     llfloaterautoreplacesettings.cpp
@@ -888,6 +889,7 @@ set(viewer_HEADER_FILES
     llfirstuse.h
     llflexibleobject.h
     llfloaterabout.h
+    llfloaterassetrecovery.h
     llfloaterbvhpreview.h
     llfloaterauction.h
     llfloaterautoreplacesettings.h
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index c0ad1b12efc..c536a73e5c1 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -108,6 +108,9 @@
 // [/RLVa:KB]
 
 #include "llweb.h"
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2011-11-24 (Catznip-3.2)
+#include "llfloaterassetrecovery.h"
+// [/SL:KB]
 #include "llfloatertexturefetchdebugger.h"
 #include "llspellcheck.h"
 #include "llscenemonitor.h"
@@ -5627,6 +5630,10 @@ void LLAppViewer::handleLoginComplete()
 
 	mOnLoginCompleted();
 
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2011-11-24 (Catznip-3.2)
+	LLAssetRecoverQueue::recoverIfNeeded();
+// [/SL:KB]
+
 	writeDebugInfo();
 
 	// we logged in successfully, so save settings on logout
diff --git a/indra/newview/llfloaterassetrecovery.cpp b/indra/newview/llfloaterassetrecovery.cpp
new file mode 100644
index 00000000000..9d5ce3c19a6
--- /dev/null
+++ b/indra/newview/llfloaterassetrecovery.cpp
@@ -0,0 +1,389 @@
+/**
+ *
+ * Copyright (c) 2011-2016, Kitty Barnett
+ *
+ * The source code in this file is provided to you under the terms of the
+ * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
+ * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
+ *
+ * By copying, modifying or distributing this software, you acknowledge that
+ * you have read and understood your obligations described above, and agree to
+ * abide by those obligations.
+ *
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llagent.h"
+#include "llcheckboxctrl.h"
+#include "lldiriterator.h"
+#include "llfloaterreg.h"
+#include "llfloaterperms.h"
+#include "llfolderview.h"
+#include "llinventoryfunctions.h"
+#include "llinventorymodel.h"
+#include "llinventorypanel.h"
+#include "llscrolllistctrl.h"
+#include "llviewerassettype.h"
+#include "llviewerinventory.h"
+#include "llviewerregion.h"
+#include "llviewerassetupload.h"
+
+#include "llfloaterassetrecovery.h"
+
+// ============================================================================
+// LLFloaterAssetRecovery
+//
+
+LLFloaterAssetRecovery::LLFloaterAssetRecovery(const LLSD& sdKey)
+	: LLFloater(sdKey)
+{
+}
+
+void LLFloaterAssetRecovery::onOpen(const LLSD& sdKey)
+{
+	LLScrollListCtrl* pListCtrl = findChild<LLScrollListCtrl>("item_list");
+
+	LLSD sdBhvrRow; LLSD& sdBhvrColumns = sdBhvrRow["columns"];
+	sdBhvrColumns[0] = LLSD().with("column", "item_check").with("type", "checkbox");
+	sdBhvrColumns[1] = LLSD().with("column", "item_name").with("type", "text");
+	sdBhvrColumns[2] = LLSD().with("column", "item_type").with("type", "text");
+
+	pListCtrl->clearRows();
+	for (LLSD::array_const_iterator itFile = sdKey["files"].beginArray(), endFile = sdKey["files"].endArray(); 
+			itFile != endFile;  ++itFile)
+	{
+		const LLSD& sdFile = *itFile;
+
+		sdBhvrRow["value"] = sdFile;
+		sdBhvrColumns[0]["value"] = true;
+		sdBhvrColumns[1]["value"] = sdFile["name"];
+		sdBhvrColumns[2]["value"] = sdFile["type"];
+
+		pListCtrl->addElement(sdBhvrRow, ADD_BOTTOM);
+	}
+}
+
+BOOL LLFloaterAssetRecovery::postBuild()
+{
+	findChild<LLUICtrl>("recover_btn")->setCommitCallback(boost::bind(&LLFloaterAssetRecovery::onBtnRecover, this));
+	findChild<LLUICtrl>("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterAssetRecovery::onBtnCancel, this));
+
+	return TRUE;
+}
+
+void LLFloaterAssetRecovery::onBtnCancel()
+{
+	LLScrollListCtrl* pListCtrl = findChild<LLScrollListCtrl>("item_list");
+
+	// Delete all listed files
+	std::vector<LLScrollListItem*> items = pListCtrl->getAllData();
+	for (std::vector<LLScrollListItem*>::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
+	{
+		LLFile::remove((*itItem)->getValue()["path"].asString());
+	}
+
+	closeFloater();
+}
+
+void LLFloaterAssetRecovery::onBtnRecover()
+{
+	LLScrollListCtrl* pListCtrl = findChild<LLScrollListCtrl>("item_list");
+
+	// Recover all selected, delete any unselected
+	std::vector<LLScrollListItem*> items = pListCtrl->getAllData(); LLSD sdFiles;
+	for (std::vector<LLScrollListItem*>::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
+	{
+		LLScrollListCheck* pCheckColumn = dynamic_cast<LLScrollListCheck*>((*itItem)->getColumn(0));
+		if (!pCheckColumn)
+			continue;
+
+		const LLSD sdFile = (*itItem)->getValue();
+		if (pCheckColumn->getCheckBox()->getValue().asBoolean())
+			sdFiles.append(sdFile);
+		else
+			LLFile::remove(sdFile["path"]);
+	}
+
+	if (!sdFiles.emptyArray())
+		new LLAssetRecoverQueue(sdFiles);
+
+	closeFloater();
+}
+
+// ============================================================================
+// LLCreateRecoverAssetCallback
+//
+
+class LLCreateRecoverAssetCallback : public LLInventoryCallback
+{
+public:
+	LLCreateRecoverAssetCallback(LLAssetRecoverQueue* pRecoverQueue)
+		: LLInventoryCallback(), mRecoverQueue(pRecoverQueue)
+	{
+	}
+
+	void fire(const LLUUID& idItem)
+	{
+		mRecoverQueue->onCreateItem(idItem);
+	}
+
+protected:
+	LLAssetRecoverQueue* mRecoverQueue;
+};
+
+// ============================================================================
+// Helper functions
+//
+
+// static
+static bool removeEmbeddedMarkers(const std::string& strFilename)
+{
+	std::ifstream inNotecardFile(strFilename.c_str(), std::ios::in | std::ios::binary);
+	if (!inNotecardFile.is_open())
+		return false;
+
+	std::string strText((std::istreambuf_iterator<char>(inNotecardFile)), std::istreambuf_iterator<char>());
+	inNotecardFile.close();
+
+	std::string::size_type idxText = strText.find((char)'\xF4', 0), lenText = strText.length();
+	while ( (std::string::npos != idxText) && (idxText + 4 <= lenText) )
+	{
+		// In UTF-8 we're looking for F4808080-F48FBFBF
+		char chByte2 = strText[idxText + 1];
+		char chByte3 = strText[idxText + 2];
+		char chByte4 = strText[idxText + 3];
+		if ( ((chByte2 >= '\x80') && (chByte2 <= '\x8F')) &&
+			 ((chByte3 >= '\x80') && (chByte3 <= '\xBF')) &&
+			 ((chByte4 >= '\x80') && (chByte4 <= '\xBF')) )
+		{
+			// We're being lazy and replacing embedded markers with spaces since we don't want to adjust the notecard length field
+			strText.replace(idxText, 4, 4, ' ');
+			continue;
+		}
+		idxText = strText.find('\xF4', idxText + 1);
+	}
+
+	std::ofstream outNotecardFile(strFilename.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
+	if (!outNotecardFile.is_open())
+		return false;
+
+	outNotecardFile.write(strText.c_str(), strText.length());
+	outNotecardFile.close();
+
+	return true;
+}
+
+// ============================================================================
+// LLAssetRecoverQueue
+//
+
+static void findRecoverFiles(LLSD& sdFiles, const std::string& strPath, const std::string& strMask, const std::string& strType)
+{
+	LLDirIterator itFiles(strPath, strMask); std::string strFilename;
+	while (itFiles.next(strFilename))
+	{
+		// Build a friendly name for the file
+		std::string strName = gDirUtilp->getBaseFileName(strFilename, true);
+		std::string::size_type offset = strName.find_last_of("-");
+		if ( (std::string::npos != offset) && (offset != 0) && (offset == strName.length() - 9))
+			strName.erase(strName.length() - 9);
+
+		LLStringUtil::trim(strName);
+		if (0 == strName.length())
+			strName = llformat("(Unknown %s)", strType.c_str());
+
+		sdFiles.append(LLSD().with("path", strPath + strFilename).with("name", strName).with("type", strType));
+	}
+}
+
+// static
+void LLAssetRecoverQueue::recoverIfNeeded()
+{
+	const std::string strTempPath = LLFile::tmpdir();
+	LLSD sdData, &sdFiles = sdData["files"];
+
+	findRecoverFiles(sdFiles, strTempPath, "*.lslbackup", "script");
+	findRecoverFiles(sdFiles, strTempPath, "*.ncbackup", "notecard");
+
+	if (sdFiles.size())
+	{
+		LLFloaterReg::showInstance("asset_recovery", sdData);
+	}
+}
+
+LLAssetRecoverQueue::LLAssetRecoverQueue(const LLSD& sdFiles)
+{
+	for (LLSD::array_const_iterator itFile = sdFiles.beginArray(), endFile = sdFiles.endArray(); itFile != endFile;  ++itFile)
+	{
+		const LLSD& sdFile = *itFile;
+		if (LLFile::isfile(sdFile["path"]))
+		{
+			LLAssetRecoverItem recoveryItem;
+			recoveryItem.strPath = sdFile["path"].asString();
+			recoveryItem.strName = sdFile["name"].asString();
+
+			// Figure out the asset type
+			if ("script" == sdFile["type"].asString())
+				recoveryItem.eAssetType = LLAssetType::AT_LSL_TEXT;
+			else if ("notecard" == sdFile["type"].asString())
+				recoveryItem.eAssetType = LLAssetType::AT_NOTECARD;
+
+			// Generate description
+			LLViewerAssetType::generateDescriptionFor(recoveryItem.eAssetType, recoveryItem.strDescription);
+
+			// Per asset type handling
+			switch (recoveryItem.eAssetType)
+			{
+				case LLAssetType::AT_LSL_TEXT:
+					recoveryItem.eInvType = LLInventoryType::IT_LSL;
+					recoveryItem.nNextOwnerPerm = LLFloaterPerms::getNextOwnerPerms("Scripts");
+					break;
+				case LLAssetType::AT_NOTECARD:
+					recoveryItem.eInvType = LLInventoryType::IT_NOTECARD;
+					recoveryItem.nNextOwnerPerm = LLFloaterPerms::getNextOwnerPerms("Notecards");
+					removeEmbeddedMarkers(recoveryItem.strPath);
+					break;
+			}
+
+			if (recoveryItem.eAssetType != LLAssetType::AT_NONE)
+				m_RecoveryQueue.push_back(recoveryItem);
+		}
+	}
+
+	if (!m_RecoveryQueue.empty())
+		recoverNext();
+	else
+		delete this;
+}
+
+bool LLAssetRecoverQueue::recoverNext()
+{
+	/**
+	 * Steps:
+	 *  (1) create a script/notecard inventory item under "Lost and Found"
+	 *  (2) once we have the item's UUID we can upload it
+	 *  (3) once the asset is uploaded we move on to the next item
+	 */
+	const LLUUID idFNF = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND);
+
+	// If the associated UUID is non-null then this file is already being processed
+	auto itItem = m_RecoveryQueue.cbegin();
+	while ( (m_RecoveryQueue.cend() != itItem) && (itItem->idItem.notNull()) )
+		++itItem;
+
+	// Empty queue - pop-up inventory floater
+	if (m_RecoveryQueue.cend() == itItem)
+	{
+		LLInventoryPanel* pInvPanel = LLInventoryPanel::getActiveInventoryPanel(TRUE);
+		if (pInvPanel)
+		{
+			LLFolderViewFolder* pFVF = dynamic_cast<LLFolderViewFolder*>(pInvPanel->getItemByID(idFNF));
+			if (pFVF)
+			{
+				pFVF->setOpenArrangeRecursively(TRUE, LLFolderViewFolder::RECURSE_UP);
+				pInvPanel->setSelection(idFNF, TRUE);
+			}
+		}
+
+		delete this;
+		return false;
+	}
+
+	// Otherwise start the recovery cycle
+	create_inventory_item(gAgent.getID(), gAgent.getSessionID(), idFNF, LLTransactionID::tnull,
+	                      itItem->strName, itItem->strDescription, itItem->eAssetType, itItem->eInvType,
+	                      NO_INV_SUBTYPE, itItem->nNextOwnerPerm, new LLCreateRecoverAssetCallback(this));
+	return true;
+}
+
+void LLAssetRecoverQueue::onCreateItem(const LLUUID& idItem)
+{
+	const LLViewerInventoryItem* pItem = gInventory.getItem(idItem);
+	if (!pItem)
+	{
+		// CATZ-TODO: error handling (can't call onUploadError or we'll create an endless loop)
+		return;
+	}
+
+	// Viewer will localize 'New Script' so we have to undo that
+	std::string strItemName = pItem->getName();
+	LLViewerInventoryItem::lookupSystemName(strItemName);
+
+	auto itItem = m_RecoveryQueue.begin();
+	while (m_RecoveryQueue.end() != itItem)
+	{
+		if (itItem->strName == strItemName)
+			break;
+		++itItem;
+	}
+
+	if (m_RecoveryQueue.end() != itItem)
+	{
+		itItem->idItem = idItem;
+
+		std::string strCapsUrl, strBuffer;
+
+		std::ifstream inNotecardFile(itItem->strPath.c_str(), std::ios::in | std::ios::binary);
+		if (inNotecardFile.is_open())
+		{
+			strBuffer.assign((std::istreambuf_iterator<char>(inNotecardFile)), std::istreambuf_iterator<char>());
+			inNotecardFile.close();
+		}
+
+		LLResourceUploadInfo::ptr_t uploadInfo;
+		switch (pItem->getType())
+		{
+			case LLAssetType::AT_LSL_TEXT:
+				strCapsUrl = gAgent.getRegion()->getCapability("UpdateScriptAgent");
+				uploadInfo = LLResourceUploadInfo::ptr_t(new LLScriptAssetUpload(idItem, strBuffer, boost::bind(&LLAssetRecoverQueue::onSavedAsset, this, _1, _4)));
+				break;
+			case LLAssetType::AT_NOTECARD:
+				strCapsUrl = gAgent.getRegion()->getCapability("UpdateNotecardAgentInventory");
+				uploadInfo = LLResourceUploadInfo::ptr_t(new LLBufferedAssetUploadInfo(itItem->idItem, LLAssetType::AT_NOTECARD, strBuffer, boost::bind(&LLAssetRecoverQueue::onSavedAsset, this, _1, _4)));
+				break;
+		}
+
+		if ( (!strCapsUrl.empty()) && (uploadInfo) )
+		{
+			uploadInfo->setUploadErrorCb(boost::bind(&LLAssetRecoverQueue::onUploadError, this, _1));
+			LLViewerAssetUpload::EnqueueInventoryUpload(strCapsUrl, uploadInfo);
+			return;
+		}
+	}
+
+	// CATZ-TODO: error handling (if we can't find the current item)
+}
+
+void LLAssetRecoverQueue::onSavedAsset(const LLUUID& idItem, const LLSD& sdResponse)
+{
+	const LLViewerInventoryItem* pItem = gInventory.getItem(idItem);
+	if (pItem)
+	{
+		auto itItem = std::find_if(m_RecoveryQueue.begin(), m_RecoveryQueue.end(), [&idItem](const LLAssetRecoverItem& item)->bool { return item.idItem == idItem; });
+		if (m_RecoveryQueue.end() != itItem)
+		{
+			LLFile::remove(itItem->strPath);
+			m_RecoveryQueue.erase(itItem);
+		}
+	}
+	recoverNext();
+}
+
+void LLAssetRecoverQueue::onUploadError(const LLUUID& idItem)
+{
+	// Skip over the file when there's an error, we can try again on the next relog
+	auto itItem = std::find_if(m_RecoveryQueue.begin(), m_RecoveryQueue.end(), [&idItem](const LLAssetRecoverItem& item)->bool { return item.idItem == idItem; });
+	if (m_RecoveryQueue.end() != itItem)
+	{
+		LLViewerInventoryItem* pItem = gInventory.getItem(itItem->idItem);
+		if (pItem)
+			gInventory.changeItemParent(pItem, gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH), FALSE);
+		m_RecoveryQueue.erase(itItem);
+	}
+	recoverNext();
+}
+
+// ============================================================================
diff --git a/indra/newview/llfloaterassetrecovery.h b/indra/newview/llfloaterassetrecovery.h
new file mode 100644
index 00000000000..b0794eff8e7
--- /dev/null
+++ b/indra/newview/llfloaterassetrecovery.h
@@ -0,0 +1,93 @@
+/**
+ *
+ * Copyright (c) 2011-2016, Kitty Barnett
+ *
+ * The source code in this file is provided to you under the terms of the
+ * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
+ * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
+ *
+ * By copying, modifying or distributing this software, you acknowledge that
+ * you have read and understood your obligations described above, and agree to
+ * abide by those obligations.
+ *
+ */
+
+#ifndef LL_FLOATERSCRIPTRECOVER_H
+#define LL_FLOATERSCRIPTRECOVER_H
+
+#include "llfloater.h"
+
+// ============================================================================
+// LLFloaterAssetRecovery class
+//
+
+class LLFloaterAssetRecovery : public LLFloater
+{
+	friend class LLFloaterReg;
+private:
+	LLFloaterAssetRecovery(const LLSD& sdKey);
+
+	/*
+	 * LLFloater overrides
+	 */
+public:
+	/*virtual*/ void onOpen(const LLSD& sdKey);
+	/*virtual*/ BOOL postBuild();
+
+	/*
+	 * Member functions
+	 */
+protected:
+	void onBtnCancel();
+	void onBtnRecover();
+};
+
+// ============================================================================
+// LLAssetRecoverQueue class
+//
+
+class LLAssetRecoverQueue
+{
+	friend class LLCreateRecoverAssetCallback;
+	friend class LLFloaterAssetRecovery;
+
+	struct LLAssetRecoverItem
+	{
+		std::string strPath;
+		std::string strName;
+		std::string strDescription;
+		LLAssetType::EType eAssetType;
+		LLInventoryType::EType eInvType;
+		U32 nNextOwnerPerm;
+		LLUUID idItem;
+
+		LLAssetRecoverItem() : eAssetType(LLAssetType::AT_NONE), eInvType(LLInventoryType::IT_NONE) {}
+	};
+protected:
+	LLAssetRecoverQueue(const LLSD& sdFiles);
+
+	/*
+	 * Member functions
+	 */
+public:
+	static void recoverIfNeeded();
+protected:
+	bool recoverNext();
+
+	void onCreateItem(const LLUUID& idItem);
+	void onSavedAsset(const LLUUID& idItem, const LLSD& sdResponse);
+	void onUploadError(const LLUUID& idItem);
+
+	/*
+	 * Member variables
+	 */
+protected:
+	typedef std::list<LLAssetRecoverItem> recovery_list_t;
+	recovery_list_t m_RecoveryQueue;
+};
+
+// ============================================================================
+
+#endif // LL_FLOATERSCRIPTRECOVER_H
diff --git a/indra/newview/llpreview.cpp b/indra/newview/llpreview.cpp
index 4b7f90deda7..5cecf435467 100644
--- a/indra/newview/llpreview.cpp
+++ b/indra/newview/llpreview.cpp
@@ -57,10 +57,29 @@
 #include "lltrans.h"
 #include "roles_constants.h"
 
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+#include "lleventtimer.h"
+
+/// ---------------------------------------------------------------------------
+/// Timer helper class
+/// ---------------------------------------------------------------------------
+class LLPreviewBackupTimer : public LLEventTimer
+{
+public:
+	LLPreviewBackupTimer(F32 nPeriod, LLPreview* pPreview) : LLEventTimer(nPeriod), mPreview(pPreview) {}
+	/*virtual*/ BOOL tick() { mPreview->onBackupTimer(); return false; }
+protected:
+	LLPreview* mPreview;
+};
+// [/SL:KB]
+
 // Constants
 
 LLPreview::LLPreview(const LLSD& key)
 :	LLFloater(key),
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	mBackupTimer(NULL),
+// [/SL:KB]
 	mItemUUID(key.has("itemid") ? key.get("itemid").asUUID() : key.asUUID()),
 	mObjectUUID(),			// set later by setObjectID()
 	mCopyToInvBtn( NULL ),
@@ -90,6 +109,15 @@ LLPreview::~LLPreview()
 {
 	gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit()
 	gInventory.removeObserver(this);
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	// Clean up the backup file (unless we've gotten disconnected)
+	if ( (gAgent.getRegion()) && (hasBackupFile()) )
+	{
+		removeBackupFile();
+	}
+	delete mBackupTimer;
+// [/SL:KB]
 }
 
 void LLPreview::setObjectID(const LLUUID& object_id)
@@ -239,6 +267,18 @@ void LLPreview::refreshFromItem()
 	getChild<LLUICtrl>("desc")->setValue(item->getDescription());
 
 	getChildView("desc")->setEnabled(canModify(mObjectUUID, item));
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	if (hasBackupFile())
+	{
+		const std::string strFilename = getBackupFileName();
+		if (strFilename != mBackupFilename)
+		{
+			LLFile::rename(mBackupFilename, strFilename);
+			mBackupFilename = strFilename;
+		}
+	}
+// [/SL:KB]
 }
 
 // static
@@ -471,6 +511,63 @@ void LLPreview::handleReshape(const LLRect& new_rect, bool by_user)
 	LLFloater::handleReshape(new_rect, by_user);
 }
 
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+void LLPreview::startBackupTimer(F32 nInterval)
+{
+	if (nInterval > 0.0f)
+	{
+		if (mBackupTimer)
+			delete mBackupTimer;
+		mBackupTimer = new LLPreviewBackupTimer(nInterval, this);
+	}
+	else
+	{
+		delete mBackupTimer;
+		mBackupTimer = NULL;
+	}
+}
+
+void LLPreview::removeBackupFile()
+{
+	if (hasBackupFile())
+	{
+		LLFile::remove(mBackupFilename);
+		mBackupFilename.clear();
+	}
+}
+
+std::string LLPreview::getBackupFileName() const
+{
+	// NOTE: this function is not guaranteed to return the same filename every time (i.e. the item name may have changed)
+	std::string strFile = LLFile::tmpdir();
+
+	// Find the inventory item for this preview
+	const LLInventoryItem* pItem = getItem();
+	if (pItem)
+	{
+		strFile += gDirUtilp->getScrubbedFileName(pItem->getName().substr(0, 32));
+		strFile += "-";
+	}
+
+	// Append a CRC of the item UUID to make the filename (hopefully) unique
+	LLCRC crcUUID;
+	crcUUID.update((U8*)(&mItemUUID.mData), UUID_BYTES);
+	strFile += llformat("%X", crcUUID.getCRC());
+
+	std::string strTypeExt;
+	if (pItem)
+	{
+		if (LLAssetType::AT_LSL_TEXT == getItem()->getType())
+			strTypeExt = "lsl";
+		else if (LLAssetType::AT_NOTECARD == getItem()->getType())
+			strTypeExt = "nc";
+	}
+	strFile.append(".").append(strTypeExt).append("backup");
+
+	return strFile;
+}
+// [/SL:KB]
+
 //
 // LLMultiPreview
 //
diff --git a/indra/newview/llpreview.h b/indra/newview/llpreview.h
index 2ee76d590ea..5269233a7fa 100644
--- a/indra/newview/llpreview.h
+++ b/indra/newview/llpreview.h
@@ -39,6 +39,9 @@ class LLInventoryItem;
 class LLLineEditor;
 class LLRadioGroup;
 class LLPreview;
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+class LLEventTimer;
+// [/SL:KB]
 
 class LLMultiPreview final : public LLMultiFloater
 {
@@ -110,6 +113,20 @@ class LLPreview : public LLFloater, LLInventoryObserver
 	// or Item  itself is unmodifiable
 	static BOOL canModify(const LLUUID taskUUID, const LLInventoryItem* item);
 
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2012-02-06 (Catznip-3.2)
+	// Backup functionality
+public:
+	void startBackupTimer(F32 nInterval);
+	bool hasBackupFile() const   { return (!mBackupFilename.empty()); }
+	bool isBackupRunning() const { return (NULL != mBackupTimer); }
+	void removeBackupFile();
+protected:
+	virtual std::string getBackupFileName() const;
+	virtual void        onBackupTimer() {}
+
+	friend class LLPreviewBackupTimer;
+// [/SL:KB]
+	
 protected:
 	virtual void onCommit();
 
@@ -152,6 +169,11 @@ class LLPreview : public LLFloater, LLInventoryObserver
 	// I am unsure if this is always the same as mObjectUUID, or why it exists
 	// at the LLPreview level.  JC 2009-06-24
 	LLUUID mNotecardObjectID;
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	std::string   mBackupFilename;
+	LLEventTimer* mBackupTimer;
+// [/SL:KB]
 };
 
 
diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp
index e1a8da55a6a..9c0921deb32 100644
--- a/indra/newview/llpreviewnotecard.cpp
+++ b/indra/newview/llpreviewnotecard.cpp
@@ -42,6 +42,9 @@
 #include "llinventorydefines.h"
 #include "llinventorymodel.h"
 #include "lllineeditor.h"
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+#include "llnotecard.h"
+// [/SL:KB]
 #include "llnotificationsutil.h"
 #include "llresmgr.h"
 #include "roles_constants.h"
@@ -268,6 +271,14 @@ void LLPreviewNotecard::loadAsset()
 				editor->makePristine();
 				editor->setEnabled(TRUE);
 				mAssetStatus = PREVIEW_ASSET_LOADED;
+
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+				// Start the timer which will perform regular backup saves
+				if (!isBackupRunning())
+				{
+					startBackupTimer(60.0f);
+				}
+// [/SL:KB]
 			}
 			else
 			{
@@ -409,6 +420,14 @@ void LLPreviewNotecard::onLoadComplete(LLVFS *vfs,
 			LL_WARNS() << "Problem loading notecard: " << status << LL_ENDL;
 			preview->mAssetStatus = PREVIEW_ASSET_ERROR;
 		}
+
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+		// Start the timer which will perform regular backup saves
+		if (!preview->isBackupRunning())
+		{
+			preview->startBackupTimer(60.0f);
+		}
+// [/SL:KB]
 	}
 	delete floater_key;
 }
@@ -624,6 +643,44 @@ void LLPreviewNotecard::deleteNotecard()
 	LLNotificationsUtil::add("DeleteNotecard", LLSD(), LLSD(), boost::bind(&LLPreviewNotecard::handleConfirmDeleteDialog,this, _1, _2));
 }
 
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+void LLPreviewNotecard::onBackupTimer()
+{
+	LLViewerTextEditor* pEditor = findChild<LLViewerTextEditor>("Notecard Editor");
+	if ( (pEditor) && (!pEditor->isPristine()) )
+	{
+		if (mBackupFilename.empty())
+			mBackupFilename = getBackupFileName();
+
+		if (!mBackupFilename.empty())
+		{
+			LLNotecard notecard(LLNotecard::MAX_SIZE);
+			notecard.setText(pEditor->getText());
+
+			std::stringstream strmNotecard;
+			notecard.exportStream(strmNotecard);
+
+			std::ofstream outNotecardFile(mBackupFilename.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
+			if (outNotecardFile.is_open())
+			{
+				outNotecardFile << strmNotecard.rdbuf();
+				outNotecardFile.close();
+			}
+		}
+	}
+}
+
+void LLPreviewNotecard::callbackSaveComplete()
+{
+	// Notecard was successfully saved so delete our backup copy if we have one and the editor is still pristine
+	LLViewerTextEditor* pEditor = findChild<LLViewerTextEditor>("Notecard Editor");
+	if ( (pEditor) && (pEditor->isPristine()) && (hasBackupFile()) )
+	{
+		removeBackupFile();
+	}
+}
+// [/SL:KB]
+
 // static
 void LLPreviewNotecard::onSaveComplete(const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed)
 {
diff --git a/indra/newview/llpreviewnotecard.h b/indra/newview/llpreviewnotecard.h
index fa2bd966543..47b72346124 100644
--- a/indra/newview/llpreviewnotecard.h
+++ b/indra/newview/llpreviewnotecard.h
@@ -83,6 +83,10 @@ class LLPreviewNotecard final : public LLPreview
 
 	void syncExternal();
 
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+	void callbackSaveComplete();
+// [/SL:KB]
+
 protected:
 
 	void updateTitleButtons() override;
@@ -91,6 +95,10 @@ class LLPreviewNotecard final : public LLPreview
 
 	void deleteNotecard();
 
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+	/*virtual*/ void onBackupTimer();
+// [/SL:KB]
+
 	static void onLoadComplete(LLVFS *vfs,
 							   const LLUUID& asset_uuid,
 							   LLAssetType::EType type,
diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp
index 31d55de51b2..5fa7f408ce4 100644
--- a/indra/newview/llpreviewscript.cpp
+++ b/indra/newview/llpreviewscript.cpp
@@ -601,49 +601,45 @@ void LLScriptEdCore::makeEditorPristine()
 
 bool LLScriptEdCore::loadScriptText(const std::string& filename)
 {
-	if (filename.empty())
-	{
-		LL_WARNS() << "Empty file name" << LL_ENDL;
-		return false;
-	}
-
-	LLFILE* file = LLFile::fopen(filename, "rb");		/*Flawfinder: ignore*/
-	if (!file)
-	{
-		LL_WARNS() << "Error opening " << filename << LL_ENDL;
-		return false;
-	}
-
-	// read in the whole file
-	fseek(file, 0L, SEEK_END);
-	size_t file_length = (size_t) ftell(file);
-	fseek(file, 0L, SEEK_SET);
-    if (file_length > 0)
-    {
-		auto buffer = std::make_unique<char[]>(file_length + 1);
-        size_t nread = fread(buffer.get(), 1, file_length, file);
-        if (nread < file_length)
-        {
-            LL_WARNS() << "Short read" << LL_ENDL;
-        }
-        buffer[nread] = '\0';
-        fclose(file);
-        
-        mEditor->setText(LLStringExplicit(buffer.get()));
-    }
-    else
-    {
-        LL_WARNS() << "Error getting file size " << filename << LL_ENDL;
-		fclose(file);
-        return false;
-    }
-	return true;
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+	return mEditor->loadFromFile(filename);
+// [/SL:KB]
+//	if (filename.empty())
+//	{
+//		LL_WARNS() << "Empty file name" << LL_ENDL;
+//		return false;
+//	}
+//
+//	LLFILE* file = LLFile::fopen(filename, "rb");		/*Flawfinder: ignore*/
+//	if (!file)
+//	{
+//		LL_WARNS() << "Error opening " << filename << LL_ENDL;
+//		return false;
+//	}
+//
+//	// read in the whole file
+//	fseek(file, 0L, SEEK_END);
+//	size_t file_length = (size_t) ftell(file);
+//	fseek(file, 0L, SEEK_SET);
+//	char* buffer = new char[file_length+1];
+//	size_t nread = fread(buffer, 1, file_length, file);
+//	if (nread < file_length)
+//	{
+//		LL_WARNS() << "Short read" << LL_ENDL;
+//	}
+//	buffer[nread] = '\0';
+//	fclose(file);
+//
+//	mEditor->setText(LLStringExplicit(buffer));
+//	delete[] buffer;
+//
+//	return true;
 }
 
 bool LLScriptEdCore::writeToFile(const std::string& filename)
 {
-	LLFILE* fp = LLFile::fopen(filename, "wb");
-	if (!fp)
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6)
+	if (!mEditor->writeToFile(filename))
 	{
 		LL_WARNS() << "Unable to write to " << filename << LL_ENDL;
 
@@ -653,18 +649,31 @@ bool LLScriptEdCore::writeToFile(const std::string& filename)
 		mErrorList->addElement(row);
 		return false;
 	}
-
-	std::string utf8text = mEditor->getText();
-
-	// Special case for a completely empty script - stuff in one space so it can store properly.  See SL-46889
-	if (utf8text.size() == 0)
-	{
-		utf8text = " ";
-	}
-
-	fputs(utf8text.c_str(), fp);
-	fclose(fp);
 	return true;
+// [/SL:KB]
+//	LLFILE* fp = LLFile::fopen(filename, "wb");
+//	if (!fp)
+//	{
+//		LL_WARNS() << "Unable to write to " << filename << LL_ENDL;
+//
+//		LLSD row;
+//		row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?";
+//		row["columns"][0]["font"] = "SANSSERIF_SMALL";
+//		mErrorList->addElement(row);
+//		return false;
+//	}
+//
+//	std::string utf8text = mEditor->getText();
+//
+//	// Special case for a completely empty script - stuff in one space so it can store properly.  See SL-46889
+//	if (utf8text.size() == 0)
+//	{
+//		utf8text = " ";
+//	}
+//
+//	fputs(utf8text.c_str(), fp);
+//	fclose(fp);
+//	return true;
 }
 
 void LLScriptEdCore::sync()
@@ -1464,6 +1473,18 @@ LLScriptEdContainer::LLScriptEdContainer(const LLSD& key) :
 {
 }
 
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+void LLScriptEdContainer::onBackupTimer()
+{
+	if ( (mScriptEd) && (mScriptEd->hasChanged()) )
+	{
+		if (mBackupFilename.empty())
+			mBackupFilename = getBackupFileName();
+		mScriptEd->writeToFile(mBackupFilename);
+	}
+}
+// [/SL:KB]
+
 std::string LLScriptEdContainer::getTmpFileName()
 {
 	// Take script inventory item id (within the object inventory)
@@ -1567,6 +1588,15 @@ void LLPreviewLSL::callbackLSLCompileSucceeded()
 	LL_INFOS() << "LSL Bytecode saved" << LL_ENDL;
 	mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful"));
 	mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete"));
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	// Script was successfully saved so delete our backup copy if we have one and the editor is still pristine
+	if ( (!mScriptEd->hasChanged()) && (hasBackupFile()) )
+	{
+		removeBackupFile();
+	}
+// [/SL:KB]
+
 	closeIfNeeded();
 }
 
@@ -1587,6 +1617,15 @@ void LLPreviewLSL::callbackLSLCompileFailed(const LLSD& compile_errors)
 		mScriptEd->mErrorList->addElement(row);
 	}
 	mScriptEd->selectFirstError();
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	// Script was successfully saved so delete our backup copy if we have one and the editor is still pristine
+	if ( (!mScriptEd->hasChanged()) && (hasBackupFile()) )
+	{
+		removeBackupFile();
+	}
+// [/SL:KB]
+
 	closeIfNeeded();
 }
 
@@ -1710,7 +1749,10 @@ void LLPreviewLSL::finishedLSLUpload(LLUUID itemId, LLSD response)
 // fails, go ahead and save the text anyway.
 void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/)
 {
-    if (!mScriptEd->hasChanged())
+//	if(!mScriptEd->hasChanged())
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2012-02-10 (Catznip-3.2)
+	if ( (!mScriptEd->hasChanged()) || (!gAgent.getRegion()) )
+// [/SL:KB]
     {
         return;
     }
@@ -1786,6 +1828,14 @@ void LLPreviewLSL::onLoadComplete( LLVFS *vfs, const LLUUID& asset_uuid, LLAsset
 			preview->mScriptEd->setScriptName(script_name);
 			preview->mScriptEd->setEnableEditing(is_modifiable);
 			preview->mAssetStatus = PREVIEW_ASSET_LOADED;
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+			// Start the timer which will perform regular backup saves
+			if (!preview->isBackupRunning())
+			{
+				preview->startBackupTimer(60.0f);
+			}
+// [/SL:KB]
 		}
 		else
 		{
@@ -1886,6 +1936,15 @@ void LLLiveLSLEditor::callbackLSLCompileSucceeded(const LLUUID& task_id,
 	mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete"));
 	getChild<LLCheckBoxCtrl>("running")->set(is_script_running);
 	mIsSaving = FALSE;
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	// Script was successfully saved so delete our backup copy if we have one and the editor is still pristine
+	if ( (!mScriptEd->hasChanged()) && (hasBackupFile()) )
+	{
+		removeBackupFile();
+	}
+// [/SL:KB]
+
 	closeIfNeeded();
 }
 
@@ -1907,6 +1966,15 @@ void LLLiveLSLEditor::callbackLSLCompileFailed(const LLSD& compile_errors)
 	}
 	mScriptEd->selectFirstError();
 	mIsSaving = FALSE;
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	// Script was successfully saved so delete our backup copy if we have one and the editor is still pristine
+	if ( (!mScriptEd->hasChanged()) && (hasBackupFile()) )
+	{
+		removeBackupFile();
+	}
+// [/SL:KB]
+
 	closeIfNeeded();
 }
 
@@ -2038,6 +2106,14 @@ void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id,
 			instance->loadScriptText(vfs, asset_id, type);
 			instance->mScriptEd->setEnableEditing(TRUE);
 			instance->mAssetStatus = PREVIEW_ASSET_LOADED;
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+			// Start the timer which will perform regular backup saves
+			if (!instance->isBackupRunning())
+			{
+				instance->startBackupTimer(60.0f);
+			}
+// [/SL:KB]
 		}
 		else
 		{
diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h
index 437e7a04cce..d9a44af685d 100644
--- a/indra/newview/llpreviewscript.h
+++ b/indra/newview/llpreviewscript.h
@@ -210,6 +210,10 @@ class LLScriptEdContainer : public LLPreview
 
 protected:
 	std::string		getTmpFileName();
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2)
+	/*virtual*/ void onBackupTimer();
+// [/SL:KB]
+
 	bool			onExternalChange(const std::string& filename);
 	virtual void	saveIfNeeded(bool sync = true) = 0;
 
diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp
index d53cc3f7457..22718f2d635 100644
--- a/indra/newview/llviewerassetupload.cpp
+++ b/indra/newview/llviewerassetupload.cpp
@@ -882,6 +882,10 @@ void LLViewerAssetUpload::HandleUploadError(LLCore::HttpStatus status, LLSD &res
         }
     }
 
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: Catznip-4.0
+	uploadInfo->callUploadErrorCb();
+// [/SL:KB]
+
     // Let the Snapshot floater know we have failed uploading.
     LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance();
     if (floater_snapshot && floater_snapshot->isWaitingState())
diff --git a/indra/newview/llviewerassetupload.h b/indra/newview/llviewerassetupload.h
index d9eacf31675..3887be1a917 100644
--- a/indra/newview/llviewerassetupload.h
+++ b/indra/newview/llviewerassetupload.h
@@ -90,6 +90,12 @@ class LLResourceUploadInfo
 	static bool			findAssetTypeOfExtension(const std::string& exten, LLAssetType::EType& asset_type);
 	static bool			findAssetTypeAndCodecOfExtension(const std::string& exten, LLAssetType::EType& asset_type, U32& codec, bool bulk_upload = true);
 
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: Catznip-4.0
+    typedef boost::function<void(LLUUID itemId)> upload_error_f;
+	// Should add this as a parameter to the constructor but this requires less code changes
+	void callUploadErrorCb() { if (mUploadErrorFn) { mUploadErrorFn(mItemId); } }
+	void setUploadErrorCb(upload_error_f fnUploadError) { mUploadErrorFn = fnUploadError; }
+// [/SL:KLB]
 protected:
     LLResourceUploadInfo(
         std::string name,
@@ -119,6 +125,9 @@ class LLResourceUploadInfo
     void                setAssetId(LLUUID assetId) { mAssetId = assetId; }
 
 private:
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: Catznip-4.0
+	upload_error_f      mUploadErrorFn;
+// [/SL:KLB]
     LLTransactionID     mTransactionId;
     LLAssetType::EType  mAssetType;
     std::string         mName;
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 220125a464e..289b1d71cd9 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -38,6 +38,9 @@
 #include "llfasttimerview.h"
 #include "llfloaterabout.h"
 #include "llfloaterauction.h"
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2011-11-24 (Catznip-3.2)
+#include "llfloaterassetrecovery.h"
+// [/SL:KB]
 #include "llfloaterautoreplacesettings.h"
 #include "llfloateravatar.h"
 #include "llfloateravatarpicker.h"
@@ -204,6 +207,9 @@ void LLViewerFloaterReg::registerFloaters()
 	LLFloaterReg::add("block_timers", "floater_fast_timers.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFastTimerView>);
 	LLFloaterReg::add("about_land", "floater_about_land.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLand>);
 	LLFloaterReg::add("appearance", "floater_my_appearance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>);
+// [SL:KB] - Patch: Build-AssetRecovery | Checked: 2011-11-24 (Catznip-3.2)
+	LLFloaterReg::add("asset_recovery", "floater_asset_recovery.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAssetRecovery>);
+// [/SL:KB]
 	LLFloaterReg::add("associate_listing", "floater_associate_listing.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAssociateListing>);
 	LLFloaterReg::add("auction", "floater_auction.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAuction>);
 	LLFloaterReg::add("avatar", "floater_avatar.xml",  (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAvatar>);
diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp
index 4bbcc967417..a4139a4350e 100644
--- a/indra/newview/llviewerinventory.cpp
+++ b/indra/newview/llviewerinventory.cpp
@@ -129,6 +129,25 @@ class LLLocalizedInventoryItemsDictionary final : public LLSingleton<LLLocalized
 		}
 		return found;
 	}
+
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2013-03-10 (Catznip-3.4)
+	bool revertInventoryObjectName(std::string& object_name)
+	{
+		LL_DEBUGS("InventoryLocalize") << "Searching for localization: " << object_name << LL_ENDL;
+
+		for (auto itDict = mInventoryItemsDict.cbegin(); itDict != mInventoryItemsDict.cend(); ++itDict)
+		{
+			if (itDict->second == object_name)
+			{
+				object_name = itDict->first;
+				LL_DEBUGS("InventoryLocalize") << "Found, new name is: " << object_name << LL_ENDL;
+				return true;
+			}
+		}
+		return false;
+	}
+// [/SL:KB]
+
 };
 
 LLLocalizedInventoryItemsDictionary::LLLocalizedInventoryItemsDictionary()
@@ -2061,6 +2080,18 @@ BOOL LLViewerInventoryItem::extractSortFieldAndDisplayName(const std::string& na
 	return result;
 }
 
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2013-03-10 (Catznip-3.4)
+bool LLViewerInventoryItem::lookupLocalizedName(std::string& name)
+{
+	return LLLocalizedInventoryItemsDictionary::instance().localizeInventoryObjectName(name);
+}
+
+bool LLViewerInventoryItem::lookupSystemName(std::string& name)
+{
+	return LLLocalizedInventoryItemsDictionary::instance().revertInventoryObjectName(name);
+}
+// [/SL:KB]
+
 // This returns true if the item that this item points to 
 // doesn't exist in memory (i.e. LLInventoryModel).  The baseitem
 // might still be in the database but just not loaded yet.
diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h
index 78f04f8bafd..322df060250 100644
--- a/indra/newview/llviewerinventory.h
+++ b/indra/newview/llviewerinventory.h
@@ -83,6 +83,10 @@ class LLViewerInventoryItem : public LLInventoryItem, public boost::signals2::tr
 	virtual U32 getCRC32() const; // really more of a checksum.
 
 	static BOOL extractSortFieldAndDisplayName(const std::string& name, S32* sortField, std::string* displayName);
+// [SL:KB] - Patch: Build-ScriptRecover | Checked: 2013-03-10 (Catznip-3.4)
+	static bool lookupLocalizedName(std::string& name);
+	static bool lookupSystemName(std::string& name);
+// [/SL:KB]
 
 	// construct a complete viewer inventory item
 	LLViewerInventoryItem(const LLUUID& uuid, const LLUUID& parent_uuid,
diff --git a/indra/newview/skins/default/xui/en/floater_asset_recovery.xml b/indra/newview/skins/default/xui/en/floater_asset_recovery.xml
new file mode 100644
index 00000000000..45ed375a223
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_asset_recovery.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ can_close="false"
+ can_minimize="false"
+ can_resize="false"
+ height="266"
+ positioning="centered"
+ name="asset_recovery"
+ single_instance="true"
+ title="ASSET RECOVERY"
+ width="350">
+
+  <text
+   follows="top|left|right"
+   font.style="BOLD"
+   height="75"
+   layout="topleft"
+   left="10"
+   length="1"
+   name="txt2"
+   top="0"
+   type="string"
+   word_wrap="true"
+   width="330">
+      [APP_NAME] has detected recoverable items that may have been left over from a recent viewer crash.
+  
+Recovered items will be placed in the 'Lost and Found' inventory folder.
+  </text>
+
+  <scroll_list
+   draw_border="false"
+   draw_heading="true"
+   draw_stripes="true"
+   follows="all"
+   height="150"
+   layout="topleft"
+   left="8"
+   multi_select="false"
+   name="item_list"
+   top_pad="5"
+   width="334">
+    <scroll_list.columns
+     label=""
+     name="item_check"
+     width="20" />
+    <scroll_list.columns
+     label="Item Name"
+     name="item_name"
+     width="170" />
+    <scroll_list.columns
+     label="Item Type"
+     name="item_type"
+     width="70" />
+  </scroll_list>
+  <panel
+   background_visible="false"
+   follows="left|right|bottom"
+    height="25"
+   label="bottom_panel"
+   layout="topleft"
+   left="6"
+   name="bottom_panel"
+   top_pad="4" >
+    <icon
+     follows="left|right"
+     height="25"
+     image_name="Toolbar_Left_Off"
+     left="0"
+     name="dummy_icon1"
+     top="1"
+     width="30" />
+    <button
+     height="25"
+     image_color="Green"
+     image_hover_unselected="Toolbar_Middle_Over"
+     image_selected="Toolbar_Middle_Selected"
+     image_unselected="Toolbar_Middle_Off"
+     label="Recover"
+     label_color="White"
+     left_pad="1"
+     name="recover_btn"
+     top="1"
+     width="120" />
+    <icon
+     follows="left|right"
+     height="25"
+     image_name="Toolbar_Middle_Off"
+     left_pad="1"
+     name="dummy_icon2"
+     top="1"
+     width="34" />    
+    <button
+     height="25"
+     image_hover_unselected="Toolbar_Middle_Over"
+     image_selected="Toolbar_Middle_Selected"
+     image_unselected="Toolbar_Middle_Off"
+     label="Cancel"
+     left_pad="1"
+     name="cancel_btn"
+     top="1"
+     width="120" />
+    <icon
+     height="25"
+     follows="left|right"
+     image_name="Toolbar_Right_Off"
+     left_pad="1"
+     name="dummy_icon3"
+     top="1"
+     width="30" />          
+  </panel> 
+</floater>
\ No newline at end of file
-- 
GitLab