diff --git a/tools/dissectors/1-hfudt.lua b/tools/dissectors/1-hfudt.lua
index 3993b2d7a0..8b9251c739 100644
--- a/tools/dissectors/1-hfudt.lua
+++ b/tools/dissectors/1-hfudt.lua
@@ -151,13 +151,16 @@ local packet_types = {
   [96] = "OctreeDataFileReply",
   [97] = "OctreeDataPersist",
   [98] = "EntityClone",
-  [99] = "EntityQueryInitialResultsComplete"
+  [99] = "EntityQueryInitialResultsComplete",
+  [100] = "BulkAvatarTraits"
 }
 
 local unsourced_packet_types = {
   ["DomainList"] = true
 }
 
+local fragments = {}
+
 function p_hfudt.dissector(buf, pinfo, tree)
 
    -- make sure this isn't a STUN packet - those don't follow HFUDT format
@@ -235,6 +238,10 @@ function p_hfudt.dissector(buf, pinfo, tree)
 
     local payload_offset = 4
 
+    local message_number = 0
+    local message_part_number = 0
+    local message_position = 0
+
     -- if the message bit is set, handle the second word
     if message_bit == 1 then
       payload_offset = 12
@@ -242,7 +249,7 @@ function p_hfudt.dissector(buf, pinfo, tree)
       local second_word = buf(4, 4):le_uint()
 
       -- read message position from upper 2 bits
-      local message_position = bit32.rshift(second_word, 30)
+      message_position = bit32.rshift(second_word, 30)
       local position = subtree:add(f_message_position, message_position)
 
       if message_positions[message_position] ~= nil then
@@ -251,10 +258,12 @@ function p_hfudt.dissector(buf, pinfo, tree)
       end
 
       -- read message number from lower 30 bits
-      subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF))
+      message_number = bit32.band(second_word, 0x3FFFFFFF)
+      subtree:add(f_message_number, message_number)
 
       -- read the message part number
-      subtree:add(f_message_part_number, buf(8, 4):le_uint())
+      message_part_number = buf(8, 4):le_uint()
+      subtree:add(f_message_part_number, message_part_number)
     end
 
     -- read the type
@@ -283,25 +292,85 @@ function p_hfudt.dissector(buf, pinfo, tree)
       i = i + 16
     end
 
-    -- Domain packets
-    if packet_type_text == "DomainList" then
-      Dissector.get("hf-domain"):call(buf(i):tvb(), pinfo, tree)
+    local payload_to_dissect = nil
+
+    -- check if we have part of a message that we need to re-assemble
+    -- before it can be dissected
+    if obfuscation_bits == 0 then
+      if message_bit == 1 and message_position ~= 0 then
+        if fragments[message_number] == nil then
+          fragments[message_number] = {}
+        end
+
+        if fragments[message_number][message_part_number] == nil then
+          fragments[message_number][message_part_number] = {}
+        end
+
+        -- set the properties for this fragment
+        fragments[message_number][message_part_number] = {
+          payload = buf(i):bytes()
+        }
+
+        -- if this is the last part, set our maximum part number
+        if message_position == 1 then
+          fragments[message_number].last_part_number = message_part_number
+        end
+
+        -- if we have the last part
+        -- enumerate our parts for this message and see if everything is present
+        if fragments[message_number].last_part_number ~= nil then
+          local i = 0
+          local has_all = true
+
+          local finalMessage = ByteArray.new()
+          local message_complete = true
+
+          while i <= fragments[message_number].last_part_number do
+            if fragments[message_number][i] ~= nil then
+              finalMessage = finalMessage .. fragments[message_number][i].payload
+            else
+              -- missing this part, have to break until we have it
+              message_complete = false
+            end
+
+            i = i + 1
+          end
+
+          if message_complete then
+            debug("Message " .. message_number .. " is " .. finalMessage:len())
+            payload_to_dissect = ByteArray.tvb(finalMessage, message_number)
+          end
+        end
+
+      else
+        payload_to_dissect = buf(i):tvb()
+      end
     end
 
-    -- AvatarData or BulkAvatarDataPacket
-    if packet_type_text == "AvatarData" or packet_type_text == "BulkAvatarData" then
-      Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree)
+    if payload_to_dissect ~= nil then
+      -- Domain packets
+      if packet_type_text == "DomainList" then
+        Dissector.get("hf-domain"):call(payload_to_dissect, pinfo, tree)
+      end
+
+      -- AvatarData or BulkAvatarDataPacket
+      if packet_type_text == "AvatarData" or
+         packet_type_text == "BulkAvatarData" or
+         packet_type_text == "BulkAvatarTraits" then
+        Dissector.get("hf-avatar"):call(payload_to_dissect, pinfo, tree)
+      end
+
+      if packet_type_text == "EntityEdit" then
+        Dissector.get("hf-entity"):call(payload_to_dissect, pinfo, tree)
+      end
+
+      if packet_types[packet_type] == "MicrophoneAudioNoEcho" or
+         packet_types[packet_type] == "MicrophoneAudioWithEcho" or
+         packet_types[packet_type] == "SilentAudioFrame" then
+        Dissector.get("hf-audio"):call(payload_to_dissect, pinfo, tree)
+      end
     end
 
-    if packet_type_text == "EntityEdit" then
-      Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree)
-    end
-
-    if packet_types[packet_type] == "MicrophoneAudioNoEcho" or
-       packet_types[packet_type] == "MicrophoneAudioWithEcho" or
-       packet_types[packet_type] == "SilentAudioFrame" then
-      Dissector.get("hf-audio"):call(buf(i):tvb(), pinfo, tree)
-    end
   end
 
   -- return the size of the header
diff --git a/tools/dissectors/3-hf-avatar.lua b/tools/dissectors/3-hf-avatar.lua
index 0fa551c6f8..f4172b01cb 100644
--- a/tools/dissectors/3-hf-avatar.lua
+++ b/tools/dissectors/3-hf-avatar.lua
@@ -21,13 +21,31 @@ local f_avatar_data_default_rotations = ProtoField.string("hf_avatar.avatar_data
 local f_avatar_data_default_translations = ProtoField.string("hf_avatar.avatar_data_default_translations", "Valid Default")
 local f_avatar_data_sizes = ProtoField.string("hf_avatar.avatar_sizes", "Sizes")
 
+-- avatar trait data fields
+local f_avatar_trait_data = ProtoField.bytes("hf_avatar.avatar_trait_data", "Avatar Trait Data")
+
+local f_avatar_trait_id = ProtoField.guid("hf_avatar.trait_avatar_id", "Trait Avatar ID")
+local f_avatar_trait_type = ProtoField.int8("hf_avatar.trait_type", "Trait Type")
+local f_avatar_trait_version = ProtoField.int32("hf_avatar.trait_version", "Trait Version")
+local f_avatar_trait_instance_id = ProtoField.guid("hf_avatar.trait_instance_id", "Trait Instance ID")
+local f_avatar_trait_binary = ProtoField.bytes("hf_avatar.trait_binary", "Trait Binary Data")
 
 p_hf_avatar.fields = {
-  f_avatar_id, f_avatar_data_parent_id
+  f_avatar_id, f_avatar_data_parent_id,
+  f_avatar_trait_data,
+  f_avatar_trait_type, f_avatar_trait_id,
+  f_avatar_trait_version, f_avatar_trait_binary,
+  f_avatar_trait_instance_id
 }
 
 local packet_type_extractor = Field.new('hfudt.type')
 
+INSTANCED_TYPES = {
+  [1] = true
+}
+
+TOTAL_TRAIT_TYPES = 2
+
 function p_hf_avatar.dissector(buf, pinfo, tree)
   pinfo.cols.protocol = p_hf_avatar.name
 
@@ -52,7 +70,7 @@ function p_hf_avatar.dissector(buf, pinfo, tree)
 
     add_avatar_data_subtrees(avatar_data)
 
-  else
+  elseif packet_type == 11 then
     -- BulkAvatarData packet
     while i < buf:len() do
       -- avatar_id is first 16 bytes
@@ -65,9 +83,88 @@ function p_hf_avatar.dissector(buf, pinfo, tree)
 
       add_avatar_data_subtrees(avatar_data)
     end
+  elseif packet_type == 100 then
+    -- BulkAvatarTraits packet
+
+    -- loop over the packet until we're done reading it
+    while i < buf:len() do
+      i = i + read_avatar_trait_data(buf(i))
+    end
   end
 end
 
+function read_avatar_trait_data(buf)
+  local i = 0
+
+  -- avatar_id is first 16 bytes
+  local avatar_id_bytes = buf(i, 16)
+  i = i + 16
+
+  local traits_map = {}
+
+  -- loop over all of the traits for this avatar
+  while i < buf:len() do
+    -- pull out this trait type
+    local trait_type = buf(i, 1):le_int()
+    i = i + 1
+
+    debug("The trait type is " .. trait_type)
+
+    -- bail on our while if the trait type is null (-1)
+    if trait_type == -1 then break end
+
+    local trait_map = {}
+
+    -- pull out the trait version
+    trait_map.version = buf(i, 4):le_int()
+    i = i + 4
+
+    if INSTANCED_TYPES[trait_type] ~= nil then
+      -- pull out the trait instance ID
+      trait_map.instance_ID = buf(i, 16)
+      i = i + 16
+    end
+
+    -- pull out the trait binary size
+    trait_map.binary_size = buf(i, 2):le_int()
+    i = i + 2
+
+    -- unpack the binary data as long as this wasn't a delete
+    if trait_map.binary_size ~= -1 then
+      -- pull out the binary data for the trait
+      trait_map.binary_data = buf(i, trait_map.binary_size)
+      i = i + trait_map.binary_size
+    end
+
+    traits_map[trait_type] = trait_map
+  end
+
+  -- add a subtree including all of the data for this avatar
+  debug("Adding trait data of " .. i .. " bytes to the avatar tree")
+  local this_avatar_tree = avatar_subtree:add(f_avatar_trait_data, buf(0, i))
+
+  this_avatar_tree:add(f_avatar_trait_id, avatar_id_bytes)
+
+  -- enumerate the pulled traits and add them to the tree
+  local trait_type = 0
+  while trait_type < TOTAL_TRAIT_TYPES do
+    trait = traits_map[trait_type]
+
+    if trait ~= nil then
+      this_avatar_tree:add(f_avatar_trait_type, trait_type)
+      this_avatar_tree:add(f_avatar_trait_version, trait.version)
+      this_avatar_tree:add(f_avatar_trait_binary, trait.binary_data)
+
+      if trait.instance_ID ~= nil then
+        this_avatar_tree:add(f_avatar_trait_instance_id, trait.instance_ID)
+      end
+    end
+
+    trait_type = trait_type + 1
+  end
+
+  return i
+end
 
 function add_avatar_data_subtrees(avatar_data)
   if avatar_data["has_flags"] then