/** * * Copyright (c) 2009-2010, Kitty Barnett * * The source code in this file is provided to you under the terms of the * GNU General Public License, version 2.0, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. Terms of the GPL can be found in doc/GPL-license.txt * in this distribution, or online at http://www.gnu.org/licenses/gpl-2.0.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 "llfloaterbeacons.h" #include "llfloaterchat.h" #include "llfloaterdaycycle.h" #include "llfloaterenvsettings.h" #include "llfloaterland.h" #include "llfloatermap.h" #include "llfloaterregioninfo.h" #include "llfloatertools.h" #include "llfloaterwater.h" #include "llfloaterwindlight.h" #include "llfloaterworldmap.h" #include "llinventoryview.h" #include "llviewermenu.h" #include "llviewermessage.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llviewerwindow.h" #include "rlvhandler.h" #include "rlvinventory.h" #include "rlvextensions.h" // ============================================================================ // Static variable initialization // BOOL RlvHandler::m_fEnabled = FALSE; rlv_handler_t gRlvHandler; // ============================================================================ // Command specific helper functions // // Checked: 2009-08-04 (RLVa-1.0.1d) | Added: RLVa-1.0.1d static bool rlvParseNotifyOption(const std::string& strOption, S32& nChannel, std::string& strFilter) { boost_tokenizer tokens(strOption, boost::char_separator(";", "", boost::keep_empty_tokens)); boost_tokenizer::const_iterator itTok = tokens.begin(); // Extract and sanity check the first token (required) which is the channel if ( (itTok == tokens.end()) || (!LLStringUtil::convertToS32(*itTok, nChannel)) || (!RlvUtil::isValidReplyChannel(nChannel)) ) return false; // Second token (optional) is the filter strFilter.clear(); if (++itTok != tokens.end()) { strFilter = *itTok; ++itTok; } return (itTok == tokens.end()); } // ============================================================================ // Constructor/destructor // // Checked: 2010-04-07 (RLVa-1.2.0d) | Modified: RLVa-1.0.1d RlvHandler::RlvHandler() : m_fCanCancelTp(true), m_posSitSource(), m_pGCTimer(NULL), m_pWLSnapshot(NULL) { // Array auto-initialization to 0 is non-standard? (Compiler warning in VC-8.0) memset(m_Behaviours, 0, sizeof(S16) * RLV_BHVR_COUNT); } // Checked: 2010-04-07 (RLVa-1.2.0d) | Modified: RLVa-1.0.1d RlvHandler::~RlvHandler() { //delete m_pGCTimer; // <- deletes itself delete m_pWLSnapshot; // <- delete on NULL is harmless } // ============================================================================ // Behaviour related functions // bool RlvHandler::hasBehaviourExcept(ERlvBehaviour eBehaviour, const std::string& strOption, const LLUUID& idObj) const { for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj) if ( (idObj != itObj->second.m_UUID) && (itObj->second.hasBehaviour(eBehaviour, strOption, false)) ) return true; return false; } // Checked: 2009-10-04 (RLVa-1.0.4c) | Modified: RLVa-1.0.4c bool RlvHandler::isException(ERlvBehaviour eBhvr, const RlvExceptionOption& varOption, ERlvExceptionCheck typeCheck) const { // We need to "strict check" exceptions only if: the restriction is actually in place *and* (isPermissive(eBhvr) == FALSE) if (RLV_CHECK_DEFAULT == typeCheck) typeCheck = ( (hasBehaviour(eBhvr)) && (!isPermissive(eBhvr)) ) ? RLV_CHECK_STRICT : RLV_CHECK_PERMISSIVE; std::list objList; if (RLV_CHECK_STRICT == typeCheck) { // If we're "strict checking" then we need the UUID of every object that currently has 'eBhvr' restricted for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj) if (itObj->second.hasBehaviour(eBhvr, !hasBehaviour(RLV_BHVR_PERMISSIVE))) objList.push_back(itObj->first); } for (rlv_exception_map_t::const_iterator itException = m_Exceptions.lower_bound(eBhvr), endException = m_Exceptions.upper_bound(eBhvr); itException != endException; ++itException) { if (itException->second.varOption == varOption) { // For permissive checks we just return on the very first match if (RLV_CHECK_PERMISSIVE == typeCheck) return true; // For strict checks we don't return until the list is empty (every object with 'eBhvr' restricted also contains the exception) std::list::iterator itList = std::find(objList.begin(), objList.end(), itException->second.idObject); if (itList != objList.end()) objList.erase(itList); if (objList.empty()) return true; } } return false; } // ============================================================================ // Command processing functions // // Checked: 2009-11-27 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::addBehaviourObserver(RlvBehaviourObserver* pBhvrObserver) { if ( (pBhvrObserver) && (std::find(m_BhvrObservers.begin(), m_BhvrObservers.end(), pBhvrObserver) == m_BhvrObservers.end()) ) m_BhvrObservers.push_back(pBhvrObserver); } // Checked: 2009-11-27 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::addCommandHandler(RlvCommandHandler* pCmdHandler) { if ( (pCmdHandler) && (std::find(m_CommandHandlers.begin(), m_CommandHandlers.end(), pCmdHandler) == m_CommandHandlers.end()) ) m_CommandHandlers.push_back(pCmdHandler); } // Checked: 2009-11-27 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::removeBehaviourObserver(RlvBehaviourObserver* pBhvrObserver) { if (pBhvrObserver) m_BhvrObservers.remove(pBhvrObserver); } // Checked: 2009-11-27 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::removeCommandHandler(RlvCommandHandler* pCmdHandler) { if (pCmdHandler) m_CommandHandlers.remove(pCmdHandler); } // Checked: 2009-10-26 (RLVa-1.1.0a) | Modified: RLVa-1.1.0a void RlvHandler::clearCommandHandlers() { std::list::const_iterator itHandler = m_CommandHandlers.begin(); while (itHandler != m_CommandHandlers.end()) { delete *itHandler; ++itHandler; } m_CommandHandlers.clear(); } // Checked: 2009-06-03 (RLVa-0.2.0h) void RlvHandler::notifyBehaviourObservers(const RlvCommand& rlvCmd, bool fInternal) { for (std::list::const_iterator itBhvrObserver = m_BhvrObservers.begin(); itBhvrObserver != m_BhvrObservers.end(); ++itBhvrObserver) { (*itBhvrObserver)->changed(rlvCmd, fInternal); } } // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f bool RlvHandler::notifyCommandHandlers(rlvCommandHandler f, const RlvCommand& rlvCmd, ERlvCmdRet& eRet, bool fNotifyAll) const { std::list::const_iterator itHandler = m_CommandHandlers.begin(); bool fContinue = true; eRet = RLV_RET_UNKNOWN; while ( (itHandler != m_CommandHandlers.end()) && ((fContinue) || (fNotifyAll)) ) { ERlvCmdRet eCmdRet = RLV_RET_UNKNOWN; if ((fContinue = !((*itHandler)->*f)(rlvCmd, eCmdRet)) == false) eRet = eCmdRet; ++itHandler; } RLV_ASSERT( (fContinue) || (eRet != RLV_RET_UNKNOWN) ); return !fContinue; } // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f ERlvCmdRet RlvHandler::processCommand(const RlvCommand& rlvCmd, bool fFromObj) { #ifdef RLV_DEBUG RLV_INFOS << "[" << rlvCmd.getObjectID() << "]: " << rlvCmd.asString() << RLV_ENDL; #endif // RLV_DEBUG if (!rlvCmd.isValid()) { #ifdef RLV_DEBUG RLV_INFOS << "\t-> invalid syntax" << RLV_ENDL; #endif // RLV_DEBUG return RLV_RET_FAILED_SYNTAX; } // Using a stack for executing commands solves a few problems: // - if we passed RlvObject::m_UUID for idObj somewhere and process a @clear then idObj points to invalid/cleared memory at the end // - if command X triggers command Y along the way then getCurrentCommand()/getCurrentObject() still return Y even when finished m_CurCommandStack.push(&rlvCmd); m_CurObjectStack.push(rlvCmd.getObjectID()); const LLUUID& idCurObj = m_CurObjectStack.top(); ERlvCmdRet eRet = RLV_RET_UNKNOWN; switch (rlvCmd.getParamType()) { case RLV_TYPE_ADD: // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f { if ( (m_Behaviours[rlvCmd.getBehaviourType()]) && ( (RLV_BHVR_SETDEBUG == rlvCmd.getBehaviourType()) || (RLV_BHVR_SETENV == rlvCmd.getBehaviourType()) ) ) { // Some restrictions can only be held by one single object to avoid deadlocks #ifdef RLV_DEBUG RLV_INFOS << "\t- " << rlvCmd.getBehaviour() << " is already set by another object => discarding" << RLV_ENDL; #endif // RLV_DEBUG eRet = RLV_RET_FAILED_LOCK; break; } rlv_object_map_t::iterator itObj = m_Objects.find(idCurObj); bool fAdded = false; if (itObj != m_Objects.end()) { RlvObject& rlvObj = itObj->second; fAdded = rlvObj.addCommand(rlvCmd); } else { RlvObject rlvObj(idCurObj); fAdded = rlvObj.addCommand(rlvCmd); m_Objects.insert(std::pair(idCurObj, rlvObj)); } #ifdef RLV_DEBUG RLV_INFOS << "\t- " << ( (fAdded) ? "adding behaviour" : "skipping duplicate" ) << RLV_ENDL; #endif // RLV_DEBUG if (fAdded) { // If FALSE then this was a duplicate, there's no need to handle those if (!m_pGCTimer) m_pGCTimer = new RlvGCTimer(); eRet = processAddRemCommand(rlvCmd); notifyBehaviourObservers(rlvCmd, !fFromObj); } else { eRet = RLV_RET_SUCCESS_DUPLICATE; } } break; case RLV_TYPE_REMOVE: // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f { rlv_object_map_t::iterator itObj = m_Objects.find(idCurObj); bool fRemoved = false; if (itObj != m_Objects.end()) fRemoved = itObj->second.removeCommand(rlvCmd); #ifdef RLV_DEBUG RLV_INFOS << "\t- " << ( (fRemoved) ? "removing behaviour" : "skipping remove (unset behaviour or unknown object)") << RLV_ENDL; #endif // RLV_DEBUG if (fRemoved) { // Don't handle non-sensical removes eRet = processAddRemCommand(rlvCmd); notifyBehaviourObservers(rlvCmd, !fFromObj); if (0 == itObj->second.m_Commands.size()) { #ifdef RLV_DEBUG RLV_INFOS << "\t- command list empty => removing " << idCurObj << RLV_ENDL; #endif // RLV_DEBUG m_Objects.erase(itObj); } } else { eRet = RLV_RET_SUCCESS_UNSET; } } break; case RLV_TYPE_CLEAR: // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f eRet = processClearCommand(rlvCmd); notifyBehaviourObservers(rlvCmd, !fFromObj); break; case RLV_TYPE_FORCE: // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f eRet = processForceCommand(rlvCmd); break; case RLV_TYPE_REPLY: // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f eRet = processReplyCommand(rlvCmd); break; case RLV_TYPE_UNKNOWN: // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f default: eRet = RLV_RET_FAILED_PARAM; break; } RLV_ASSERT(RLV_RET_UNKNOWN != eRet); #ifdef RLV_DEBUG RLV_INFOS << "\t--> command " << ((eRet & RLV_RET_SUCCESS) ? "succeeded" : "failed") << RLV_ENDL; #endif // RLV_DEBUG m_CurCommandStack.pop(); m_CurObjectStack.pop(); return eRet; } // Checked: 2010-02-27 (RLVa-1.2.0a) | Modified: RLVa-1.1.0f void RlvHandler::processRetainedCommands(ERlvBehaviour eBhvrFilter /*=RLV_BHVR_UNKNOWN*/, ERlvParamType eTypeFilter /*=RLV_TYPE_UNKNOWN*/) { rlv_command_list_t::iterator itCmd = m_Retained.begin(), itCurCmd; while (itCmd != m_Retained.end()) { itCurCmd = itCmd++; // Point the loop iterator ahead const RlvCommand& rlvCmd = *itCurCmd; if ( ((RLV_BHVR_UNKNOWN == eBhvrFilter) || (rlvCmd.getBehaviourType() == eBhvrFilter)) && ((RLV_TYPE_UNKNOWN == eTypeFilter) || (rlvCmd.getParamType() == eTypeFilter)) ) { processCommand(rlvCmd, true); m_Retained.erase(itCurCmd); } } } ERlvCmdRet RlvHandler::processClearCommand(const RlvCommand& rlvCmd) { const std::string& strFilter = rlvCmd.getParam(); std::string strCmdRem; rlv_object_map_t::const_iterator itObj = m_Objects.find(rlvCmd.getObjectID()); if (itObj != m_Objects.end()) // No sense in clearing if we don't have any commands for this object { const RlvObject& rlvObj = itObj->second; bool fContinue = true; for (rlv_command_list_t::const_iterator itCmd = rlvObj.m_Commands.begin(), itCurCmd; ((fContinue) && (itCmd != rlvObj.m_Commands.end())); ) { itCurCmd = itCmd++; // Point itCmd ahead so it won't get invalidated if/when we erase a command const RlvCommand& rlvCmdRem = *itCurCmd; strCmdRem = rlvCmdRem.asString(); if ( (strFilter.empty()) || (std::string::npos != strCmdRem.find(strFilter)) ) { fContinue = (rlvObj.m_Commands.size() > 1); // rlvObj will become invalid once we remove the last command processCommand(rlvCmd.getObjectID(), strCmdRem.append("=y"), false); } } } // Let our observers know about clear commands ERlvCmdRet eRet = RLV_RET_SUCCESS; notifyCommandHandlers(&RlvCommandHandler::onClearCommand, rlvCmd, eRet, true); return RLV_RET_SUCCESS; // Don't fail clear commands even if the object didn't exist since it confuses people } // ============================================================================ // Externally invoked event handlers // // Checked: 2010-08-29 (RLVa-1.2.1c) | Modified: RLVa-1.2.1c void RlvHandler::onSitOrStand(bool fSitting) { #ifdef RLV_EXTENSION_STARTLOCATION if (rlv_handler_t::isEnabled()) { RlvSettings::updateLoginLastLocation(); } #endif // RLV_EXTENSION_STARTLOCATION if ( (hasBehaviour(RLV_BHVR_STANDTP)) && (!fSitting) && (!m_posSitSource.isExactlyZero()) ) { RlvUtil::forceTp(m_posSitSource); m_posSitSource.setZero(); } } // Checked: 2010-03-11 (RLVa-1.2.0a) | Modified: RLVa-1.2.0a void RlvHandler::onAttach(const LLViewerObject* pAttachObj, const LLViewerJointAttachment* pAttachPt) { // Assertion - pAttachObj is never NULL always specifies the root RLV_ASSERT( (pAttachObj) && (pAttachObj == pAttachObj->getRootEdit()) ); // Sanity check - we need to be called *after* LLViewerJointAttachment::addObject() RLV_ASSERT( (pAttachPt) && (pAttachPt->isObjectAttached(pAttachObj)) ); if ( (!pAttachObj) || (!pAttachPt) || (!pAttachPt->isObjectAttached(pAttachObj)) ) return; // Check if we already have an RlvObject instance for this object or one of its child prims for (rlv_object_map_t::iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj) { // Only if we haven't been able to find this object (= attachment that rezzed in) or if it's a rezzed prim attached from in-world if ( (!itObj->second.m_fLookup) || (!itObj->second.m_idxAttachPt) ) { const LLViewerObject* pObj = gObjectList.findObject(itObj->first); if ( (pObj) && (pObj->getRootEdit()->getID() == pAttachObj->getID()) ) { // Reset any lookup information we might have for this object itObj->second.m_fLookup = true; itObj->second.m_idxAttachPt = RlvAttachPtLookup::getAttachPointIndex(pAttachObj); itObj->second.m_idRoot = pAttachObj->getID(); // We need to check this object for an active "@detach=n" and actually lock it down now that it's been attached somewhere if (itObj->second.hasBehaviour(RLV_BHVR_DETACH, false)) gRlvAttachmentLocks.addAttachmentLock(pAttachObj->getID(), itObj->second.m_UUID); } } } // Fetch the inventory item if it isn't already (we need it in case of a reattach-on-detach) and rename it if appropriate if ( (STATE_STARTED == LLStartUp::getStartupState()) && (gInventory.isInventoryUsable()) ) { RlvRenameOnWearObserver* pFetcher = new RlvRenameOnWearObserver(pAttachObj->getAttachmentItemID()); pFetcher->startFetch(); if (pFetcher->isFinished()) pFetcher->done(); else gInventory.addObserver(pFetcher); } } // Checked: 2010-03-11 (RLVa-1.2.0a) | Modified: RLVa-1.2.0a void RlvHandler::onDetach(const LLViewerObject* pAttachObj, const LLViewerJointAttachment* pAttachPt) { // Assertion - pAttachObj is never NULL always specifies the root RLV_ASSERT( (pAttachObj) && (pAttachObj == pAttachObj->getRootEdit()) ); // Sanity check - we need to be called *before* LLViewerJointAttachment::removeObject() RLV_ASSERT( (pAttachPt) && (pAttachPt->isObjectAttached(pAttachObj)) ); if ( (!pAttachObj) || (!pAttachPt) || (!pAttachPt->isObjectAttached(pAttachObj)) ) return; // If the attachment is no longer attached then then the user "Drop"'ed this attachment somehow if (!pAttachObj->isAttachment()) { // Check if we have any RlvObject instances for this object (or any of its child prims) for (rlv_object_map_t::iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj) { if ( (itObj->second.m_fLookup) && (itObj->second.m_idRoot == pAttachObj->getID()) ) { // Clear the attachment point lookup since it's now an in-world prim itObj->second.m_idxAttachPt = false; // If this object has an active "@detach=n" then we need to release the attachment lock since it's no longer attached if (itObj->second.hasBehaviour(RLV_BHVR_DETACH, false)) gRlvAttachmentLocks.removeAttachmentLock(pAttachObj->getID(), itObj->second.m_UUID); } } } else { // If it's still attached then we need to clean up any restrictions this object (or one of its child prims) may still have set rlv_object_map_t::iterator itObj = m_Objects.begin(), itCurObj; while (itObj != m_Objects.end()) { itCurObj = itObj++; // @clear will invalidate our iterator so point it ahead now #ifdef RLV_DEBUG bool itObj = true; RLV_ASSERT(itObj); // Little hack to push itObj out of scope and prevent it from being accidentally used below #endif // RLV_DEBUG // NOTE: ObjectKill seems to happen in reverse (child prims are killed before the root is) so we can't use gObjectList here if (itCurObj->second.m_idRoot == pAttachObj->getID()) { RLV_INFOS << "Clearing " << itCurObj->first.asString() << ":" << RLV_ENDL; processCommand(itCurObj->second.m_UUID, "clear", true); RLV_INFOS << "\t-> done" << RLV_ENDL; } } } } // Checked: 2010-03-13 (RLVa-1.2.0a) | Modified: RLVa-1.2.0a bool RlvHandler::onGC() { rlv_object_map_t::iterator itObj = m_Objects.begin(), itCurObj; while (itObj != m_Objects.end()) { itCurObj = itObj++; // @clear will invalidate our iterator so point it ahead now #ifdef RLV_DEBUG bool itObj = true; RLV_ASSERT(itObj); #endif // RLV_DEBUG const LLViewerObject* pObj = gObjectList.findObject(itCurObj->second.m_UUID); if (!pObj) { // If the RlvObject once existed in gObjectList and now doesn't then expire it right away // If the RlvObject never existed in gObjectList and still doesn't then increase its "lookup misses" counter // but if that reaches 20 (we run every 30 seconds so that's about 10 minutes) then we'll expire it too if ( (itCurObj->second.m_fLookup) || (++itCurObj->second.m_nLookupMisses > 20) ) { RLV_INFOS << "Garbage collecting " << itCurObj->first.asString() << ":" << RLV_ENDL; processCommand(itCurObj->first, "clear", true); RLV_INFOS << "\t-> done" << RLV_ENDL; } } else { // Assertion: if the GC encounters an RlvObject instance that hasn't existed in gObjectList up until now then // it has to be a rezzed prim (if it was an attachment then RlvHandler::onAttach() should have caught it) RLV_ASSERT( (itCurObj->second.m_fLookup) || (!pObj->isAttachment()) ); if (!itCurObj->second.m_fLookup) { RLV_INFOS << "Resolved missing object " << itCurObj->first.asString() << RLV_ENDL; itCurObj->second.m_fLookup = true; itCurObj->second.m_idxAttachPt = RlvAttachPtLookup::getAttachPointIndex(pObj); itCurObj->second.m_idRoot = pObj->getRootEdit()->getID(); // NOTE: the following code should NEVER run (see assertion above), but just to be double-triple safety sure // -> if it does run it likely means that there's a @detach=n in a *child* prim that we couldn't look up in onAttach() // -> since RLV doesn't currently support @detach=n from child prims it's actually not such a big deal right now but still if ( (pObj->isAttachment()) && (itCurObj->second.hasBehaviour(RLV_BHVR_DETACH, false)) ) gRlvAttachmentLocks.addAttachmentLock(pObj->getID(), itCurObj->second.m_UUID); } } } RLV_ASSERT(gRlvAttachmentLocks.verifyAttachmentLocks()); // Verify that we haven't leaked any attachment locks somehow return (0 != m_Objects.size()); // GC will kill itself if it has nothing to do } // Checked: 2009-11-26 (RLVa-1.1.0f) | Added: RLVa-1.1.0f void RlvHandler::onIdleStartup(void* pParam) { LLTimer* pTimer = (LLTimer*)pParam; if (LLStartUp::getStartupState() < STATE_STARTED) { // We don't want to run this *too* often if ( (LLStartUp::getStartupState() >= STATE_MISC) && (pTimer->getElapsedTimeF32() >= 2.0) ) { gRlvHandler.processRetainedCommands(RLV_BHVR_VERSION, RLV_TYPE_REPLY); gRlvHandler.processRetainedCommands(RLV_BHVR_VERSIONNEW, RLV_TYPE_REPLY); gRlvHandler.processRetainedCommands(RLV_BHVR_VERSIONNUM, RLV_TYPE_REPLY); pTimer->reset(); } } else { // Clean-up gIdleCallbacks.deleteFunction(onIdleStartup, pParam); delete pTimer; } } // Checked: 2010-03-09 (RLVa-1.2.0a) | Added: RLVa-1.2.0a void RlvHandler::onLoginComplete() { RlvAttachPtLookup::initLookupTable(); RlvInventory::instance().fetchWornItems(); RlvInventory::instance().fetchSharedInventory(); #ifdef RLV_EXTENSION_STARTLOCATION RlvSettings::updateLoginLastLocation(); #endif // RLV_EXTENSION_STARTLOCATION processRetainedCommands(); } // ============================================================================ // String/chat censoring functions // // LL must have included an strlen for UTF8 *somewhere* but I can't seem to find it so this one is home grown size_t utf8str_strlen(const std::string& utf8) { const char* pUTF8 = utf8.c_str(); size_t length = 0; for (int idx = 0, cnt = utf8.length(); idx < cnt ;idx++) { // We're looking for characters that don't start with 10 as their high bits if ((pUTF8[idx] & 0xC0) != 0x80) length++; } return length; } // TODO-RLV: works, but more testing won't hurt std::string utf8str_chtruncate(const std::string& utf8, size_t length) { if (0 == length) return std::string(); if (utf8.length() <= length) return utf8; const char* pUTF8 = utf8.c_str(); int idx = 0; while ( (pUTF8[idx]) && (length > 0) ) { // We're looking for characters that don't start with 10 as their high bits if ((pUTF8[idx] & 0xC0) != 0x80) length--; idx++; } return utf8.substr(0, idx); } // Checked: 2010-03-26 (RLVa-1.2.0b) | Modified: RLVa-1.0.0f void RlvHandler::filterChat(std::string& strUTF8Text, bool fFilterEmote) const { if (strUTF8Text.empty()) return; if (RlvUtil::isEmote(strUTF8Text)) // Check if it's an emote { if (fFilterEmote) // Emote filtering depends on fFilterEmote { if ( (strUTF8Text.find_first_of("\"()*=^_?~") != std::string::npos) || (strUTF8Text.find(" -") != std::string::npos) || (strUTF8Text.find("- ") != std::string::npos) || (strUTF8Text.find("''") != std::string::npos) ) { strUTF8Text = "..."; // Emote contains illegal character (or character sequence) } else if (!hasBehaviour(RLV_BHVR_EMOTE)) { int idx = strUTF8Text.find('.'); // Truncate at 20 characters or at the dot (whichever is shorter) strUTF8Text = utf8str_chtruncate(strUTF8Text, ( (idx > 0) && (idx < 20) ) ? idx + 1 : 20); } } } else if (strUTF8Text[0] == '/') // Not an emote, but starts with a '/' { if (utf8str_strlen(strUTF8Text) > 7) // Allow as long if it's 6 characters or less strUTF8Text = "..."; } else if ((strUTF8Text.length() < 4) || (strUTF8Text.compare(0, 2, "((")) || (strUTF8Text.compare(strUTF8Text.length() - 2, 2, "))"))) { strUTF8Text = "..."; // Regular chat (not OOC) } } // Checked: 2010-02-27 (RLVa-1.2.0b) | Modified: RLVa-1.2.0a bool RlvHandler::redirectChatOrEmote(const std::string& strUTF8Text) const { // Sanity check - @redirchat only for chat and @rediremote only for emotes ERlvBehaviour eBhvr = (!RlvUtil::isEmote(strUTF8Text)) ? RLV_BHVR_REDIRCHAT : RLV_BHVR_REDIREMOTE; if (!hasBehaviour(eBhvr)) return false; if (RLV_BHVR_REDIRCHAT == eBhvr) { std::string strText = strUTF8Text; filterChat(strText, false); if (strText != "...") return false; // @sendchat wouldn't filter it so @redirchat won't redirect it either } for (rlv_exception_map_t::const_iterator itRedir = m_Exceptions.lower_bound(eBhvr), endRedir = m_Exceptions.upper_bound(eBhvr); itRedir != endRedir; ++itRedir) { S32 nChannel = boost::get(itRedir->second.varOption); if ( (!hasBehaviour(RLV_BHVR_SENDCHANNEL)) || (isException(RLV_BHVR_SENDCHANNEL, nChannel)) ) RlvUtil::sendChatReply(nChannel, strUTF8Text); } return true; } // ============================================================================ // Composite folders // #ifdef RLV_EXPERIMENTAL_COMPOSITEFOLDERS // Checked: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i bool RlvHandler::getCompositeInfo(const LLInventoryCategory* pFolder, std::string* pstrName) const { if (pFolder) { // Composite folder naming: ^\.?[Folder] const std::string& cstrFolder = pFolder->getName(); std::string::size_type idxStart = cstrFolder.find('['), idxEnd = cstrFolder.find(']', idxStart); if ( ((0 == idxStart) || (1 == idxStart)) && (idxEnd - idxStart > 1) ) { if (pstrName) pstrName->assign(cstrFolder.substr(idxStart + 1, idxEnd - idxStart - 1)); return true; } } return false; } // Checked: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i bool RlvHandler::getCompositeInfo(const LLUUID& idItem, std::string* pstrName, LLViewerInventoryCategory** ppFolder) const { LLViewerInventoryCategory* pRlvRoot; LLViewerInventoryItem* pItem; if ( (idItem.notNull()) && ((pRlvRoot = getSharedRoot()) != NULL) && (gInventory.isObjectDescendentOf(idItem, pRlvRoot->getUUID())) && ((pItem = gInventory.getItem(idItem)) != NULL) ) { // We know it's an item in a folder under the shared root (we need its parent if it's a folded folder) LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID()); if (isFoldedFolder(pFolder, true, false)) // Don't check if the folder is a composite folder pFolder = gInventory.getCategory(pFolder->getParentUUID()); if ( (pFolder) && (getCompositeInfo(pFolder, pstrName)) ) { if (ppFolder) *ppFolder = pFolder; return true; } } return false; } #endif // RLV_EXPERIMENTAL_COMPOSITEFOLDERS #ifdef RLV_EXPERIMENTAL_COMPOSITE_FOLDING // Checked: inline bool RlvHandler::isHiddenCompositeItem(const LLUUID& idItem, const std::string& cstrItemType) const { // An item that's part of a composite folder will be hidden from @getoutfit and @getattach if: // (1) the composite name specifies either a wearable layer or an attachment point // (2) the specified wearable layer or attachment point is worn and resides in the folder // (3) cstrItemType isn't the specified wearable layer or attach point // // Example: #RLV/Separates/Shoes/ChiChi Pumps/.[shoes] with items: "Shoe Base", "Shoe (left foot)" and "Shoe (right foot)" // -> as long as "Shoe Base" is worn, @getattach should not reflect "left foot", nor "right foot" std::string strComposite; LLViewerInventoryCategory* pFolder; EWearableType type; S32 idxAttachPt; if ( (getCompositeInfo(idItem, &strComposite, &pFolder)) && (cstrItemType != strComposite) ) { LLUUID idCompositeItem; if ((type = LLWearable::typeNameToType(strComposite)) != WT_INVALID) { idCompositeItem = gAgent.getWearableItem(type); } else if ((idxAttachPt = getAttachPointIndex(strComposite, true)) != 0) { LLVOAvatar* pAvatar; LLViewerJointAttachment* pAttachmentPt; if ( ((pAvatar = gAgent.getAvatarObject()) != NULL) && ((pAttachmentPt = get_if_there(pAvatar->mAttachmentPoints, idxAttachPt, (LLViewerJointAttachment*)NULL)) != NULL) ) { idCompositeItem = pAttachmentPt->getItemID(); } } if ( (idCompositeItem.notNull()) && (gInventory.isObjectDescendentOf(idCompositeItem, pFolder->getUUID())) ) return true; } return false; } #endif // RLV_EXPERIMENTAL_COMPOSITEFOLDING #ifdef RLV_EXPERIMENTAL_COMPOSITEFOLDERS // Checked: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i bool RlvHandler::canTakeOffComposite(const LLInventoryCategory* pFolder) const { // Sanity check - if there's no folder or no avatar then there is nothing to take off LLVOAvatar* pAvatar = gAgent.getAvatarObject(); if ( (!pFolder) || (!pAvatar) ) return false; // Sanity check - if nothing is locked then we can definitely take it off if ( (!hasBehaviour(RLV_BHVR_REMOUTFIT)) && (!hasLockedAttachment(RLV_LOCK_REMOVE)) ) return true; LLInventoryModel::cat_array_t folders; LLInventoryModel::item_array_t items; RlvWearableItemCollector functor(pFolder->getUUID(), true, false); gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor); for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++) { const LLViewerInventoryItem* pItem = items.get(idxItem); switch (pItem->getType()) { case LLAssetType::AT_BODYPART: case LLAssetType::AT_CLOTHING: { LLWearable* pWearable = gAgent.getWearableFromWearableItem(pItem->getUUID()); if ( (pWearable) && (!isRemovable(pWearable->getType())) ) return false; // If one wearable in the folder is non-removeable then the entire folder should be } break; case LLAssetType::AT_OBJECT: { LLViewerObject* pObj = pAvatar->getWornAttachment(pItem->getUUID()); if ( (pObj != NULL) && (isLockedAttachment(pObj, RLV_LOCK_REMOVE)) ) return false; // If one attachment in the folder is non-detachable then the entire folder should be } break; default: break; } } return true; } // Checked: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i bool RlvHandler::canWearComposite(const LLInventoryCategory* pFolder) const { // Sanity check - if there's no folder or no avatar then there is nothing to wear LLVOAvatar* pAvatar = gAgent.getAvatarObject(); if ( (!pFolder) || (!pAvatar) ) return false; // Sanity check - if nothing is locked then we can definitely wear it if ( (!hasBehaviour(RLV_BHVR_ADDOUTFIT)) && (!hasBehaviour(RLV_BHVR_REMOUTFIT)) && (!hasLockedAttachment(RLV_LOCK_ANY)) ) return true; LLInventoryModel::cat_array_t folders; LLInventoryModel::item_array_t items; RlvWearableItemCollector functor(pFolder->getUUID(), true, false); gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor); for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++) { LLViewerInventoryItem* pItem = items.get(idxItem); if (RlvForceWearLegacy::isWearingItem(pItem)) continue; // Don't examine any items we're already wearing // A wearable layer or attachment point: // - can't be "add locked" // - can't be worn and "remove locked" // - can't be worn and have its item belong to a *different* composite folder that we can't take off switch (pItem->getType()) { case LLAssetType::AT_BODYPART: case LLAssetType::AT_CLOTHING: { // NOTE: without its asset we don't know what type the wearable is so we need to look at the item's flags instead EWearableType wtType = (EWearableType)(pItem->getFlags() & LLInventoryItem::II_FLAGS_WEARABLES_MASK); LLViewerInventoryCategory* pFolder; if ( (!isWearable(wtType)) || ( (gAgent.getWearable(wtType)) && (!isRemovable(wtType)) ) || ( (gRlvHandler.getCompositeInfo(gAgent.getWearableItem(wtType), NULL, &pFolder)) && (pFolder->getUUID() != pItem->getParentUUID()) && (!gRlvHandler.canTakeOffComposite(pFolder)) ) ) { return false; } } break; case LLAssetType::AT_OBJECT: { // If we made it here then *something* is add/remove locked so we absolutely need to know its attachment point LLViewerJointAttachment* pAttachPt = getAttachPoint(pItem, true); LLViewerInventoryCategory* pFolder; if ( (!pAttachPt) || (isLockedAttachment(pAttachPt, RLV_LOCK_ADD)) || ( (pAttachPt->getObject()) && (isLockedAttachment(pAttachPt, RLV_LOCK_REMOVE)) ) || ( (gRlvHandler.getCompositeInfo(pAttachPt->getItemID(), NULL, &pFolder)) && (pFolder->getUUID() != pItem->getParentUUID()) && (!gRlvHandler.canTakeOffComposite(pFolder)) ) ) { return false; } } break; default: break; } } return true; } #endif // RLV_EXPERIMENTAL_COMPOSITEFOLDERS // ============================================================================ // Initialization helper functions // // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f BOOL RlvHandler::setEnabled(BOOL fEnable) { if (m_fEnabled == fEnable) return fEnable; if (fEnable) { // Initialize the command lookup table RlvCommand::initLookupTable(); // Initialize static classes RlvSettings::initClass(); RlvStrings::initClass(); gRlvHandler.addCommandHandler(new RlvExtGetSet()); // Fetch shared inventory if we're enabled after logon if (LLStartUp::getStartupState() >= STATE_CLEANUP) RlvInventory::instance().fetchSharedInventory(); m_fEnabled = TRUE; } #ifdef RLV_ADVANCED_MENU // RELEASE-RLVa: LL defines CLIENT_MENU_NAME but we can't get to it from here so we need to keep those two in sync manually LLMenuGL* pClientMenu = NULL; if ( (gMenuBarView) && ((pClientMenu = gMenuBarView->getChildMenuByName("Advanced", FALSE)) != NULL) ) { pClientMenu->setItemVisible("RLVa", m_fEnabled); pClientMenu->setItemEnabled("RLVa", m_fEnabled); } #endif // RLV_ADVANCED_MENU return m_fEnabled; // Return enabled/disabled state } BOOL RlvHandler::canDisable() { return FALSE; } // ============================================================================ // Command handlers (RLV_TYPE_ADD and RLV_TYPE_REMOVE) // #define VERIFY_OPTION(x) { if (!(x)) { eRet = RLV_RET_FAILED_OPTION; break; } } #define VERIFY_OPTION_REF(x) { if (!(x)) { eRet = RLV_RET_FAILED_OPTION; break; } fRefCount = true; } // Checked: 2010-03-03 (RLVa-1.1.3b) | Modified: RLVa-1.2.0a ERlvCmdRet RlvHandler::processAddRemCommand(const RlvCommand& rlvCmd) { // NOTE: - at this point the command has already been: // * added to the RlvObject // * removed from the RlvObject (which still exists at this point even if this is the last restriction) // - the object's UUID may or may not exist in gObjectList (see handling of @detach=n|y) ERlvBehaviour eBhvr = rlvCmd.getBehaviourType(); ERlvParamType eType = rlvCmd.getParamType(); ERlvCmdRet eRet = RLV_RET_SUCCESS; bool fRefCount = false, fRefreshHover = false; const std::string& strOption = rlvCmd.getOption(); switch (eBhvr) { case RLV_BHVR_DETACH: // @detach[: