diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt
index 4786956e85f20957cd6c76ac9768232057b27ef2..e44309476b1110c0901c1502cb946141dfdae41a 100644
--- a/indra/llmessage/CMakeLists.txt
+++ b/indra/llmessage/CMakeLists.txt
@@ -30,6 +30,7 @@ set(llmessage_SOURCE_FILES
     lldispatcher.cpp
     llexperiencecache.cpp
     llfiltersd2xmlrpc.cpp
+    llgenericstreamingmessage.cpp
     llhost.cpp
     llhttpnode.cpp
     llhttpsdhandler.cpp
@@ -114,6 +115,7 @@ set(llmessage_HEADER_FILES
     llextendedstatus.h
     llfiltersd2xmlrpc.h
     llfollowcamparams.h
+    llgenericstreamingmessage.h
     llhost.h
     llhttpnode.h
     llhttpnodeadapter.h
diff --git a/indra/llmessage/llgenericstreamingmessage.cpp b/indra/llmessage/llgenericstreamingmessage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8627675c54edd74d8ef3f8ed8c88bf962c7754bc
--- /dev/null
+++ b/indra/llmessage/llgenericstreamingmessage.cpp
@@ -0,0 +1,72 @@
+/**
+ * @file llgenericstreamingmessage.cpp
+ * @brief Generic Streaming Message helpers.  Shared between viewer and simulator.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, 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
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llgenericstreamingmessage.h"
+
+#include "message.h"
+
+void LLGenericStreamingMessage::send(LLMessageSystem* msg)
+{
+#if 0 // viewer cannot send GenericStreamingMessage
+    msg->newMessageFast(_PREHASH_GenericStreamingMessage);
+
+    if (mData.size() < 1024 * 7)
+    { // disable warning about big messages unless we're sending a REALLY big message
+        msg->tempDisableWarnAboutBigMessage();
+    }
+    else
+    {
+        LL_WARNS("Messaging") << "Attempted to send too large GenericStreamingMessage, dropping." << LL_ENDL;
+        return;
+    }
+
+    msg->nextBlockFast(_PREHASH_MethodData);
+    msg->addU16Fast(_PREHASH_Method, mMethod);
+    msg->nextBlockFast(_PREHASH_DataBlock);
+    msg->addStringFast(_PREHASH_Data, mData.c_str());
+#endif
+}
+
+void LLGenericStreamingMessage::unpack(LLMessageSystem* msg)
+{
+    U16* m = (U16*)&mMethod; // squirrely pass enum as U16 by reference
+    msg->getU16Fast(_PREHASH_MethodData, _PREHASH_Method, *m);
+
+    constexpr int MAX_SIZE = 7 * 1024;
+
+    char buffer[MAX_SIZE];
+
+    // NOTE: don't use getStringFast to avoid 1200 byte truncation
+    U32 size = msg->getSizeFast(_PREHASH_DataBlock, _PREHASH_Data);
+    msg->getBinaryDataFast(_PREHASH_DataBlock, _PREHASH_Data, buffer, size, 0, MAX_SIZE);
+
+    mData.assign(buffer, size);
+}
+
+
+
diff --git a/indra/llmessage/llgenericstreamingmessage.h b/indra/llmessage/llgenericstreamingmessage.h
new file mode 100644
index 0000000000000000000000000000000000000000..9ac9719ea1402a815147a67dd1db38b90c511108
--- /dev/null
+++ b/indra/llmessage/llgenericstreamingmessage.h
@@ -0,0 +1,50 @@
+/**
+ * @file llgenericstreamingmessage.h
+ * @brief Generic Streaming Message helpers.  Shared between viewer and simulator.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, 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
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+#include <string>
+#include "stdtypes.h"
+
+class LLMessageSystem;
+
+class LLGenericStreamingMessage
+{
+public:
+    enum Method : U16
+    {
+        METHOD_GLTF_MATERIAL_OVERRIDE = 0x4175,
+        METHOD_UNKNOWN = 0xFFFF,
+    };
+
+    void send(LLMessageSystem* msg);
+    void unpack(LLMessageSystem* msg);
+
+    Method mMethod = METHOD_UNKNOWN;
+    std::string mData;
+};
+
+
diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp
index 57ea954054bcfadfc20c95c3657918a6eb976a8e..4dccacb889ee882f12337c5adb41493770756efe 100644
--- a/indra/llmessage/message_prehash.cpp
+++ b/indra/llmessage/message_prehash.cpp
@@ -1367,6 +1367,7 @@ char const* const _PREHASH_MuteType = LLMessageStringTable::getInstance()->getSt
 char const* const _PREHASH_IMViaEMail = LLMessageStringTable::getInstance()->getString("IMViaEMail");
 char const* const _PREHASH_RentPrice = LLMessageStringTable::getInstance()->getString("RentPrice");
 char const* const _PREHASH_GenericMessage = LLMessageStringTable::getInstance()->getString("GenericMessage");
+char const* const _PREHASH_GenericStreamingMessage = LLMessageStringTable::getInstance()->getString("GenericStreamingMessage");
 char const* const _PREHASH_ChildAgentAlive = LLMessageStringTable::getInstance()->getString("ChildAgentAlive");
 char const* const _PREHASH_AssetType = LLMessageStringTable::getInstance()->getString("AssetType");
 char const* const _PREHASH_SpawnPointBlock = LLMessageStringTable::getInstance()->getString("SpawnPointBlock");
diff --git a/indra/llmessage/message_prehash.h b/indra/llmessage/message_prehash.h
index 572dadd40820447cc348a15320b95aa50de3d6d2..a393bbabb2e1db7af5dfd5446706f7451f6ab8e4 100644
--- a/indra/llmessage/message_prehash.h
+++ b/indra/llmessage/message_prehash.h
@@ -1368,6 +1368,7 @@ extern char const* const _PREHASH_MuteType;
 extern char const* const _PREHASH_IMViaEMail;
 extern char const* const _PREHASH_RentPrice;
 extern char const* const _PREHASH_GenericMessage;
+extern char const* const _PREHASH_GenericStreamingMessage;
 extern char const* const _PREHASH_ChildAgentAlive;
 extern char const* const _PREHASH_AssetType;
 extern char const* const _PREHASH_SpawnPointBlock;
diff --git a/indra/llprimitive/llgltfmaterial.cpp b/indra/llprimitive/llgltfmaterial.cpp
index c16803d39d8a59a3ddf912f017b8d4e6b97175a1..8475e7231aec971b30749eb90e9e48826f91c9fe 100644
--- a/indra/llprimitive/llgltfmaterial.cpp
+++ b/indra/llprimitive/llgltfmaterial.cpp
@@ -27,6 +27,7 @@
 #include "linden_common.h"
 
 #include "llgltfmaterial.h"
+#include "llsdserialize.h"
 
 // NOTE -- this should be the one and only place tiny_gltf.h is included
 #include "tinygltf/tiny_gltf.h"
@@ -693,6 +694,177 @@ void LLGLTFMaterial::applyOverride(const LLGLTFMaterial& override_mat)
     }
 }
 
+void LLGLTFMaterial::getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& data)
+{
+    LL_PROFILE_ZONE_SCOPED;
+    llassert(data.isUndefined());
+
+    // make every effort to shave bytes here
+
+    for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i)
+    {
+        LLUUID& texture_id = mTextureId[i];
+        const LLUUID& override_texture_id = override_mat.mTextureId[i];
+        if (override_texture_id.notNull() && override_texture_id != texture_id)
+        {
+            data["tex"][i] = LLSD::UUID(override_texture_id);
+        }
+
+    }
+
+    if (override_mat.mBaseColor != getDefaultBaseColor())
+    {
+        data["bc"] = override_mat.mBaseColor.getValue();
+    }
+
+    if (override_mat.mEmissiveColor != getDefaultEmissiveColor())
+    {
+        data["ec"] = override_mat.mEmissiveColor.getValue();
+    }
+
+    if (override_mat.mMetallicFactor != getDefaultMetallicFactor())
+    {
+        data["mf"] = override_mat.mMetallicFactor;
+    }
+
+    if (override_mat.mRoughnessFactor != getDefaultRoughnessFactor())
+    {
+        data["rf"] = override_mat.mRoughnessFactor;
+    }
+
+    if (override_mat.mAlphaMode != getDefaultAlphaMode() || override_mat.mOverrideAlphaMode)
+    {
+        data["am"] = override_mat.mAlphaMode;
+    }
+
+    if (override_mat.mAlphaCutoff != getDefaultAlphaCutoff())
+    {
+        data["ac"] = override_mat.mAlphaCutoff;
+    }
+
+    if (override_mat.mDoubleSided != getDefaultDoubleSided() || override_mat.mOverrideDoubleSided)
+    {
+        data["ds"] = override_mat.mDoubleSided;
+    }
+
+    for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i)
+    {
+        if (override_mat.mTextureTransform[i].mOffset != getDefaultTextureOffset())
+        {
+            data["ti"][i]["o"] = override_mat.mTextureTransform[i].mOffset.getValue();
+        }
+
+        if (override_mat.mTextureTransform[i].mScale != getDefaultTextureScale())
+        {
+            data["ti"][i]["s"] = override_mat.mTextureTransform[i].mScale.getValue();
+        }
+
+        if (override_mat.mTextureTransform[i].mRotation != getDefaultTextureRotation())
+        {
+            data["ti"][i]["r"] = override_mat.mTextureTransform[i].mRotation;
+        }
+    }
+
+#if 0
+    {
+        std::ostringstream ostr;
+        LLSDSerialize::serialize(data, ostr, LLSDSerialize::LLSD_NOTATION);
+        std::string param_str(ostr.str());
+        LL_INFOS() << param_str << LL_ENDL;
+        LL_INFOS() << "Notation size: " << param_str.size() << LL_ENDL;
+    }
+
+    {
+        std::ostringstream ostr;
+        LLSDSerialize::serialize(data, ostr, LLSDSerialize::LLSD_BINARY);
+        std::string param_str(ostr.str());
+        LL_INFOS() << "Binary size: " << param_str.size() << LL_ENDL;
+    }
+#endif
+}
+
+
+void LLGLTFMaterial::applyOverrideLLSD(const LLSD& data)
+{
+    const LLSD& tex = data["tex"];
+
+    if (tex.isArray())
+    {
+        for (int i = 0; i < tex.size(); ++i)
+        {
+            mTextureId[i] = tex[i].asUUID();
+        }
+    }
+
+    const LLSD& bc = data["bc"];
+    if (bc.isDefined())
+    {
+        mBaseColor.setValue(bc);
+    }
+
+    const LLSD& ec = data["ec"];
+    if (ec.isDefined())
+    {
+        mEmissiveColor.setValue(ec);
+    }
+
+    const LLSD& mf = data["mf"];
+    if (mf.isReal())
+    {
+        mMetallicFactor = mf.asReal();
+    }
+
+    const LLSD& rf = data["rf"];
+    if (rf.isReal())
+    {
+        mRoughnessFactor = rf.asReal();
+    }
+
+    const LLSD& am = data["am"];
+    if (am.isInteger())
+    {
+        mAlphaMode = (AlphaMode) am.asInteger();
+    }
+
+    const LLSD& ac = data["ac"];
+    if (ac.isReal())
+    {
+        mAlphaCutoff = ac.asReal();
+    }
+
+    const LLSD& ds = data["ds"];
+    if (data.isBoolean())
+    {
+        mDoubleSided = ds.asBoolean();
+        mOverrideDoubleSided = true;
+    }
+
+    const LLSD& ti = data["ti"];
+    if (ti.isArray())
+    {
+        for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i)
+        {
+            const LLSD& o = ti[i]["o"];
+            if (o.isDefined())
+            {
+                mTextureTransform[i].mOffset.setValue(o);
+            }
+
+            const LLSD& s = ti[i]["s"];
+            if (s.isDefined())
+            {
+                mTextureTransform[i].mScale.setValue(s);
+            }
+
+            const LLSD& r = ti[i]["r"];
+            if (r.isReal())
+            {
+                mTextureTransform[i].mRotation = r.asReal();
+            }
+        }
+    }
+}
+
 LLUUID LLGLTFMaterial::getHash() const
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
diff --git a/indra/llprimitive/llgltfmaterial.h b/indra/llprimitive/llgltfmaterial.h
index ad7784f6d1f41ca6874faf99b393f6f3f30ad89b..ca2750770783678925d26a7abdfe0e4ea70785c8 100644
--- a/indra/llprimitive/llgltfmaterial.h
+++ b/indra/llprimitive/llgltfmaterial.h
@@ -176,6 +176,7 @@ class LLGLTFMaterial : public LLRefCount
     // get the contents of this LLGLTFMaterial as a json string
     std::string asJSON(bool prettyprint = false) const;
 
+
     // initialize from given tinygltf::Model
     // model - the model to reference
     // mat_index - index of material in model's material array
@@ -185,6 +186,14 @@ class LLGLTFMaterial : public LLRefCount
     void writeToModel(tinygltf::Model& model, S32 mat_index) const;
 
     void applyOverride(const LLGLTFMaterial& override_mat);
+    
+    // apply the given LLSD override data
+    void applyOverrideLLSD(const LLSD& data);
+
+    // Get the given override on this LLGLTFMaterial as LLSD
+    // override_mat -- the override source data
+    // data -- output LLSD object (should be passed in empty)
+    void getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& data);
 
     // For base materials only (i.e. assets). Clears transforms to
     // default since they're not supported in assets yet.
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 343778fe038015e8401d6cf697aa9d5789fab621..a6e938e54c5f24382a876405ecafc3eb3bb50632 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -4138,7 +4138,7 @@ U32 LLAppViewer::getObjectCacheVersion()
 {
 	// Viewer object cache version, change if object update
 	// format changes. JC
-	const U32 INDRA_OBJECT_CACHE_VERSION = 16;
+	const U32 INDRA_OBJECT_CACHE_VERSION = 17;
 
 	return INDRA_OBJECT_CACHE_VERSION;
 }
diff --git a/indra/newview/llgltfmateriallist.cpp b/indra/newview/llgltfmateriallist.cpp
index 99a052f719092fd8240f71c2ebf8c9c7eda0ccc4..a204315a2ae107cfead1a301f5e3e16339efdd87 100644
--- a/indra/newview/llgltfmateriallist.cpp
+++ b/indra/newview/llgltfmateriallist.cpp
@@ -160,9 +160,9 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
         //  sides - array of S32 indices of texture entries
         //  gltf_json - array of corresponding Strings of GLTF json for override data
 
-
         LLSD message;
         bool success = true;
+#if 0 //deprecated
         for(const std::string& llsdRaw : strings)
         {
             std::istringstream llsdData(llsdRaw);
@@ -198,6 +198,7 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
             applyData(object_override);
         }
 
+#endif
         return success;
     }
 
@@ -213,6 +214,7 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
     {
         // Parse the data
 
+#if 0 // DEPRECATED
         LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
         LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General");
 
@@ -235,24 +237,17 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
 
                 results.reserve(sides.size());
                 // parse json
-                std::unordered_map<S32, std::string>::const_iterator iter = sides.begin();
-                std::unordered_map<S32, std::string>::const_iterator end = sides.end();
+                std::unordered_map<S32, LLSD>::const_iterator iter = sides.begin();
+                std::unordered_map<S32, LLSD>::const_iterator end = sides.end();
                 while (iter != end)
                 {
-                    std::string warn_msg, error_msg;
-
                     ReturnData result;
 
-                    bool success = result.mMaterial.fromJSON(iter->second, warn_msg, error_msg);
-
-                    result.mSuccess = success;
+                    result.mMaterial.applyOverrideLLSD(iter->second);
+                    
+                    result.mSuccess = true;
                     result.mSide = iter->first;
 
-                    if (!success)
-                    {
-                        LL_WARNS("GLTF") << "failed to parse GLTF override data.  errors: " << error_msg << " | warnings: " << warn_msg << LL_ENDL;
-                    }
-
                     results.push_back(result);
                     iter++;
                 }
@@ -318,6 +313,7 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
                 }
             });
         }
+#endif
     }
 
 private:
@@ -330,6 +326,59 @@ namespace
     LLGLTFMaterialOverrideDispatchHandler handle_gltf_override_message;
 }
 
+void LLGLTFMaterialList::applyOverrideMessage(LLMessageSystem* msg, const std::string& data_in)
+{
+    std::istringstream str(data_in);
+
+    LLSD data;
+
+    LLSDSerialize::fromNotation(data, str, data_in.length());
+
+    const LLHost& host = msg->getSender();
+    
+    LLViewerRegion* region = LLWorld::instance().getRegion(host);
+
+    if (region)
+    {
+        U32 local_id = data.get("id").asInteger();
+        LLUUID id;
+        gObjectList.getUUIDFromLocal(id, local_id, host.getAddress(), host.getPort());
+        LLViewerObject* obj = gObjectList.findObject(id);
+
+        if (obj)
+        {
+            const LLSD& tes = data["te"];
+            const LLSD& od = data["od"];
+
+            if (tes.isArray())
+            {
+                LLGLTFOverrideCacheEntry cache;
+                cache.mLocalId = local_id;
+                cache.mObjectId = id;
+                cache.mRegionHandle = region->getHandle();
+
+                for (int i = 0; i < tes.size(); ++i)
+                {
+                    S32 te = tes[i].asInteger();
+                    LLGLTFMaterial* mat = new LLGLTFMaterial(); // setTEGLTFMaterialOverride will take ownership
+                    mat->applyOverrideLLSD(od[i]);
+                    obj->setTEGLTFMaterialOverride(te, mat);
+
+                    cache.mSides[te] = od[i];
+                    cache.mGLTFMaterial[te] = mat;
+
+                    if (obj->getTE(te) && obj->getTE(te)->isSelected())
+                    {
+                        handle_gltf_override_message.doSelectionCallbacks(id, te);
+                    }
+                }
+
+                region->cacheFullUpdateGLTFOverride(cache);
+            }
+        }
+    }
+}
+
 void LLGLTFMaterialList::queueOverrideUpdate(const LLUUID& id, S32 side, LLGLTFMaterial* override_data)
 {
 #if 0
diff --git a/indra/newview/llgltfmateriallist.h b/indra/newview/llgltfmateriallist.h
index ce8781babacaf98b2c4696e1370536fbc3308825..7317214019d52ec41e39f96f057ca747c39d4f34 100644
--- a/indra/newview/llgltfmateriallist.h
+++ b/indra/newview/llgltfmateriallist.h
@@ -101,6 +101,9 @@ class LLGLTFMaterialList
 
     static void loadCacheOverrides(const LLGLTFOverrideCacheEntry& override);
 
+    // Apply an override update with the given data
+    void applyOverrideMessage(LLMessageSystem* msg, const std::string& data);
+
 private:
     friend class LLGLTFMaterialOverrideDispatchHandler;
     // save an override update that we got from the simulator for later (for example, if an override arrived for an unknown object)
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 8ffab761f4dad3cfda2e7ecc13e9677f1eba2dd6..eccfd40fe6ea2d0c8fd785a10d56edcab488e354 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -2709,6 +2709,7 @@ void register_viewer_callbacks(LLMessageSystem* msg)
 	msg->setHandlerFunc("InitiateDownload", process_initiate_download);
 	msg->setHandlerFunc("LandStatReply", LLFloaterTopObjects::handle_land_reply);
     msg->setHandlerFunc("GenericMessage", process_generic_message);
+    msg->setHandlerFunc("GenericStreamingMessage", process_generic_streaming_message);
     msg->setHandlerFunc("LargeGenericMessage", process_large_generic_message);
 
 	msg->setHandlerFuncFast(_PREHASH_FeatureDisabled, process_feature_disabled_message);
diff --git a/indra/newview/llviewergenericmessage.cpp b/indra/newview/llviewergenericmessage.cpp
index d3de9d72bfa979b86b9536b8592b122c90ee478f..7ea792c404976b9bc425399710ac9b0f2cab0beb 100644
--- a/indra/newview/llviewergenericmessage.cpp
+++ b/indra/newview/llviewergenericmessage.cpp
@@ -32,9 +32,10 @@
 #include "lldispatcher.h"
 #include "lluuid.h"
 #include "message.h"
+#include "llgenericstreamingmessage.h"
 
 #include "llagent.h"
-
+#include "llgltfmateriallist.h"
 
 LLDispatcher gGenericDispatcher;
 
@@ -92,6 +93,21 @@ void process_generic_message(LLMessageSystem* msg, void**)
 	}
 }
 
+void process_generic_streaming_message(LLMessageSystem* msg, void**)
+{
+    LLGenericStreamingMessage data;
+    data.unpack(msg);
+    switch (data.mMethod)
+    {
+    case LLGenericStreamingMessage::METHOD_GLTF_MATERIAL_OVERRIDE:
+        gGLTFMaterialList.applyOverrideMessage(msg, data.mData);
+        break;
+    default:
+        LL_WARNS() << "GenericStreamingMessage received unknown method: " << data.mMethod << LL_ENDL;
+        break;
+    }
+}
+
 void process_large_generic_message(LLMessageSystem* msg, void**)
 {
     LLUUID agent_id;
diff --git a/indra/newview/llviewergenericmessage.h b/indra/newview/llviewergenericmessage.h
index 170f38a48552931faed09f1bea03b5e350c3092f..96a73a3d5f0a14fd15e2f633d052fd989dac00ba 100644
--- a/indra/newview/llviewergenericmessage.h
+++ b/indra/newview/llviewergenericmessage.h
@@ -38,6 +38,7 @@ void send_generic_message(const std::string& method,
 						  const LLUUID& invoice = LLUUID::null);
 
 void process_generic_message(LLMessageSystem* msg, void**);
+void process_generic_streaming_message(LLMessageSystem* msg, void**);
 void process_large_generic_message(LLMessageSystem* msg, void**);
 
 
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 6b92b16ef4077aafcfaafa367ac66c0ba9581d21..ead1e8c07314f0187ec234b23dbb46dc5dcd8da5 100755
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -215,7 +215,7 @@ class LLViewerRegionImpl
 	LLVOCacheEntry::vocache_entry_set_t   mVisibleEntries; //must-be-created visible entries wait for objects creation.	
 	LLVOCacheEntry::vocache_entry_priority_list_t mWaitingList; //transient list storing sorted visible entries waiting for object creation.
 	std::set<U32>                          mNonCacheableCreatedList; //list of local ids of all non-cacheable objects
-    LLVOCacheEntry::vocache_gltf_overrides_map_t mGLTFOverridesJson; // for materials
+    LLVOCacheEntry::vocache_gltf_overrides_map_t mGLTFOverridesLLSD; // for materials
 
 	// time?
 	// LRU info?
@@ -787,7 +787,7 @@ void LLViewerRegion::loadObjectCache()
 	{
         LLVOCache & vocache = LLVOCache::instance();
 		vocache.readFromCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap);
-        vocache.readGenericExtrasFromCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesJson);
+        vocache.readGenericExtrasFromCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD);
 
 		if (mImpl->mCacheMap.empty())
 		{
@@ -817,7 +817,7 @@ void LLViewerRegion::saveObjectCache()
         LLVOCache & instance = LLVOCache::instance();
 
         instance.writeToCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap, mCacheDirty, removal_enabled);
-        instance.writeGenericExtrasToCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesJson, mCacheDirty, removal_enabled);
+        instance.writeGenericExtrasToCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD, mCacheDirty, removal_enabled);
 		mCacheDirty = FALSE;
 	}
 
@@ -2656,7 +2656,7 @@ LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLViewerObjec
 void LLViewerRegion::cacheFullUpdateGLTFOverride(const LLGLTFOverrideCacheEntry &override_data)
 {
     U32 local_id = override_data.mLocalId;
-    mImpl->mGLTFOverridesJson[local_id] = override_data;
+    mImpl->mGLTFOverridesLLSD[local_id] = override_data;
 }
 
 LLVOCacheEntry* LLViewerRegion::getCacheEntryForOctree(U32 local_id)
@@ -3546,8 +3546,8 @@ std::string LLViewerRegion::getSimHostName()
 
 void LLViewerRegion::loadCacheMiscExtras(U32 local_id)
 {
-    auto iter = mImpl->mGLTFOverridesJson.find(local_id);
-    if (iter != mImpl->mGLTFOverridesJson.end())
+    auto iter = mImpl->mGLTFOverridesLLSD.find(local_id);
+    if (iter != mImpl->mGLTFOverridesLLSD.end())
     {
         LLGLTFMaterialList::loadCacheOverrides(iter->second);
     }
@@ -3559,8 +3559,8 @@ void LLViewerRegion::applyCacheMiscExtras(LLViewerObject* obj)
     llassert(obj);
 
     U32 local_id = obj->getLocalID();
-    auto iter = mImpl->mGLTFOverridesJson.find(local_id);
-    if (iter != mImpl->mGLTFOverridesJson.end())
+    auto iter = mImpl->mGLTFOverridesLLSD.find(local_id);
+    if (iter != mImpl->mGLTFOverridesLLSD.end())
     {
         llassert(iter->second.mGLTFMaterial.size() == iter->second.mSides.size());
 
diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp
index a92057d01086de801b7fc15cf4a58853e6d469d3..dd5b9f9fd56a97ccd6d09223e43c90ddf93edcef 100644
--- a/indra/newview/llvocache.cpp
+++ b/indra/newview/llvocache.cpp
@@ -85,40 +85,24 @@ bool LLGLTFOverrideCacheEntry::fromLLSD(const LLSD& data)
 
     // message should be interpreted thusly:
     ///  sides is a list of face indices
-    //   gltf_json is a list of corresponding json
+    //   gltf_llsd is a list of corresponding GLTF override LLSD
     //   any side not represented in "sides" has no override
-    if (data.has("sides") && data.has("gltf_json"))
+    if (data.has("sides") && data.has("gltf_llsd"))
     {
         LLSD const& sides = data.get("sides");
-        LLSD const& gltf_json = data.get("gltf_json");
+        LLSD const& gltf_llsd = data.get("gltf_llsd");
 
-        if (sides.isArray() && gltf_json.isArray() &&
+        if (sides.isArray() && gltf_llsd.isArray() &&
             sides.size() != 0 &&
-            sides.size() == gltf_json.size())
+            sides.size() == gltf_llsd.size())
         {
             for (int i = 0; i < sides.size(); ++i)
             {
                 S32 side_idx = sides[i].asInteger();
-                std::string gltf_json_str = gltf_json[i].asString();
-                mSides[side_idx] = gltf_json_str;
+                mSides[side_idx] = gltf_llsd[i];
                 LLGLTFMaterial* override_mat = new LLGLTFMaterial();
-                std::string error, warn;
-                if (override_mat->fromJSON(gltf_json_str, warn, error))
-                {
-                    mGLTFMaterial[side_idx] = override_mat;
-                }
-                else
-                {
-                    LL_WARNS() << "Invalid GLTF string: \n" << gltf_json_str << LL_ENDL;
-                    if (!error.empty())
-                    {
-                        LL_WARNS() << "Error: " << error << LL_ENDL;
-                    }
-                    if (!warn.empty())
-                    {
-                        LL_WARNS() << "Warning: " << warn << LL_ENDL;
-                    }
-                }
+                override_mat->applyOverrideLLSD(gltf_llsd[i]);
+                mGLTFMaterial[side_idx] = override_mat;
             }
         }
         else
@@ -157,7 +141,7 @@ LLSD LLGLTFOverrideCacheEntry::toLLSD() const
         // check that mSides and mGLTFMaterial have exactly the same keys present
         llassert(mGLTFMaterial.count(side.first) == 1);
         data["sides"].append(LLSD::Integer(side.first));
-        data["gltf_json"].append(side.second);
+        data["gltf_llsd"].append(side.second);
     }
 
     return data;
diff --git a/indra/newview/llvocache.h b/indra/newview/llvocache.h
index ec0df31828552e647381264989b52166a3da3f2d..8525edd121e0125ad578fa4035126b0b85bcc392 100644
--- a/indra/newview/llvocache.h
+++ b/indra/newview/llvocache.h
@@ -48,7 +48,7 @@ class LLGLTFOverrideCacheEntry
 
     LLUUID mObjectId;
     U32    mLocalId = 0;
-    std::unordered_map<S32, std::string> mSides; //json per side
+    std::unordered_map<S32, LLSD> mSides; //override LLSD per side
     std::unordered_map<S32, LLPointer<LLGLTFMaterial> > mGLTFMaterial; //GLTF material per side
     U64 mRegionHandle = 0;
 };
diff --git a/scripts/messages/message_template.msg b/scripts/messages/message_template.msg
index a3ddc6d33640b2d76c45a965c0bc35a1fdc1caf8..c019a767932c8575edd6c29c1c5416da3d5db812 100755
--- a/scripts/messages/message_template.msg
+++ b/scripts/messages/message_template.msg
@@ -5790,6 +5790,25 @@ version 2.0
 	}
 }
 
+// GenericStreamingMessage
+// Optimized generic message for streaming arbitrary data to viewer
+// Avoid payloads over 7KB (8KB ceiling)
+// Method -- magic number indicating method to use to decode payload:
+//      0x4175 - GLTF material override data
+// Payload -- data to be decoded
+{
+    GenericStreamingMessage High 31 Trusted Unencoded
+    {
+        MethodData Single
+        { Method    U16 }
+    }
+
+    {
+        DataBlock Single
+        { Data Variable 2 }
+    }
+}
+
 // LargeGenericMessage
 // Similar to the above messages, but can handle larger payloads and serialized 
 // LLSD.  Uses HTTP transport 
diff --git a/scripts/messages/message_template.msg.sha1 b/scripts/messages/message_template.msg.sha1
index 4712a03e8d6e42cbcffb697f6ff16eddac8b033f..ddde49680e9172bd66e51e09f981884223da9ae8 100755
--- a/scripts/messages/message_template.msg.sha1
+++ b/scripts/messages/message_template.msg.sha1
@@ -1 +1 @@
-dddb11f7e45f1779ff536819f36a20e63d572ba8
\ No newline at end of file
+992450072b9c04c2157247be5cf5341b96d6f167
\ No newline at end of file