using System; using System.Collections.Generic; using System.Net; using System.Diagnostics; using System.Text; using System.Reflection; namespace Lidgren.Network { public sealed partial class NetOutgoingMessage { private const int c_overAllocateAmount = 4; private static Dictionary s_writeMethods; internal byte[] m_data; internal int m_bitLength; static NetOutgoingMessage() { s_writeMethods = new Dictionary(); MethodInfo[] methods = typeof(NetOutgoingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo mi in methods) { if (mi.Name == "Write") { ParameterInfo[] pis = mi.GetParameters(); if (pis.Length == 1) s_writeMethods[pis[0].ParameterType] = mi; } } } /// /// Returns the internal data buffer, don't modify /// public byte[] PeekDataBuffer() { return m_data; } /// /// Gets or sets the length of the buffer in bytes /// public int LengthBytes { get { return ((m_bitLength + 7) >> 3); } set { m_bitLength = value * 8; InternalEnsureBufferSize(m_bitLength); } } /// /// Gets or sets the length of the buffer in bits /// public int LengthBits { get { return m_bitLength; } set { m_bitLength = value; InternalEnsureBufferSize(m_bitLength); } } /// /// Ensures the buffer can hold this number of bits /// public void EnsureBufferSize(int numberOfBits) { int byteLen = ((numberOfBits + 7) >> 3); if (m_data == null) { m_data = new byte[byteLen + c_overAllocateAmount]; return; } if (m_data.Length < byteLen) Array.Resize(ref m_data, byteLen + c_overAllocateAmount); return; } /// /// Ensures the buffer can hold this number of bits /// public void InternalEnsureBufferSize(int numberOfBits) { int byteLen = ((numberOfBits + 7) >> 3); if (m_data == null) { m_data = new byte[byteLen]; return; } if (m_data.Length < byteLen) Array.Resize(ref m_data, byteLen); return; } // // 1 bit // public void Write(bool value) { EnsureBufferSize(m_bitLength + 1); NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength); m_bitLength += 1; } // // 8 bit // public void Write(byte source) { EnsureBufferSize(m_bitLength + 8); NetBitWriter.WriteByte(source, 8, m_data, m_bitLength); m_bitLength += 8; } [CLSCompliant(false)] public void Write(sbyte source) { EnsureBufferSize(m_bitLength + 8); NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength); m_bitLength += 8; } public void Write(byte source, int numberOfBits) { NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits"); EnsureBufferSize(m_bitLength + numberOfBits); NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength); m_bitLength += numberOfBits; } public void Write(byte[] source) { if (source == null) throw new ArgumentNullException("source"); int bits = source.Length * 8; EnsureBufferSize(m_bitLength + bits); NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength); m_bitLength += bits; } public void Write(byte[] source, int offsetInBytes, int numberOfBytes) { if (source == null) throw new ArgumentNullException("source"); int bits = numberOfBytes * 8; EnsureBufferSize(m_bitLength + bits); NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength); m_bitLength += bits; } // // 16 bit // [CLSCompliant(false)] public void Write(UInt16 source) { EnsureBufferSize(m_bitLength + 16); NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength); m_bitLength += 16; } [CLSCompliant(false)] public void Write(UInt16 source, int numberOfBits) { NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits"); EnsureBufferSize(m_bitLength + numberOfBits); NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength); m_bitLength += numberOfBits; } public void Write(Int16 source) { EnsureBufferSize(m_bitLength + 16); NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength); m_bitLength += 16; } // // 32 bit // #if UNSAFE public unsafe void Write(Int32 source) { EnsureBufferSize(m_bitLength + 32); // can write fast? if (m_bitLength % 8 == 0) { fixed (byte* numRef = &Data[m_bitLength / 8]) { *((int*)numRef) = source; } } else { NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength); } m_bitLength += 32; } #else public void Write(Int32 source) { EnsureBufferSize(m_bitLength + 32); NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength); m_bitLength += 32; } #endif #if UNSAFE public unsafe void Write(UInt32 source) { EnsureBufferSize(m_bitLength + 32); // can write fast? if (m_bitLength % 8 == 0) { fixed (byte* numRef = &Data[m_bitLength / 8]) { *((uint*)numRef) = source; } } else { NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength); } m_bitLength += 32; } #else [CLSCompliant(false)] public void Write(UInt32 source) { EnsureBufferSize(m_bitLength + 32); NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength); m_bitLength += 32; } #endif [CLSCompliant(false)] public void Write(UInt32 source, int numberOfBits) { NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits"); EnsureBufferSize(m_bitLength + numberOfBits); NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength); m_bitLength += numberOfBits; } public void Write(Int32 source, int numberOfBits) { NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits"); EnsureBufferSize(m_bitLength + numberOfBits); if (numberOfBits != 32) { // make first bit sign int signBit = 1 << (numberOfBits - 1); if (source < 0) source = (-source - 1) | signBit; else source &= (~signBit); } NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength); m_bitLength += numberOfBits; } // // 64 bit // [CLSCompliant(false)] public void Write(UInt64 source) { EnsureBufferSize(m_bitLength + 64); NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength); m_bitLength += 64; } [CLSCompliant(false)] public void Write(UInt64 source, int numberOfBits) { EnsureBufferSize(m_bitLength + numberOfBits); NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength); m_bitLength += numberOfBits; } public void Write(Int64 source) { EnsureBufferSize(m_bitLength + 64); ulong usource = (ulong)source; NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength); m_bitLength += 64; } public void Write(Int64 source, int numberOfBits) { EnsureBufferSize(m_bitLength + numberOfBits); ulong usource = (ulong)source; NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength); m_bitLength += numberOfBits; } // // Floating point // #if UNSAFE public unsafe void Write(float source) { uint val = *((uint*)&source); #if BIGENDIAN val = NetUtility.SwapByteOrder(val); #endif Write(val); } #else public void Write(float source) { byte[] val = BitConverter.GetBytes(source); #if BIGENDIAN // swap byte order byte tmp = val[3]; val[3] = val[0]; val[0] = tmp; tmp = val[2]; val[2] = val[1]; val[1] = tmp; #endif Write(val); } #endif #if UNSAFE public unsafe void Write(double source) { ulong val = *((ulong*)&source); #if BIGENDIAN val = NetUtility.SwapByteOrder(val); #endif Write(val); } #else public void Write(double source) { byte[] val = BitConverter.GetBytes(source); #if BIGENDIAN // 0 1 2 3 4 5 6 7 // swap byte order byte tmp = val[7]; val[7] = val[0]; val[0] = tmp; tmp = val[6]; val[6] = val[1]; val[1] = tmp; tmp = val[5]; val[5] = val[2]; val[2] = tmp; tmp = val[4]; val[4] = val[3]; val[3] = tmp; #endif Write(val); } #endif // // Variable bits // /// /// Write Base128 encoded variable sized unsigned integer /// /// number of bytes written [CLSCompliant(false)] public int WriteVariableUInt32(uint value) { int retval = 1; uint num1 = (uint)value; while (num1 >= 0x80) { this.Write((byte)(num1 | 0x80)); num1 = num1 >> 7; retval++; } this.Write((byte)num1); return retval; } /// /// Write Base128 encoded variable sized signed integer /// /// number of bytes written public int WriteVariableInt32(int value) { int retval = 1; uint num1 = (uint)((value << 1) ^ (value >> 31)); while (num1 >= 0x80) { this.Write((byte)(num1 | 0x80)); num1 = num1 >> 7; retval++; } this.Write((byte)num1); return retval; } /// /// Write Base128 encoded variable sized unsigned integer /// /// number of bytes written [CLSCompliant(false)] public int WriteVariableUInt64(UInt64 value) { int retval = 1; UInt64 num1 = (UInt64)value; while (num1 >= 0x80) { this.Write((byte)(num1 | 0x80)); num1 = num1 >> 7; retval++; } this.Write((byte)num1); return retval; } /// /// Compress (lossy) a float in the range -1..1 using numberOfBits bits /// public void WriteSignedSingle(float value, int numberOfBits) { NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value); float unit = (value + 1.0f) * 0.5f; int maxVal = (1 << numberOfBits) - 1; uint writeVal = (uint)(unit * (float)maxVal); Write(writeVal, numberOfBits); } /// /// Compress (lossy) a float in the range 0..1 using numberOfBits bits /// public void WriteUnitSingle(float value, int numberOfBits) { NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value); int maxValue = (1 << numberOfBits) - 1; uint writeVal = (uint)(value * (float)maxValue); Write(writeVal, numberOfBits); } /// /// Compress a float within a specified range using a certain number of bits /// public void WriteRangedSingle(float value, float min, float max, int numberOfBits) { NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value); float range = max - min; float unit = ((value - min) / range); int maxVal = (1 << numberOfBits) - 1; Write((UInt32)((float)maxVal * unit), numberOfBits); } /// /// Writes an integer with the least amount of bits need for the specified range /// Returns number of bits written /// public int WriteRangedInteger(int min, int max, int value) { NetException.Assert(value >= min && value <= max, "Value not within min/max range!"); uint range = (uint)(max - min); int numBits = NetUtility.BitsToHoldUInt(range); uint rvalue = (uint)(value - min); Write(rvalue, numBits); return numBits; } /// /// Write a string /// public void Write(string source) { if (string.IsNullOrEmpty(source)) { EnsureBufferSize(m_bitLength + 8); WriteVariableUInt32(0); return; } byte[] bytes = Encoding.UTF8.GetBytes(source); EnsureBufferSize(m_bitLength + 1 + bytes.Length); WriteVariableUInt32((uint)bytes.Length); Write(bytes); } /// /// Writes an endpoint description /// public void Write(IPEndPoint endPoint) { byte[] bytes = endPoint.Address.GetAddressBytes(); Write((byte)bytes.Length); Write(bytes); Write((ushort)endPoint.Port); } /// /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. /// public void WritePadBits() { m_bitLength = ((m_bitLength + 7) >> 3) * 8; EnsureBufferSize(m_bitLength); } /// /// Pads data with the specified number of bits. /// public void WritePadBits(int numberOfBits) { m_bitLength += numberOfBits; EnsureBufferSize(m_bitLength); } /// /// Writes all public and private declared instance fields of the object in alphabetical order using reflection /// public void WriteAllFields(object ob) { WriteAllFields(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); } /// /// Writes all fields with specified binding in alphabetical order using reflection /// public void WriteAllFields(object ob, BindingFlags flags) { if (ob == null) return; Type tp = ob.GetType(); FieldInfo[] fields = tp.GetFields(flags); NetIncomingMessage.SortMembersList(fields); foreach (FieldInfo fi in fields) { object value = fi.GetValue(ob); // find the appropriate Write method MethodInfo writeMethod; if (s_writeMethods.TryGetValue(fi.FieldType, out writeMethod)) writeMethod.Invoke(this, new object[] { value }); else throw new NetException("Failed to find write method for type " + fi.FieldType); } } /// /// Writes all public and private declared instance properties of the object in alphabetical order using reflection /// public void WriteAllProperties(object ob) { WriteAllProperties(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); } /// /// Writes all properties with specified binding in alphabetical order using reflection /// public void WriteAllProperties(object ob, BindingFlags flags) { if (ob == null) return; Type tp = ob.GetType(); PropertyInfo[] fields = tp.GetProperties(flags); NetIncomingMessage.SortMembersList(fields); foreach (PropertyInfo fi in fields) { MethodInfo getMethod = fi.GetGetMethod((flags & BindingFlags.NonPublic) == BindingFlags.NonPublic); object value = getMethod.Invoke(ob, null); // find the appropriate Write method MethodInfo writeMethod; if (s_writeMethods.TryGetValue(fi.PropertyType, out writeMethod)) writeMethod.Invoke(this, new object[] { value }); } } /// /// Append all the bits of message to this message /// public void Write(NetOutgoingMessage message) { EnsureBufferSize(m_bitLength + (message.LengthBytes * 8)); Write(message.m_data, 0, message.LengthBytes); // did we write excessive bits? int bitsInLastByte = (message.m_bitLength % 8); if (bitsInLastByte != 0) { int excessBits = 8 - bitsInLastByte; m_bitLength -= excessBits; } } } }