Example #1
0
        private void WriteRecordingHeader(BinaryWriter writer)
        {
            // First write a server info message to define the coordinate frame and other server details.
            PacketBuffer   packet        = new PacketBuffer();
            PacketHeader   header        = PacketHeader.Default;
            ControlMessage frameCountMsg = new ControlMessage();

            header.RoutingID = (ushort)RoutingID.ServerInfo;

            packet.Reset();
            packet.WriteHeader(header);
            _serverInfo.Write(packet);
            packet.FinalisePacket();
            packet.ExportTo(writer);

            // Next write a placeholder control packet to define the total number of frames.
            header.RoutingID     = (ushort)RoutingID.Control;
            header.MessageID     = (ushort)ControlMessageID.FrameCount;
            header.PayloadSize   = 0;
            header.PayloadOffset = 0;

            frameCountMsg.ControlFlags = 0;
            frameCountMsg.Value32      = 0; // Placeholder. Frame count is currently unknown.
            frameCountMsg.Value64      = 0;
            packet.Reset();
            packet.WriteHeader(header);
            frameCountMsg.Write(packet);
            packet.FinalisePacket();
            packet.ExportTo(writer);
        }
Example #2
0
        /// <summary>
        /// Serialises a list of objects to <paramref name="writer"/>
        /// </summary>
        /// <param name="writer">The writer to serialise to.</param>
        /// <param name="objects">The object to write.</param>
        /// <param name="processedCount">Number of objects processed.</param>
        /// <returns>An error code on failure.</returns>
        /// <remarks>
        /// Default serialisation uses the following logic on each object:
        /// <list type="bullet">
        /// <item>Call <see cref="CreateSerialisationShape(ShapeComponent)"/> to
        ///       create a temporary shape to match the unity object</item>
        /// <item>Call <see cref="Shapes.Shape.WriteCreate(PacketBuffer)"/> to generate the creation message
        ///       and serialise the packet.</item>
        /// <item>For complex shapes, call <see cref="Shapes.Shape.WriteData(PacketBuffer, ref uint)"/> as required
        ///       and serialise the packets.</item>
        /// </list>
        ///
        /// Using the <see cref="Shapes.Shape"/> classes ensures serialisation is consistent with the server code
        /// and reduces the code maintenance to one code path.
        /// </remarks>
        protected virtual Error SerialiseObjects(BinaryWriter writer, IEnumerator <GameObject> objects, ref uint processedCount)
        {
            // Serialise transient objects.
            PacketBuffer packet = new PacketBuffer();
            Error        err;

            Shapes.Shape tempShape  = null;
            uint         dataMarker = 0;
            int          dataResult = 0;

            Debug.Assert(tempShape != null && tempShape.RoutingID == RoutingID);

            processedCount = 0;
            while (objects.MoveNext())
            {
                ++processedCount;
                GameObject obj = objects.Current;
                tempShape = CreateSerialisationShape(obj.GetComponent <ShapeComponent>());
                if (tempShape != null)
                {
                    tempShape.WriteCreate(packet);
                    packet.FinalisePacket();
                    packet.ExportTo(writer);

                    if (tempShape.IsComplex)
                    {
                        dataResult = 1;
                        dataMarker = 0;
                        while (dataResult > 0)
                        {
                            dataResult = tempShape.WriteData(packet, ref dataMarker);
                            packet.FinalisePacket();
                            packet.ExportTo(writer);
                        }

                        if (dataResult < 0)
                        {
                            return(new Error(ErrorCode.SerialisationFailure));
                        }

                        // Post serialisation extensions.
                        err = PostSerialiseCreateObject(packet, writer, obj.GetComponent <ShapeComponent>());
                        if (err.Failed)
                        {
                            return(err);
                        }
                    }
                }
                else
                {
                    return(new Error(ErrorCode.SerialisationFailure));
                }
            }

            return(new Error());
        }
Example #3
0
        /// <summary>
        /// Serialise messages to generate <paramref name="mesh"/>.
        /// </summary>
        /// <param name="mesh">The mesh of interest.</param>
        /// <param name="packet">Packet buffer to compose messages in</param>
        /// <param name="writer">Writer to export completed message packets to.</param>
        /// <returns></returns>
        /// <remarks>
        /// Writes:
        /// <list type="bullet">
        /// <item><see cref="MeshCreateMessage"/></item>
        /// <item><see cref="MeshComponentMessage"/> for each component type from
        /// <see cref="MeshMessageType"/></item>
        /// <item><see cref="MeshFinaliseMessage"/> only when <paramref name="mesh"/> is already
        /// finalised.</item>
        /// </list>
        /// </remarks>
        protected Error Serialise(MeshDetails mesh, PacketBuffer packet, BinaryWriter writer)
        {
            // First write a create message.
            MeshCreateMessage msg = new MeshCreateMessage();

            packet.Reset((ushort)RoutingID, (ushort)MeshCreateMessage.MessageID);

            msg.MeshID      = mesh.ID;
            msg.VertexCount = (uint)mesh.Builder.VertexCount;
            msg.IndexCount  = (uint)(mesh.Builder.ExplicitIndices ? mesh.Builder.IndexCount : 0);
            msg.DrawType    = mesh.DrawType;

            msg.Attributes.X = mesh.LocalPosition.x;
            msg.Attributes.Y = mesh.LocalPosition.y;
            msg.Attributes.Z = mesh.LocalPosition.z;

            msg.Attributes.RotationX = mesh.LocalRotation.x;
            msg.Attributes.RotationY = mesh.LocalRotation.y;
            msg.Attributes.RotationZ = mesh.LocalRotation.z;
            msg.Attributes.RotationW = mesh.LocalRotation.w;

            msg.Attributes.ScaleX = mesh.LocalScale.x;
            msg.Attributes.ScaleY = mesh.LocalScale.y;
            msg.Attributes.ScaleZ = mesh.LocalScale.z;

            msg.Attributes.Colour = ShapeComponent.ConvertColour(mesh.Tint);

            msg.Write(packet);
            if (!packet.FinalisePacket())
            {
                return(new Error(ErrorCode.SerialisationFailure));
            }
            packet.ExportTo(writer);

            // Now use the MeshResource methods to complete serialisation.
            MeshSerialiser   serialiser = new MeshSerialiser(mesh);
            TransferProgress prog       = new TransferProgress();

            prog.Reset();
            while (!prog.Complete)
            {
                serialiser.Transfer(packet, 0, ref prog);
                if (!packet.FinalisePacket())
                {
                    return(new Error(ErrorCode.SerialisationFailure));
                }
                packet.ExportTo(writer);
            }

            return(new Error());
        }
        /// <summary>
        /// Request the additional of a keyframe at the given frame and post the associated control message.
        /// </summary>
        /// <param name="frameNumber">The keyframe frame number.</param>
        /// <param name="streamOffset">The stream position/bytes read (total) on this frame.</param>
        private void RequestKeyframe(uint frameNumber, uint offsetFrameNumber, long streamOffset)
        {
            if (HaveKeyframe(frameNumber) || !AllowKeyframes)
            {
                return;
            }

            // Add a placeholder.
            Keyframe keyframe = new Keyframe();

            keyframe.FrameNumber       = frameNumber;
            keyframe.OffsetFrameNumber = offsetFrameNumber;
            keyframe.StreamOffset      = streamOffset;
            lock (_keyframes)
            {
                _keyframes.Add(keyframe);
            }

            PacketBuffer   packet  = new PacketBuffer(PacketHeader.Size + 32);
            ControlMessage message = new ControlMessage();

            message.ControlFlags = 0;
            message.Value32      = frameNumber;
            message.Value64      = 0;

            packet.Reset((ushort)RoutingID.Control, (ushort)ControlMessageID.Keyframe);
            message.Write(packet);

            if (packet.FinalisePacket())
            {
                _packetQueue.Enqueue(packet);
            }
        }
Example #5
0
        /// <summary>
        /// Helper function to write a packet to a stream with an optional <see cref="T:CollatedPacketEncoder"/>.
        /// </summary>
        /// <param name="stream">Stream to write <paramref name="packet"/> to.</param>
        /// <param name="encoder">Optional collated packet encoder. May be null for no collation.</param>
        /// <param name="packet">The packet data to write.</param>
        /// <remarks>
        /// The <paramref name="encoder"/> is flushed if required.
        /// </remarks>
        static void WritePacket(Stream stream, CollatedPacketEncoder encoder, PacketBuffer packet)
        {
            if (packet.Status != PacketBufferStatus.Complete)
            {
                if (!packet.FinalisePacket())
                {
                    throw new IOException("Packet finalisation failed.");
                }
            }

            if (encoder == null)
            {
                stream.Write(packet.Data, 0, packet.Count);
            }
            else
            {
                if (encoder.Add(packet) == -1)
                {
                    // Failed to add. Try flushing the packet first.
                    Flush(stream, encoder);
                    if (encoder.Add(packet) == -1)
                    {
                        // Failed after flush. Raise exception.
                        throw new IOException("Failed to collate data packet after flush.");
                    }
                }
            }
        }
Example #6
0
        private static bool Serialise(Text2DManager textManager, PacketBuffer packet, BinaryWriter writer, ref uint count)
        {
            Shapes.Text2D shape          = new Shapes.Text2D(null);
            uint          progressMarker = 0;
            int           dataResult     = 1;
            bool          shapeOk        = true;

            foreach (TextEntry entry in textManager.Entries)
            {
                ++count;
                shape.ID       = entry.ID;
                shape.Category = entry.Category;
                shape.Flags    = entry.ObjectFlags;
                shape.SetPosition(entry.Position.x, entry.Position.y, entry.Position.z);
                shape.Text = entry.Text;
                shape.WriteCreate(packet);

                shapeOk = packet.FinalisePacket();
                if (shapeOk)
                {
                    packet.ExportTo(writer);

                    if (shape.IsComplex)
                    {
                        dataResult = 1;
                        while (dataResult > 0 && shapeOk)
                        {
                            shape.WriteData(packet, ref progressMarker);
                            shapeOk = packet.FinalisePacket();
                            if (shapeOk)
                            {
                                packet.ExportTo(writer);
                            }
                        }

                        shapeOk = dataResult == 0;
                    }
                }

                if (!shapeOk)
                {
                    return(false);
                }
            }

            return(true);
        }
        private static bool Serialise(Text2DManager textManager, PacketBuffer packet, BinaryWriter writer, ref uint count)
        {
            Shapes.Text2D shape          = new Shapes.Text2D(null);
            uint          progressMarker = 0;
            int           dataResult     = 1;
            bool          shapeOk        = true;

            foreach (TextEntry entry in textManager.Entries)
            {
                ++count;
                ConvertToShape(shape, entry);
                shape.WriteCreate(packet);

                shapeOk = packet.FinalisePacket();
                if (shapeOk)
                {
                    packet.ExportTo(writer);

                    if (shape.IsComplex)
                    {
                        dataResult = 1;
                        while (dataResult > 0 && shapeOk)
                        {
                            shape.WriteData(packet, ref progressMarker);
                            shapeOk = packet.FinalisePacket();
                            if (shapeOk)
                            {
                                packet.ExportTo(writer);
                            }
                        }

                        shapeOk = dataResult == 0;
                    }
                }

                if (!shapeOk)
                {
                    return(false);
                }
            }

            return(true);
        }
Example #8
0
        /// <summary>
        /// Helper for sending an arbitrary message via <paramref name="connection"/>.
        /// </summary>
        /// <param name="connection">The client or clients to send to.</param>
        /// <param name="routingId">The message <see cref="RoutingID"/>.</param>
        /// <param name="messageId">The message ID associated with the <paramref name="routingId"/>.</param>
        /// <param name="message">The message to send.</param>
        /// <returns>The number of bytes written, or -1 on failure.</returns>
        public static int SendMessage(IServer connection, ushort routingId, ushort messageId, IMessage message)
        {
            PacketBuffer buffer = new PacketBuffer(1024);

            buffer.Reset(routingId, messageId);
            if (message.Write(buffer) && buffer.FinalisePacket())
            {
                return(connection.Send(buffer));
            }

            return(-1);
        }
        /// <summary>
        /// Creates a frame flush packet targeting the given frame number.
        /// </summary>
        /// <param name="frameNumber">Optional frame number to flush with (zero for none).</param>
        /// <returns></returns>
        /// <remarks>
        /// The returned packet will ensure visualisation of a frame and that the current frame is set
        /// to <paramref name="frameNumber"/>. Transient objects are preserved.
        /// </remarks>
        PacketBuffer CreateFrameFlushPacket(uint frameNumber)
        {
            PacketBuffer   packet  = new PacketBuffer();
            ControlMessage message = new ControlMessage();

            message.ControlFlags = (uint)EndFrameFlag.Persist;
            message.Value32      = 0;
            message.Value64      = frameNumber;

            packet.Reset((ushort)RoutingID.Control, (ushort)ControlMessageID.ForceFrameFlush);
            message.Write(packet);
            packet.FinalisePacket();
            return(packet);
        }
Example #10
0
        /// <summary>
        /// Create a reset packet.
        /// </summary>
        /// <param name="forFrameNumber">Used to set <c>Value32</c> in the <see cref="ControlMessage"/>.</param>
        /// This identifies the frame we are resetting to.
        protected PacketBuffer BuildResetPacket(uint forFrameNumber = 0)
        {
            PacketBuffer   packet  = new PacketBuffer(PacketHeader.Size + 32);
            ControlMessage message = new ControlMessage();

            // Write reset
            message.ControlFlags = 0;
            message.Value32      = forFrameNumber;
            message.Value64      = 0;

            packet.Reset((ushort)RoutingID.Control, (ushort)ControlMessageID.Reset);
            message.Write(packet);
            packet.FinalisePacket();

            return(packet);
        }
        /// <summary>
        /// Extracts the next packet from the collated buffer.
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// For collated packets, this decodes and decompresses the next packet. For non-collated
        /// packets, this simply returns the packet given to <see cref="PacketBuffer"/>, then
        /// <code>null</code> on the following call.
        /// </remarks>
        public PacketBuffer Next()
        {
            PacketBuffer next = null;

            if (_packetStream != null)
            {
                PacketHeader header = new PacketHeader();
                // Read the header via the network reader to do the network byte order swap as required.
                if (_targetBytes - _decodedBytes > PacketHeader.Size && header.Read(_streamReader))
                {
                    _decodedBytes += (uint)PacketHeader.Size;
                    next           = new PacketBuffer(header.PacketSize);
                    next.WriteHeader(header);
                    // Read payload data as it to preserve network byte order.
                    next.Emplace(_packetStream, header.DataSize);
                    next.FinalisePacket(true);
                    _decodedBytes += (uint)header.DataSize;
                    // Skip CRC if present.
                    if ((header.Flags & (byte)PacketFlag.NoCrc) == 0)
                    {
                        // Skip CRC.
                        _streamReader.ReadUInt16();
                        _decodedBytes += 2;
                    }
                }
            }
            else
            {
                // Not compressed.
                next = _packet;
                SetPacket(null);
            }

            // Check for failure or completion.
            if (next == null || _decodedBytes >= _targetBytes)
            {
                SetPacket(null);
            }

            return(next);
        }
        /// <summary>
        /// Serialises the currently active objects in for playback from file.
        /// </summary>
        /// <param name="writer">The write to serialise to.</param>
        /// <param name="info">Statistics</param>
        /// <returns>An error code on failure.</returns>
        public override Error Serialise(BinaryWriter writer, ref SerialiseInfo info)
        {
            Error err = new Error();

            info.PersistentCount = info.TransientCount = 0;
            if (_cameras.Count > 0)
            {
                PacketBuffer  packet = new PacketBuffer(256);
                CameraMessage msg    = new CameraMessage();
                Vector3       v      = Vector3.zero;
                msg.Reserved1 = 0;
                msg.Reserved2 = 0;
                foreach (CameraInfo camera in _cameras.Values)
                {
                    ++info.PersistentCount;
                    msg.CameraID = camera.ID;
                    v            = FrameTransform.UnityToRemote(camera.transform.localPosition, ServerInfo.CoordinateFrame);
                    msg.X        = v.x;
                    msg.Y        = v.y;
                    msg.Z        = v.z;
                    v            = FrameTransform.UnityToRemote(camera.transform.forward, ServerInfo.CoordinateFrame);
                    msg.DirX     = v.x;
                    msg.DirY     = v.y;
                    msg.DirZ     = v.z;
                    v            = FrameTransform.UnityToRemote(camera.transform.up, ServerInfo.CoordinateFrame);
                    msg.UpX      = v.x;
                    msg.UpY      = v.y;
                    msg.UpZ      = v.z;
                    msg.Near     = camera.Near;
                    msg.Far      = camera.Far;
                    msg.FOV      = camera.FOV;
                    packet.Reset(RoutingID, 0);
                    msg.Write(packet);
                    packet.FinalisePacket();
                    packet.ExportTo(writer);
                }
            }

            return(err);
        }
Example #13
0
        /// <summary>
        /// Write a <see cref="MeshComponentMessage"/> to serialise <c>Vector3</c> data.
        /// </summary>
        /// <param name="meshID">Mesh resource ID we are serialising for.</param>
        /// <param name="type">The mesh component type; e.g., <see cref="MeshMessageType.Vertex"/>.</param>
        /// <param name="data">The <c>Vector3</c> data array.</param>
        /// <param name="packet">Packet buffer to compose messages in</param>
        /// <param name="writer">Writer to export completed message packets to.</param>
        /// <param name="blockSize">The maximum number of elements per message.</param>
        /// <remarks>
        /// Writes multiple messages to <paramref name="writer"/> as required to ensure all
        /// <paramref name="data"/> are written.
        /// </remarks>
        protected void WriteMeshComponent(uint meshID, MeshMessageType type, Vector3[] data,
                                          PacketBuffer packet, BinaryWriter writer, uint blockSize)
        {
            if (data == null || data.Length == 0)
            {
                return;
            }

            MeshComponentMessage cmsg = new MeshComponentMessage();

            cmsg.MeshID   = meshID;
            cmsg.Offset   = 0;
            cmsg.Reserved = 0;
            cmsg.Count    = 0;

            while (cmsg.Offset < data.Length)
            {
                Vector3 v;
                cmsg.Count = (ushort)Math.Min(blockSize, (uint)data.Length - cmsg.Offset);
                packet.Reset((ushort)RoutingID, (ushort)type);
                cmsg.Write(packet);
                for (int i = 0; i < cmsg.Count; ++i)
                {
                    v = data[i + cmsg.Offset];
                    packet.WriteBytes(BitConverter.GetBytes(v.x), true);
                    packet.WriteBytes(BitConverter.GetBytes(v.y), true);
                    packet.WriteBytes(BitConverter.GetBytes(v.z), true);
                }

                if (cmsg.Count > 0)
                {
                    packet.FinalisePacket();
                    packet.ExportTo(writer);
                }

                cmsg.Offset += cmsg.Count;
                cmsg.Count   = 0;
            }
        }
        /// <summary>
        /// Serialises the currently active objects in for playback from file.
        /// </summary>
        /// <param name="writer">The write to serialise to.</param>
        /// <param name="info">Statistics</param>
        /// <returns>An error code on failure.</returns>
        public override Error Serialise(BinaryWriter writer, ref SerialiseInfo info)
        {
            Error err = new Error();

            info.TransientCount = info.PersistentCount = 0u;

            PacketBuffer        packet = new PacketBuffer(1024);
            CategoryNameMessage msg    = new CategoryNameMessage();

            foreach (Category cat in _categories.Values)
            {
                ++info.PersistentCount;
                msg.CategoryID    = cat.ID;
                msg.ParentID      = cat.ParentID;
                msg.DefaultActive = cat.Active;
                msg.Name          = cat.Name;
                packet.Reset(RoutingID, CategoryNameMessage.MessageID);
                msg.Write(packet);
                packet.FinalisePacket();
                packet.ExportTo(writer);
            }

            return(err);
        }
        /// <summary>
        /// Update active transfers, transferring up to the given <paramref name="byteLimit"/>.
        /// </summary>
        /// <param name="byteLimit">The maximum number of bytes allowed to be transferred in this update.
        /// Zero for no limit.</param>
        public int UpdateTransfers(int byteLimit)
        {
            int          transferred = 0;
            PacketBuffer packet      = null;

            if (byteLimit != 0 && transferred >= byteLimit)
            {
                // Byte limit exceeded. Stop.
                return(transferred);
            }

            // Next update shared resources.
            _resourceLock.Lock();
            try
            {
                packet = packet ?? new PacketBuffer(2 * 1024);

                // Do shared resource transfer.
                while ((byteLimit == 0 || transferred < byteLimit) && (_currentResource != null || _sharedResourceQueue.Count != 0))
                {
                    if (_currentResource == null)
                    {
                        // No current resource. Pop the next resource.
                        _currentResource = _sharedResources[_sharedResourceQueue.First.Value];
                        _currentResourceProgress.Reset();
                        _sharedResourceQueue.RemoveFirst();
                        // Start transfer.
                        _currentResource.Started = true;
                        _currentResource.Resource.Create(packet);
                        if (packet.FinalisePacket())
                        {
                            int sendRes = Send(packet.Data, 0, packet.Count);
                            if (sendRes >= 0)
                            {
                                transferred += sendRes;
                            }
                            else
                            {
                                // TODO: report error.
                            }
                        }
                    }

                    // Process the current resource.
                    if (_currentResource != null)
                    {
                        // Update a part transfer.
                        _currentResource.Resource.Transfer(packet, Math.Max(0, byteLimit - transferred), ref _currentResourceProgress);
                        if (packet.FinalisePacket())
                        {
                            int sendRes = Send(packet.Data, 0, packet.Count);
                            if (sendRes >= 0)
                            {
                                transferred += sendRes;
                            }
                            else
                            {
                                // TODO: report error.
                            }
                        }

                        // Check for completion
                        if (_currentResourceProgress.Complete)
                        {
                            // Resource transfer complete.
                            _currentResource.Sent = true;
                            _currentResource      = null;
                        }
                        else if (_currentResourceProgress.Failed)
                        {
                            // TODO: report error and mark failure.
                            _currentResource.Sent = true;
                            _currentResource      = null;
                        }
                    }
                }
            }
            finally
            {
                _resourceLock.Unlock();
            }

            return(transferred);
        }
Example #16
0
        /// <summary>
        /// Serialises a list of objects to <paramref name="writer"/>
        /// </summary>
        /// <param name="writer">The writer to serialise to.</param>
        /// <param name="objects">The object to write.</param>
        /// <param name="processedCount">Number of objects processed.</param>
        /// <returns>An error code on failure.</returns>
        /// <remarks>
        /// Default serialisation uses the following logic on each object:
        /// <list type="bullet">
        /// <item>Call <see cref="CreateSerialisationShape(ShapeCache, int, CreateMessage)"/> to
        ///       create a temporary shape to match the unity object</item>
        /// <item>Call <see cref="Shapes.Shape.WriteCreate(PacketBuffer)"/> to generate the creation message
        ///       and serialise the packet.</item>
        /// <item>For complex shapes, call <see cref="Shapes.Shape.WriteData(PacketBuffer, ref uint)"/> as required
        ///       and serialise the packets.</item>
        /// </list>
        ///
        /// Using the <see cref="Shapes.Shape"/> classes ensures serialisation is consistent with the server code
        /// and reduces the code maintenance to one code path.
        /// </remarks>
        protected virtual Error SerialiseShapes(BinaryWriter writer, ShapeCache cache, ref uint processedCount)
        {
            // Serialise transient objects.
            PacketBuffer packet = new PacketBuffer();
            Error        err;

            Shapes.Shape        tempShape      = null;
            List <Shapes.Shape> multiShapeList = new List <Shapes.Shape>();
            uint dataMarker = 0;
            int  dataResult = 0;

            Debug.Assert(tempShape != null && tempShape.RoutingID == RoutingID);

            processedCount = 0;
            foreach (int shapeIndex in cache.ShapeIndices)
            {
                tempShape = null;
                ++processedCount;
                CreateMessage shapeData = cache.GetShapeByIndex(shapeIndex);
                if ((shapeData.Flags & (ushort)ObjectFlag.MultiShape) != 0)
                {
                    // Multi-shape. Follow the link.
                    multiShapeList.Clear();
                    int nextIndex = cache.GetMultiShapeChainByIndex(shapeIndex);
                    while (nextIndex != -1)
                    {
                        CreateMessage multiShapeData = cache.GetShapeByIndex(nextIndex);
                        tempShape    = CreateSerialisationShape(cache, shapeIndex, multiShapeData);
                        tempShape.ID = shapeData.ObjectID;
                        if (tempShape != null)
                        {
                            // Successfully created the child. Add to the list.
                            multiShapeList.Add(tempShape);
                        }
                        else
                        {
                            Debug.LogError($"{Name} failed to create multi-shape entry");
                        }
                        nextIndex = cache.GetMultiShapeChainByIndex(nextIndex);
                    }

                    // Create the multi-shape
                    tempShape          = new Shapes.MultiShape(multiShapeList.ToArray());
                    tempShape.ID       = shapeData.ObjectID;
                    tempShape.Category = shapeData.Category;
                    tempShape.SetAttributes(shapeData.Attributes);
                }
                else if (shapeData.ObjectID != ShapeCache.MultiShapeID)
                {
                    tempShape = CreateSerialisationShape(cache, shapeIndex, shapeData);
                }

                if (tempShape != null)
                {
                    tempShape.WriteCreate(packet);
                    packet.FinalisePacket();
                    packet.ExportTo(writer);

                    if (tempShape.IsComplex)
                    {
                        dataResult = 1;
                        dataMarker = 0;
                        while (dataResult > 0)
                        {
                            dataResult = tempShape.WriteData(packet, ref dataMarker);
                            packet.FinalisePacket();
                            packet.ExportTo(writer);
                        }

                        if (dataResult < 0)
                        {
                            return(new Error(ErrorCode.SerialisationFailure));
                        }

                        // Post serialisation extensions.
                        err = PostSerialiseCreateObject(packet, writer, cache, shapeIndex);
                        if (err.Failed)
                        {
                            return(err);
                        }
                    }
                }
                else if (shapeData.ObjectID != ShapeCache.MultiShapeID)
                {
                    return(new Error(ErrorCode.SerialisationFailure));
                }
            }

            return(new Error());
        }
Example #17
0
        private void FinaliseOutput(BinaryWriter writer, uint frameCount)
        {
            // Rewind the stream to the beginning and find the first RoutingID.ServerInfo message
            // and RoutingID.Control message with a ControlMessageID.FrameCount ID. These should be
            // the first and second messages in the stream.
            // We'll limit searching to the first 5 messages.
            long serverInfoMessageStart = -1;
            long frameCountMessageStart = -1;

            writer.Flush();

            // Extract the stream from the writer. The first stream may be a GZip stream, in which case we must
            // extract the stream that is writing to instead as we can't rewind compression streams
            // and we wrote the header raw.
            writer.BaseStream.Flush();

            Stream          outStream = null;
            CollationStream zipStream = writer.BaseStream as CollationStream;

            if (zipStream != null)
            {
                outStream = zipStream.BaseStream;
                zipStream.Flush();
            }
            else
            {
                outStream = writer.BaseStream;
            }

            // Check we are allowed to seek the stream.
            if (!outStream.CanSeek)
            {
                // Stream does not support seeking. The frame count will not be fixed.
                return;
            }

            // Record the initial stream position to restore later.
            long restorePos = outStream.Position;

            outStream.Seek(0, SeekOrigin.Begin);

            long streamPos = 0;

            byte[]       headerBuffer = new byte[PacketHeader.Size];
            PacketHeader header       = new PacketHeader();

            byte[] markerValidationBytes = BitConverter.GetBytes(Tes.IO.Endian.ToNetwork(PacketHeader.PacketMarker));
            byte[] markerBytes           = new byte[markerValidationBytes.Length];
            bool   markerValid           = false;

            int attemptsRemaining = 5;
            int byteReadLimit     = 0;

            markerBytes[0] = 0;
            while ((frameCountMessageStart < 0 || serverInfoMessageStart < 0) && attemptsRemaining > 0 && outStream.CanRead)
            {
                --attemptsRemaining;
                markerValid = false;

                // Limit the number of bytes we try read in each attempt.
                byteReadLimit = 1024;
                while (byteReadLimit > 0)
                {
                    --byteReadLimit;
                    outStream.Read(markerBytes, 0, 1);
                    if (markerBytes[0] == markerValidationBytes[0])
                    {
                        markerValid = true;
                        int i = 1;
                        for (i = 1; markerValid && outStream.CanRead && i < markerValidationBytes.Length; ++i)
                        {
                            outStream.Read(markerBytes, i, 1);
                            markerValid = markerValid && markerBytes[i] == markerValidationBytes[i];
                        }

                        if (markerValid)
                        {
                            break;
                        }
                        else
                        {
                            // We've failed to fully validate the maker. However, we did read and validate
                            // one byte in the marker, then continued reading until the failure. It's possible
                            // that the last byte read, the failed byte, may be the start of the actual marker.
                            // We check this below, and if so, we rewind the stream one byte in order to
                            // start validation from there on the next iteration. We can ignore the byte if
                            // it is does not match the first validation byte. We are unlikely to ever make this
                            // match though.
                            --i; // Go back to the last read byte.
                            if (markerBytes[i] == markerValidationBytes[0])
                            {
                                // Potentially the start of a new marker. Rewind the stream to attempt to validate it.
                                outStream.Seek(-1, SeekOrigin.Current);
                            }
                        }
                    }
                }

                if (markerValid && outStream.CanRead)
                {
                    // Potential packet target. Record the stream position at the start of the marker.
                    streamPos = outStream.Position - markerBytes.Length;
                    outStream.Seek(streamPos, SeekOrigin.Begin);

                    // Test the packet.
                    int bytesRead = outStream.Read(headerBuffer, 0, headerBuffer.Length);
                    if (bytesRead == headerBuffer.Length)
                    {
                        // Create a packet.
                        if (header.Read(new NetworkReader(new MemoryStream(headerBuffer, false))))
                        {
                            // Header is OK. Looking for RoutingID.Control
                            if (header.RoutingID == (ushort)RoutingID.ServerInfo)
                            {
                                serverInfoMessageStart = streamPos;
                            }
                            else if (header.RoutingID == (ushort)RoutingID.Control)
                            {
                                // It's control message. Complete and validate the packet.
                                // Read the header. Determine the expected size and read that much more data.
                                PacketBuffer packet = new PacketBuffer(header.PacketSize + Crc16.CrcSize);
                                packet.Emplace(headerBuffer, bytesRead);
                                packet.Emplace(outStream, header.PacketSize + Crc16.CrcSize - bytesRead);
                                if (packet.Status == PacketBufferStatus.Complete)
                                {
                                    // Packet complete. Extract the control message.
                                    NetworkReader  packetReader = new NetworkReader(packet.CreateReadStream(true));
                                    ControlMessage message      = new ControlMessage();
                                    if (message.Read(packetReader) && header.MessageID == (ushort)ControlMessageID.FrameCount)
                                    {
                                        // Found the message location.
                                        frameCountMessageStart = streamPos;
                                    }
                                }
                            }
                            else
                            {
                                // At this point, we've failed to find the right kind of header. We could use the payload size to
                                // skip ahead in the stream which should align exactly to the next message.
                                // Not done for initial testing.
                            }
                        }
                    }
                }
            }

            if (serverInfoMessageStart >= 0)
            {
                // Found the correct location. Seek the stream to here and write a new FrameCount control message.
                outStream.Seek(serverInfoMessageStart, SeekOrigin.Begin);
                PacketBuffer packet = new PacketBuffer();

                header = PacketHeader.Create((ushort)RoutingID.ServerInfo, 0);

                packet.WriteHeader(header);
                _serverInfo.Write(packet);
                packet.FinalisePacket();
                BinaryWriter patchWriter = new Tes.IO.NetworkWriter(outStream);
                packet.ExportTo(patchWriter);
                patchWriter.Flush();
            }

            if (frameCountMessageStart >= 0)
            {
                // Found the correct location. Seek the stream to here and write a new FrameCount control message.
                outStream.Seek(frameCountMessageStart, SeekOrigin.Begin);
                PacketBuffer   packet        = new PacketBuffer();
                ControlMessage frameCountMsg = new ControlMessage();

                header = PacketHeader.Create((ushort)RoutingID.Control, (ushort)ControlMessageID.FrameCount);

                frameCountMsg.ControlFlags = 0;
                frameCountMsg.Value32      = frameCount; // Placeholder. Frame count is currently unknown.
                frameCountMsg.Value64      = 0;
                packet.WriteHeader(header);
                frameCountMsg.Write(packet);
                packet.FinalisePacket();
                BinaryWriter patchWriter = new Tes.IO.NetworkWriter(outStream);
                packet.ExportTo(patchWriter);
                patchWriter.Flush();
            }

            if (outStream.Position != restorePos)
            {
                outStream.Seek(restorePos, SeekOrigin.Begin);
            }
        }