예제 #1
0
        /// <summary>
        /// See interface docs.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public byte[] Compress(BaseStationMessage message)
        {
            byte[] result = null;

            if (IsMeaningfulMessage(message))
            {
                using (var stream = new MemoryStream()) {
                    using (var writer = new BinaryWriter(stream)) {
                        stream.WriteByte(0);     // <-- reserve a byte for the buffer length
                        writer.Write((ushort)0); // <-- reserve a word for the checksum
                        stream.WriteByte((byte)(int)message.TransmissionType);
                        EncodeIcao(stream, message.Icao24);
                        long optionalFlagsOffset = stream.Position;
                        stream.WriteByte(0); // <-- reserve two bytes for the optional fields flags
                        stream.WriteByte(0); // <--    "
                        OptionalFields optionalFields = OptionalFields.None;
                        if (!String.IsNullOrEmpty(message.Callsign))
                        {
                            optionalFields |= OptionalFields.CallSign; EncodeString(stream, message.Callsign);
                        }
                        if (message.Altitude != null)
                        {
                            optionalFields |= OptionalFields.Altitude; EncodeFloatInt(stream, message.Altitude.Value);
                        }
                        if (message.GroundSpeed != null)
                        {
                            optionalFields |= OptionalFields.GroundSpeed; EncodeFloatShort(writer, message.GroundSpeed.Value);
                        }
                        if (message.Track != null)
                        {
                            optionalFields |= OptionalFields.Track; EncodeFloatShort(writer, message.Track.Value * 10f);
                        }
                        if (message.Latitude != null)
                        {
                            optionalFields |= OptionalFields.Latitude; EncodeFloat(writer, (float)message.Latitude.Value);
                        }
                        if (message.Longitude != null)
                        {
                            optionalFields |= OptionalFields.Longitude; EncodeFloat(writer, (float)message.Longitude.Value);
                        }
                        if (message.VerticalRate != null)
                        {
                            optionalFields |= OptionalFields.VerticalRate; EncodeFloatShort(writer, message.VerticalRate.Value);
                        }
                        if (message.Squawk != null)
                        {
                            optionalFields |= OptionalFields.Squawk; EncodeShort(writer, (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, message.Squawk.Value)));
                        }

                        CompressedFlags flags   = CompressedFlags.None;
                        bool            hasFlag = false;
                        if (message.SquawkHasChanged != null)
                        {
                            optionalFields |= OptionalFields.SquawkHasChanged; hasFlag = true; if (message.SquawkHasChanged.Value)
                            {
                                flags |= CompressedFlags.SquawkHasChanged;
                            }
                        }
                        if (message.Emergency != null)
                        {
                            optionalFields |= OptionalFields.Emergency; hasFlag = true; if (message.Emergency.Value)
                            {
                                flags |= CompressedFlags.Emergency;
                            }
                        }
                        if (message.IdentActive != null)
                        {
                            optionalFields |= OptionalFields.IdentActive; hasFlag = true; if (message.IdentActive.Value)
                            {
                                flags |= CompressedFlags.IdentActive;
                            }
                        }
                        if (message.OnGround != null)
                        {
                            optionalFields |= OptionalFields.OnGround; hasFlag = true; if (message.OnGround.Value)
                            {
                                flags |= CompressedFlags.OnGround;
                            }
                        }
                        if (hasFlag)
                        {
                            stream.WriteByte((byte)flags);
                        }

                        stream.Seek(optionalFlagsOffset, SeekOrigin.Begin);
                        EncodeShort(writer, (short)optionalFields);
                    }

                    result = stream.ToArray();
                    if (result.Length != 0)
                    {
                        result[0] = (byte)result.Length;
                        var checksum = CalculateChecksum(result);
                        result[1] = (byte)(checksum & 0x00ff);
                        result[2] = (byte)((checksum & 0xff00) >> 8);
                    }
                }
            }

            return(result ?? new byte[0]);
        }
        protected override void DeSerialise(byte[] buf, ref int o, int length)
        {
            RegionHandle = new RegionHandle(BinarySerializer.DeSerializeUInt64_Le(buf, ref o, length));
            TimeDilation = BinarySerializer.DeSerializeUInt16_Le(buf, ref o, length);

            string logMessage = $"ObjectUpdateCompressed: RegionHandle={RegionHandle}, TimeDilation={TimeDilation}";
            int    nObjects   = buf[o++];

            for (int i = 0; i < nObjects; i++)
            {
                UInt32 len;
                ObjectUpdateMessage.ObjectData data = new ObjectUpdateMessage.ObjectData();
                Objects.Add(data);

                data.UpdateFlags = (ObjectUpdateFlags)BinarySerializer.DeSerializeUInt32_Le(buf, ref o, length);

                int    compressedLength = BinarySerializer.DeSerializeUInt16_Le(buf, ref o, length);
                byte[] compressedData   = new byte[compressedLength];
                Array.Copy(buf, o, compressedData, 0, compressedLength);
                o += compressedLength;
                int compressedOffset = 0;

                logMessage      += $"\n  Object {i}: UpdateFlags={data.UpdateFlags}, Data({compressedData.Length})={BitConverter.ToString(compressedData)}";
                data.FullId      = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength);
                data.LocalId     = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);
                data.PCode       = (PCode)compressedData[compressedOffset++];
                data.State       = compressedData[compressedOffset++];
                data.Crc         = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);
                data.Material    = (MaterialType)compressedData[compressedOffset++];
                data.ClickAction = (ClickAction)compressedData[compressedOffset++];
                data.Scale       = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength);
                data.Position    = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength);
                data.Rotation    = BinarySerializer.DeSerializeQuaternion(compressedData, ref compressedOffset, compressedLength);
                CompressedFlags compressedFlags = (CompressedFlags)BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);

                data.OwnerId = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength);

                logMessage += $"\n    FullId={data.FullId}, LocalId={data.LocalId}, PCode={data.PCode}, State={data.State}, Crc={data.Crc}, Material={data.Material}, ClickAction={data.ClickAction}, Scale={data.Scale}, Position={data.Position}, Rotation={data.Rotation}, CompressedFlags=({compressedFlags})";

                if ((compressedFlags & CompressedFlags.HasAngularVelocity) != 0)
                {
                    data.AngularVelocity = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength);
                    logMessage          += $", AngularVelocity={data.AngularVelocity}";
                }

                data.ParentId = (compressedFlags & CompressedFlags.HasParent) != 0 ? BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength) : (uint)0;
                logMessage   += $", ParentId={data.ParentId}";

                if ((compressedFlags & CompressedFlags.Tree) != 0)
                {
                    byte treeSpecies = compressedData[compressedOffset++];
                    logMessage += $", TreeSpecies={treeSpecies}";
                }

                if ((compressedFlags & CompressedFlags.ScratchPad) != 0)
                {
                    len = compressedData[compressedOffset++];
                    compressedOffset += (int)len; // TODO: These offsets and length should all be UInt32
                    logMessage       += $", Scratchpad({len})";
                }

                if ((compressedFlags & CompressedFlags.HasText) != 0)
                {
                    data.Text       = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0);
                    data.TextColour = BinarySerializer.DeSerializeColor(compressedData, ref compressedOffset, compressedLength);
                    logMessage     += $", Text={data.Text}, TextColour={data.TextColour}";
                }

                if ((compressedFlags & CompressedFlags.MediaURL) != 0)
                {
                    data.MediaUrl = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0);
                    logMessage   += $", MediaUrl={data.MediaUrl}";
                }

                if ((compressedFlags & CompressedFlags.HasParticles) != 0)
                {
                    // TODO: Parse the particle system data. OpenMetaverse says that this is a BitPack of 86 bytes.
                    len = 86;
                    compressedOffset += (int)len;
                    logMessage       += $", ParticleSystem({len})";
                }

                data.ExtraParameters = BinarySerializer.DeSerializeExtraParameters(compressedData, ref compressedOffset, compressedOffset + compressedLength);

                if ((compressedFlags & CompressedFlags.HasSound) != 0)
                {
                    data.SoundId    = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength);
                    data.Gain       = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);
                    data.SoundFlags = (SoundFlags)compressedData[compressedOffset++];
                    data.Radius     = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, compressedLength);
                    logMessage     += $", SoundId={data.SoundId}, Gain={data.Gain}, SoundFlags={data.SoundFlags}, Radius={data.Radius}";
                }

                if ((compressedFlags & CompressedFlags.HasNameValues) != 0)
                {
                    data.NameValue = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0);
                    logMessage    += $", NameValue={data.NameValue}";
                }

                data.PathCurve        = (PathType)compressedData[compressedOffset++];
                data.PathBegin        = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA;
                data.PathEnd          = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA;
                data.PathScaleX       = compressedData[compressedOffset++] * SCALE_QUANTA;
                data.PathScaleY       = compressedData[compressedOffset++] * SCALE_QUANTA;
                data.PathShearX       = compressedData[compressedOffset++] * SHEAR_QUANTA;
                data.PathShearY       = compressedData[compressedOffset++] * SHEAR_QUANTA;
                data.PathTwist        = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA;
                data.PathTwistBegin   = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA;
                data.PathRadiusOffset = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA;
                data.PathTaperX       = (sbyte)compressedData[compressedOffset++] * TAPER_QUANTA;
                data.PathTaperY       = (sbyte)compressedData[compressedOffset++] * TAPER_QUANTA;
                data.PathRevolutions  = compressedData[compressedOffset++] * REV_QUANTA;
                data.PathSkew         = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA;

                data.ProfileCurve  = (ProfileType)compressedData[compressedOffset++];
                data.ProfileBegin  = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA;
                data.ProfileEnd    = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA;
                data.ProfileHollow = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * HOLLOW_QUANTA;

                data.TextureEntry = BinarySerializer.DeSerializeTextureEntry(compressedData, ref compressedOffset, compressedLength, true);
                logMessage       += $", TextureEntry={data.TextureEntry}";

                if ((compressedFlags & CompressedFlags.TextureAnimation) != 0)
                {
                    data.TextureAnimation = BinarySerializer.DeSerializeTextureAnimation(compressedData, ref compressedOffset, compressedLength);
                    logMessage           += ", TextureAnimation";
                }

                data.IsAttachment = (compressedFlags & CompressedFlags.HasNameValues) != 0 && data.ParentId != 0;
            }
            //Logger.LogDebug("ObjectUpdateCompressedMessage.DeSerialise", logMessage);
        }
예제 #3
0
        /// <summary>
        /// See interface docs.
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public BaseStationMessage Decompress(byte[] buffer)
        {
            BaseStationMessage result = null;

            if (buffer.Length > 0 && (int)buffer[0] == buffer.Length)
            {
                using (var stream = new MemoryStream(buffer)) {
                    using (var reader = new BinaryReader(stream)) {
                        result = new BaseStationMessage();
                        stream.ReadByte();                     // <-- length of buffer, we can skip this

                        ushort checksum = reader.ReadUInt16(); // the checksum is skipped as well - caller should ensure it's a valid message before decompression

                        result.MessageType      = BaseStationMessageType.Transmission;
                        result.StatusCode       = BaseStationStatusCode.None;
                        result.TransmissionType = (BaseStationTransmissionType)stream.ReadByte();
                        result.Icao24           = DecodeIcao(stream);
                        result.MessageGenerated = result.MessageLogged = DateTime.Now;

                        OptionalFields optionalFields = (OptionalFields)DecodeShort(reader);

                        if ((optionalFields & OptionalFields.CallSign) != 0)
                        {
                            result.Callsign = DecodeString(stream);
                        }
                        if ((optionalFields & OptionalFields.Altitude) != 0)
                        {
                            result.Altitude = (int)DecodeFloatInt(stream);
                        }
                        if ((optionalFields & OptionalFields.GroundSpeed) != 0)
                        {
                            result.GroundSpeed = (int)DecodeFloatShort(reader);
                        }
                        if ((optionalFields & OptionalFields.Track) != 0)
                        {
                            result.Track = DecodeFloatShort(reader) / 10f;
                        }
                        if ((optionalFields & OptionalFields.Latitude) != 0)
                        {
                            result.Latitude = DecodeFloat(reader);
                        }
                        if ((optionalFields & OptionalFields.Longitude) != 0)
                        {
                            result.Longitude = DecodeFloat(reader);
                        }
                        if ((optionalFields & OptionalFields.VerticalRate) != 0)
                        {
                            result.VerticalRate = (int)DecodeFloatShort(reader);
                        }
                        if ((optionalFields & OptionalFields.Squawk) != 0)
                        {
                            result.Squawk = DecodeShort(reader);
                        }

                        bool hasSquawkHasChanged = (optionalFields & OptionalFields.SquawkHasChanged) != 0;
                        bool hasEmergency        = (optionalFields & OptionalFields.Emergency) != 0;
                        bool hasIdentActive      = (optionalFields & OptionalFields.IdentActive) != 0;
                        bool hasOnGround         = (optionalFields & OptionalFields.OnGround) != 0;

                        if (hasSquawkHasChanged || hasEmergency || hasIdentActive || hasOnGround)
                        {
                            CompressedFlags flags = (CompressedFlags)stream.ReadByte();
                            if (hasSquawkHasChanged)
                            {
                                result.SquawkHasChanged = (flags & CompressedFlags.SquawkHasChanged) != 0;
                            }
                            if (hasEmergency)
                            {
                                result.Emergency = (flags & CompressedFlags.Emergency) != 0;
                            }
                            if (hasIdentActive)
                            {
                                result.IdentActive = (flags & CompressedFlags.IdentActive) != 0;
                            }
                            if (hasOnGround)
                            {
                                result.OnGround = (flags & CompressedFlags.OnGround) != 0;
                            }
                        }
                    }
                }
            }

            return(result);
        }
예제 #4
0
    protected override void DeSerialise(byte[] buf, ref int o, int length)
    {
        RegionHandle = new RegionHandle(BinarySerializer.DeSerializeUInt64_Le(buf, ref o, length));
        TimeDilation = BinarySerializer.DeSerializeUInt16_Le(buf, ref o, length);

        string logMessage = $"ObjectUpdateCompressed: RegionHandle={RegionHandle}, TimeDilation={TimeDilation}";
        int    nObjects   = buf[o++];

        for (int i = 0; i < nObjects; i++)
        {
            UInt32 len;
            ObjectUpdateMessage.ObjectData data = new ObjectUpdateMessage.ObjectData();
            Objects.Add(data);

            data.UpdateFlags = (ObjectUpdateFlags)BinarySerializer.DeSerializeUInt32_Le(buf, ref o, length);

            int    compressedLength = BinarySerializer.DeSerializeUInt16_Le(buf, ref o, length);
            byte[] compressedData   = new byte[compressedLength];
            Array.Copy(buf, o, compressedData, 0, compressedLength);
            o += compressedLength;
            int compressedOffset = 0;

            logMessage      += $"\n  Object {i}: UpdateFlags={data.UpdateFlags}, Data({compressedData.Length})={BitConverter.ToString(compressedData)}";
            data.FullId      = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength);
            data.LocalId     = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);
            data.PCode       = (PCode)compressedData[compressedOffset++];
            data.State       = compressedData[compressedOffset++];
            data.Crc         = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);
            data.Material    = (MaterialType)compressedData[compressedOffset++];
            data.ClickAction = (ClickAction)compressedData[compressedOffset++];
            data.Scale       = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength);
            data.Position    = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength);
            data.Rotation    = BinarySerializer.DeSerializeQuaternion(compressedData, ref compressedOffset, compressedLength);
            CompressedFlags compressedFlags = (CompressedFlags)BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);

            data.OwnerId = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength);

            logMessage += $"\n    FullId={data.FullId}, LocalId={data.LocalId}, PCode={data.PCode}, State={data.State}, Crc={data.Crc}, Material={data.Material}, ClickAction={data.ClickAction}, Scale={data.Scale}, Position={data.Position}, Rotation={data.Rotation}, CompressedFlags=({compressedFlags})";

            if ((compressedFlags & CompressedFlags.HasAngularVelocity) != 0)
            {
                data.AngularVelocity = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength);
                logMessage          += $", AngularVelocity={data.AngularVelocity}";
            }

            data.ParentId = (compressedFlags & CompressedFlags.HasParent) != 0 ? BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength) : (uint)0;
            logMessage   += $", ParentId={data.ParentId}";

            if ((compressedFlags & CompressedFlags.Tree) != 0)
            {
                byte treeSpecies = compressedData[compressedOffset++];
                logMessage += $", TreeSpecies={treeSpecies}";
            }

            if ((compressedFlags & CompressedFlags.ScratchPad) != 0)
            {
                len = compressedData[compressedOffset++];
                compressedOffset += (int)len; // TODO: These offsets and length should all be UInt32
                logMessage       += $", Scratchpad({len})";
            }

            if ((compressedFlags & CompressedFlags.HasText) != 0)
            {
                data.Text       = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0);
                data.TextColour = BinarySerializer.DeSerializeColor(compressedData, ref compressedOffset, compressedLength);
                logMessage     += $", Text={data.Text}, TextColour={data.TextColour}";
            }

            if ((compressedFlags & CompressedFlags.MediaURL) != 0)
            {
                data.MediaUrl = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0);
                logMessage   += $", MediaUrl={data.MediaUrl}";
            }

            if ((compressedFlags & CompressedFlags.HasParticles) != 0)
            {
                len         = 86;
                logMessage += $", ParticleSystem({len})";
            }

            byte nExtraParameters = compressedData[compressedOffset++];
            for (int j = 0; j < nExtraParameters; j++)
            {
                if (j == 0)
                {
                    logMessage += ", ExtraParameters=(";
                }
                ExtraParamType type = (ExtraParamType)BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, compressedLength);
                len = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);
                switch (type)
                {
                case ExtraParamType.Flexible:
                    break;

                case ExtraParamType.Light:
                    break;

                case ExtraParamType.Sculpt:
                    break;

                case ExtraParamType.LightImage:
                    break;

                case ExtraParamType.Mesh:
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }
                logMessage += $"{type}, ";

                compressedOffset += (int)len; // TODO: These offsets and length should all be UInt32

                if (j == nExtraParameters - 1)
                {
                    logMessage += ")";
                }
            }

            if ((compressedFlags & CompressedFlags.HasSound) != 0)
            {
                data.SoundId    = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength);
                data.Gain       = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength);
                data.SoundFlags = (SoundFlags)compressedData[compressedOffset++];
                data.Radius     = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, compressedLength);
                logMessage     += $", SoundId={data.SoundId}, Gain={data.Gain}, SoundFlags={data.SoundFlags}, Radius={data.Radius}";
            }

            if ((compressedFlags & CompressedFlags.HasNameValues) != 0)
            {
                data.NameValue = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0);
                logMessage    += $", NameValue={data.NameValue}";
            }

            data.PathCurve        = (PathType)compressedData[compressedOffset++];
            data.PathBegin        = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA;
            data.PathEnd          = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA;
            data.PathScaleX       = compressedData[compressedOffset++] * SCALE_QUANTA;
            data.PathScaleY       = compressedData[compressedOffset++] * SCALE_QUANTA;
            data.PathShearX       = compressedData[compressedOffset++] * SHEAR_QUANTA;
            data.PathShearY       = compressedData[compressedOffset++] * SHEAR_QUANTA;
            data.PathTwist        = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA;
            data.PathTwistBegin   = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA;
            data.PathRadiusOffset = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA;
            data.PathTaperX       = (sbyte)compressedData[compressedOffset++] * TAPER_QUANTA;
            data.PathTaperY       = (sbyte)compressedData[compressedOffset++] * TAPER_QUANTA;
            data.PathRevolutions  = compressedData[compressedOffset++] * REV_QUANTA;
            data.PathSkew         = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA;

            data.ProfileCurve  = (ProfileType)compressedData[compressedOffset++];
            data.ProfileBegin  = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA;
            data.ProfileEnd    = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA;
            data.ProfileHollow = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * HOLLOW_QUANTA;

            UInt32 textureEntryLength = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, length);
            logMessage       += $", textures({textureEntryLength})";
            compressedOffset += (int)textureEntryLength;

            if ((compressedFlags & CompressedFlags.TextureAnimation) != 0)
            {
                TextureAnimation textureAnimation = new TextureAnimation()
                {
                    Mode   = (TextureAnimationMode)compressedData[compressedOffset++],
                    Face   = compressedData[compressedOffset++],
                    SizeX  = compressedData[compressedOffset++],
                    SizeY  = compressedData[compressedOffset++],
                    Start  = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, length),
                    Length = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, length),
                    Rate   = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, length)
                };
                logMessage += ", TextureAnimation";
            }

            data.IsAttachment = (compressedFlags & CompressedFlags.HasNameValues) != 0 && data.ParentId != 0;
        }
        //Logger.LogDebug(logMessage);
    }