using System; using System.Text; using Aleeda.Specialized.Encoding; namespace Aleeda.Net.Messages { /// /// Represents a Habbo client > server protocol structured message, providing methods to identify and 'read' the message. /// public class ClientMessage : IHabboMessage { #region Fields /// /// The ID of this message as an unsigned 32 bit integer. /// private readonly uint fID; /// /// The content of this message as a byte array. /// private readonly byte[] fContent; /// /// The current index in the content array, used when reading the message. /// private int fContentCursor; #endregion #region Properties /// /// Gets the ID of this message as an unsigned 32 bit integer. /// public uint ID { get { return fID; } } /// /// Gets the header of this message, by Base64 encoding the message ID to a 2 byte string. /// public string GetHeader() { return AleedaEnvironment.GetDefaultEncoding().GetString(Base64Encoding.Encodeuint(fID, 2)); } /// /// Gets the length of the content in this message. /// public int GetContentLength() { return fContent.Length; } /// /// Gets the amount of unread content bytes. /// public int GetRemainingContent() { return (fContent.Length - fContentCursor); } public int contentLength { get { return fContent.Length - fContentCursor; } } #endregion #region Constructors /// /// Constructs a ClientMessage object for a given message ID and a given content byte array. /// /// The ID of the message as an unsigned 32 bit integer. /// The content as a byte array. If null is supplied, an empty byte array will be created. public ClientMessage(uint ID, byte[] bzContent) { if (bzContent == null) bzContent = new byte[0]; fID = ID; fContent = bzContent; fContentCursor = 0; } #endregion #region Methods /// /// Resets the client message to it's state when it was constructed by resetting the content reader cursor. This allows to re-read read data. /// public void Reset() { fContentCursor = 0; } /// /// Advances the content cursor by a given amount of bytes. /// /// The amount of bytes to 'skip'. public void Advance(int n) { fContentCursor += n; } /// /// Returns the total content of this message as a string. /// /// String public string GetContentString() { return Encoding.Default.GetString(fContent); } /// /// Returns the header and total content of this message as a string. /// public string GetFullString() { return this.GetHeader() + GetContentString(); } /// /// Reads a given amount of bytes from the remaining message content and returns it in a byte array. The reader cursor is incremented during reading. /// /// The amount of bytes to read, advance and return. If there is less remaining data than this value, all remaining data will be read. /// byte[] public byte[] ReadBytes(int numBytes) { if (numBytes > this.GetRemainingContent()) numBytes = this.GetRemainingContent(); byte[] bzData = new byte[numBytes]; for (int x = 0; x < numBytes; x++) { bzData[x] = fContent[fContentCursor++]; } return bzData; } /// /// Reads a given amount of bytes from the remaining message content and returns it in a byte array. The reader cursor does not increment during reading. /// /// The amount of bytes to read, advance and return. If there is less remaining data than this value, all remaining data will be read. /// byte[] public byte[] ReadBytesFreezeCursor(int numBytes) { if (numBytes > this.GetRemainingContent()) numBytes = this.GetRemainingContent(); byte[] bzData = new byte[numBytes]; for (int x = 0, y = fContentCursor; x < numBytes; x++, y++) { bzData[x] = fContent[y]; } return bzData; } /// /// Reads a length-prefixed (Base64) value from the message and returns it as a byte array. /// /// byte[] public byte[] ReadPrefixedValue() { Int32 Length = Base64Encoding.DecodeInt32(this.ReadBytes(2)); return this.ReadBytes(Length); } /// /// Reads a Base64 boolean and returns it. False is returned if there is no remaining content. /// /// Boolean public Boolean PopBase64Boolean() { return (this.GetRemainingContent() > 0 && fContent[fContentCursor++] == Base64Encoding.POSITIVE); } public Int32 PopInt32() { return Base64Encoding.DecodeInt32(this.ReadBytes(2)); } public UInt32 PopUInt32() { return (UInt32)PopInt32(); } public string PopFixedString() { return PopFixedString(AleedaEnvironment.GetDefaultEncoding()); } public string PopFixedString(Encoding encoding) { return encoding.GetString(ReadPrefixedValue()).Replace(Convert.ToChar(1), ' '); } /// /// Reads a length prefixed string 32 bit integer from the message content and tries to parse it to integer. No exceptions are thrown if parsing fails. /// /// Int32 public Int32 PopPrefixedInt32() { Int32 i; String s = PopFixedString(Encoding.UTF8); Int32.TryParse(s, out i); return i; } /// /// Reads a length prefixed string 32 bit unsigned integer from the message content and tries to parse it to integer. No exceptions are thrown if parsing fails. /// /// Int32 /// PopPrefixedInt32 public uint PopPrefixedUInt32() { return (uint)PopPrefixedInt32(); } /// /// Reads a wire format boolean and returns it. False is returned if there is no remaining content. /// /// Boolean public Boolean PopWiredBoolean() { return (this.GetRemainingContent() > 0 && fContent[fContentCursor++] == WireEncoding.POSITIVE); } /// /// Reads the next wire encoded 32 bit integer from the message content and advances the reader cursor. /// /// Int32 public Int32 PopWiredInt32() { if (this.GetRemainingContent() == 0) return 0; byte[] bzData = ReadBytesFreezeCursor(WireEncoding.MAX_INTEGER_BYTE_AMOUNT); Int32 totalBytes = 0; Int32 i = WireEncoding.DecodeInt32(bzData, out totalBytes); fContentCursor += totalBytes; return i; } /// /// Reads the next wire encoded unsigned 32 bit integer from the message content and advances the reader cursor. /// /// Int32 /// PopWiredInt32() public uint PopWiredUInt32() { return (uint)PopWiredInt32(); } public string PopEncodeInt32() { return wireEncoding.encodeVL64(PopWiredInt32()); } #endregion } }