Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
769 lines (671 sloc) 25.9 KB
// VncSharp - .NET VNC Client Library
// Copyright (C) 2008 David Humphrey
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.IO;
using System.Net;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Diagnostics;
using System.Net.Sockets;
using System.Security.Cryptography;
using ComponentAce.Compression.Libs.zlib;
namespace VncSharp
{
/// <summary>
/// Contains methods and properties to handle all aspects of the RFB Protocol versions 3.3 - 3.8.
/// </summary>
public class RfbProtocol
{
#region Constants
// ReSharper disable InconsistentNaming
// Encoding Constants
public const int RAW_ENCODING = 0;
public const int COPYRECT_ENCODING = 1;
public const int RRE_ENCODING = 2;
public const int CORRE_ENCODING = 4;
public const int HEXTILE_ENCODING = 5;
public const int ZRLE_ENCODING = 16;
// Server to Client Message-Type constants
public const int FRAMEBUFFER_UPDATE = 0;
public const int SET_COLOUR_MAP_ENTRIES = 1;
public const int BELL = 2;
public const int SERVER_CUT_TEXT = 3;
// Client to Server Message-Type constants
protected const byte SET_PIXEL_FORMAT = 0;
protected const byte SET_ENCODINGS = 2;
protected const byte FRAMEBUFFER_UPDATE_REQUEST = 3;
protected const byte KEY_EVENT = 4;
protected const byte POINTER_EVENT = 5;
protected const byte CLIENT_CUT_TEXT = 6;
// Keyboard constants
public const int XK_BackSpace = 0xFF08;
public const int XK_Tab = 0xFF09;
public const int XK_Clear = 0xFF0B;
public const int XK_Return = 0xFF0D;
public const int XK_Pause = 0xFF13;
public const int XK_Sys_Req = 0xFF15;
public const int XK_Escape = 0xFF1B;
public const int XK_Home = 0xFF50;
public const int XK_Left = 0xFF51;
public const int XK_Up = 0xFF52;
public const int XK_Right = 0xFF53;
public const int XK_Down = 0xFF54;
public const int XK_Prior = 0xFF55;
public const int XK_Next = 0xFF56;
public const int XK_End = 0xFF57;
public const int XK_Select = 0xFF60;
public const int XK_Print = 0xFF61;
public const int XK_Execute = 0xFF62;
public const int XK_Insert = 0xFF63;
public const int XK_Menu = 0xFF67;
public const int XK_Cancel = 0xFF69;
public const int XK_Help = 0xFF6A;
public const int XK_Break = 0xFF6B;
public const int XK_F1 = 0xFFBE;
public const int XK_F2 = 0xFFBF;
public const int XK_F3 = 0xFFC0;
public const int XK_F4 = 0xFFC1;
public const int XK_F5 = 0xFFC2;
public const int XK_F6 = 0xFFC3;
public const int XK_F7 = 0xFFC4;
public const int XK_F8 = 0xFFC5;
public const int XK_F9 = 0xFFC6;
public const int XK_F10 = 0xFFC7;
public const int XK_F11 = 0xFFC8;
public const int XK_F12 = 0xFFC9;
public const int XK_Shift_L = 0xFFE1;
public const int XK_Shift_R = 0xFFE2;
public const int XK_Control_L = 0xFFE3;
public const int XK_Control_R = 0xFFE4;
public const int XK_Meta_L = 0xFFE7;
public const int XK_Meta_R = 0xFFE8;
public const int XK_Alt_L = 0xFFE9;
public const int XK_Alt_R = 0xFFEA;
public const int XK_Super_L = 0xFFEB;
public const int XK_Super_R = 0xFFEC;
public const int XK_Hyper_L = 0xFFED;
public const int XK_Hyper_R = 0xFFEE;
public const int XK_Delete = 0xFFFF;
// ReSharper restore InconsistentNaming
#endregion
protected int verMajor; // Major version of Protocol--probably 3
protected int verMinor; // Minor version of Protocol--probably 3, 7, or 8
protected TcpClient tcp; // Network object used to communicate with host
protected NetworkStream stream; // Stream object used to send/receive data
protected BinaryReader reader; // Integral rather than Byte values are typically
protected BinaryWriter writer; // sent and received, so these handle this.
protected ZRLECompressedReader zrleReader;
public RfbProtocol()
{
}
/// <summary>
/// Gets the Protocol Version of the remote VNC Host--probably 3.3, 3.7, or 3.8.
/// </summary>
public float ServerVersion {
get {
return (float) verMajor + (verMinor * 0.1f);
}
}
public BinaryReader Reader
{
get
{
return reader;
}
}
public ZRLECompressedReader ZrleReader
{
get
{
return zrleReader;
}
}
/// <summary>
/// Attempt to connect to a remote VNC Host.
/// </summary>
/// <param name="host">The IP Address or Host Name of the VNC Host.</param>
/// <param name="port">The Port number on which to connect. Usually this will be 5900, except in the case that the VNC Host is running on a different Display, in which case the Display number should be added to 5900 to determine the correct port.</param>
public void Connect(string host, int port)
{
if (host == null) throw new ArgumentNullException("host");
// Try to connect, passing any exceptions up to the caller, and if successful,
// wrap a big endian Binary Reader and Binary Writer around the resulting stream.
tcp = new TcpClient();
tcp.NoDelay = true; // turn-off Nagle's Algorithm for better interactive performance with host.
tcp.Connect(host, port);
stream = tcp.GetStream();
// Most of the RFB protocol uses Big-Endian byte order, while
// .NET uses Little-Endian. These wrappers convert between the
// two. See BigEndianReader and BigEndianWriter below for more details.
reader = new BigEndianBinaryReader(stream);
writer = new BigEndianBinaryWriter(stream);
zrleReader = new ZRLECompressedReader(stream);
}
/// <summary>
/// Closes the connection to the remote host.
/// </summary>
public void Close()
{
try {
writer.Close();
reader.Close();
stream.Close();
tcp.Close();
} catch (Exception ex) {
Debug.Fail(ex.Message);
}
}
/// <summary>
/// Reads VNC Host Protocol Version message (see RFB Doc v. 3.8 section 6.1.1)
/// </summary>
/// <exception cref="InvalidDataException">Thrown if the ProtocolVersion message is not in the correct format.</exception>
/// <exception cref="NotSupportedException">Thrown if the version of the server is not known or supported.</exception>
public void ReadProtocolVersion()
{
var versionMessage = Encoding.ASCII.GetString(reader.ReadBytes(12));
var versionMatch = Regex.Match(versionMessage, @"RFB (\d{3}).(\d{3})\n");
if (!versionMatch.Success)
{
throw new InvalidDataException("The ProtocolVersion message received from the server is not in the correct format.");
}
var majorVersion = Convert.ToInt32(versionMatch.Groups[1].Value);
var minorVersion = Convert.ToInt32(versionMatch.Groups[2].Value);
if (majorVersion != 3)
{
throw new NotSupportedException(string.Format("The server is using an unsupported version of the RFB protocol. The server is using version {0}.{1} but only version 3.x is supported.", majorVersion, minorVersion));
}
verMajor = majorVersion;
// As of the time of writing, the only supported versions are 3.3, 3.7, and 3.8.
if (minorVersion < 7) verMinor = 3;
if (minorVersion >= 7 && minorVersion < 8) verMinor = 7;
if (minorVersion >= 8) verMinor = 8;
}
/// <summary>
/// Send the Protocol Version supported by the client. Will be highest supported by server (see RFB Doc v. 3.8 section 6.1.1).
/// </summary>
public void WriteProtocolVersion()
{
// We will use which ever version the server understands, be it 3.3, 3.7, or 3.8.
Debug.Assert(verMinor == 3 || verMinor == 7 || verMinor == 8, "Wrong Protocol Version!",
String.Format("Protocol Version should be 3.3, 3.7, or 3.8 but is {0}.{1}", verMajor.ToString(), verMinor.ToString()));
writer.Write(GetBytes(String.Format("RFB 003.00{0}\n", verMinor.ToString())));
writer.Flush();
}
/// <summary>
/// Determine the type(s) of authentication that the server supports. See RFB Doc v. 3.8 section 6.1.2.
/// </summary>
/// <returns>An array of bytes containing the supported Security Type(s) of the server.</returns>
public byte[] ReadSecurityTypes()
{
// Read and return the types of security supported by the server (see protocol doc 6.1.2)
byte[] types = null;
// Protocol Version 3.7 onward supports multiple security types, while 3.3 only 1
if (verMinor == 3) {
types = new byte[] { (byte) reader.ReadUInt32() };
} else {
byte num = reader.ReadByte();
types = new byte[num];
for (int i = 0; i < num; ++i) {
types[i] = reader.ReadByte();
}
}
return types;
}
/// <summary>
/// If the server has rejected the connection during Authentication, a reason is given. See RFB Doc v. 3.8 section 6.1.2.
/// </summary>
/// <returns>Returns a string containing the reason for the server rejecting the connection.</returns>
public string ReadSecurityFailureReason()
{
int length = (int) reader.ReadUInt32();
return GetString(reader.ReadBytes(length));
}
/// <summary>
/// Indicate to the server which type of authentication will be used. See RFB Doc v. 3.8 section 6.1.2.
/// </summary>
/// <param name="type">The type of Authentication to be used, 1 (None) or 2 (VNC Authentication).</param>
public void WriteSecurityType(byte type)
{
Debug.Assert(type >= 1, "Wrong Security Type", "The Security Type must be one that requires authentication.");
// Only bother writing this byte if the version of the server is 3.7
if (verMinor >= 7) {
writer.Write(type);
writer.Flush();
}
}
/// <summary>
/// When the server uses Security Type 2 (i.e., VNC Authentication), a Challenge/Response
/// mechanism is used to authenticate the user. See RFB Doc v. 3.8 section 6.1.2 and 6.2.2.
/// </summary>
/// <returns>Returns the 16 byte Challenge sent by the server.</returns>
public byte[] ReadSecurityChallenge()
{
return reader.ReadBytes(16);
}
/// <summary>
/// Sends the encrypted Response back to the server. See RFB Doc v. 3.8 section 6.1.2.
/// </summary>
/// <param name="response">The DES password encrypted challege sent by the server.</param>
public void WriteSecurityResponse(byte[] response)
{
writer.Write(response, 0, response.Length);
writer.Flush();
}
/// <summary>
/// When the server uses VNC Authentication, after the Challege/Response, the server
/// sends a status code to indicate whether authentication worked. See RFB Doc v. 3.8 section 6.1.3.
/// </summary>
/// <returns>An integer indicating the status of authentication: 0 = OK; 1 = Failed; 2 = Too Many (deprecated).</returns>
public uint ReadSecurityResult()
{
return reader.ReadUInt32();
}
/// <summary>
/// Sends an Initialisation message to the server. See RFB Doc v. 3.8 section 6.1.4.
/// </summary>
/// <param name="shared">True if the server should allow other clients to connect, otherwise False.</param>
public void WriteClientInitialisation(bool shared)
{
// Non-zero if TRUE, zero if FALSE
writer.Write((byte)(shared ? 1 : 0));
writer.Flush();
}
/// <summary>
/// Reads the server's Initialization message, specifically the remote Framebuffer's properties. See RFB Doc v. 3.8 section 6.1.5.
/// </summary>
/// <returns>Returns a Framebuffer object representing the geometry and properties of the remote host.</returns>
public Framebuffer ReadServerInit()
{
int w = (int) reader.ReadUInt16();
int h = (int) reader.ReadUInt16();
Framebuffer buffer = Framebuffer.FromPixelFormat(reader.ReadBytes(16), w, h);
int length = (int) reader.ReadUInt32();
buffer.DesktopName = GetString(reader.ReadBytes(length));
return buffer;
}
/// <summary>
/// Sends the format to be used by the server when sending Framebuffer Updates. See RFB Doc v. 3.8 section 6.3.1.
/// </summary>
/// <param name="buffer">A Framebuffer telling the server how to encode pixel data. Typically this will be the same one sent by the server during initialization.</param>
public void WriteSetPixelFormat(Framebuffer buffer)
{
writer.Write(SET_PIXEL_FORMAT);
WritePadding(3);
writer.Write(buffer.ToPixelFormat()); // 16-byte Pixel Format
writer.Flush();
}
/// <summary>
/// Tell the server which encodings are supported by the client. See RFB Doc v. 3.8 section 6.3.3.
/// </summary>
/// <param name="encodings">An array of integers indicating the encoding types supported. The order indicates preference, where the first item is the first preferred.</param>
public void WriteSetEncodings(uint[] encodings)
{
writer.Write(SET_ENCODINGS);
WritePadding(1);
writer.Write((ushort)encodings.Length);
for (int i = 0; i < encodings.Length; i++) {
writer.Write(encodings[i]);
}
writer.Flush();
}
/// <summary>
/// Sends a request for an update of the area specified by (x, y, w, h). See RFB Doc v. 3.8 section 6.3.4.
/// </summary>
/// <param name="x">The x-position of the area to be updated.</param>
/// <param name="y">The y-position of the area to be updated.</param>
/// <param name="width">The width of the area to be updated.</param>
/// <param name="height">The height of the area to be updated.</param>
/// <param name="incremental">Indicates whether only changes to the client's data should be sent or the entire desktop.</param>
public void WriteFramebufferUpdateRequest(ushort x, ushort y, ushort width, ushort height, bool incremental)
{
writer.Write(FRAMEBUFFER_UPDATE_REQUEST);
writer.Write((byte)(incremental ? 1 : 0));
writer.Write(x);
writer.Write(y);
writer.Write(width);
writer.Write(height);
writer.Flush();
}
/// <summary>
/// Sends a key press or release to the server. See RFB Doc v. 3.8 section 6.3.5.
/// </summary>
/// <param name="keysym">The value of the key pressed, expressed using X Window "keysym" values.</param>
/// <param name="pressed"></param>
public void WriteKeyEvent(uint keysym, bool pressed)
{
writer.Write(KEY_EVENT);
writer.Write( (byte) (pressed ? 1 : 0));
WritePadding(2);
writer.Write(keysym);
writer.Flush();
}
/// <summary>
/// Sends a mouse movement or button press/release to the server. See RFB Doc v. 3.8 section 6.3.6.
/// </summary>
/// <param name="buttonMask">A bitmask indicating which button(s) are pressed.</param>
/// <param name="point">The location of the mouse cursor.</param>
public void WritePointerEvent(byte buttonMask, Point point)
{
writer.Write(POINTER_EVENT);
writer.Write(buttonMask);
writer.Write( (ushort) point.X);
writer.Write( (ushort) point.Y);
writer.Flush();
}
/// <summary>
/// Sends text in the client's Cut Buffer to the server. See RFB Doc v. 3.8 section 6.3.7.
/// </summary>
/// <param name="text">The text to be sent to the server.</param></param>
public void WriteClientCutText(string text)
{
writer.Write(CLIENT_CUT_TEXT);
WritePadding(3);
writer.Write( (uint) text.Length);
writer.Write(GetBytes(text));
writer.Flush();
}
/// <summary>
/// Reads the type of message being sent by the server--all messages are prefixed with a message type.
/// </summary>
/// <returns>Returns the message type as an integer.</returns>
public int ReadServerMessageType()
{
return (int) reader.ReadByte();
}
/// <summary>
/// Reads the number of update rectangles being sent by the server. See RFB Doc v. 3.8 section 6.4.1.
/// </summary>
/// <returns>Returns the number of rectangles that follow.</returns>
public int ReadFramebufferUpdate()
{
ReadPadding(1);
return (int) reader.ReadUInt16();
}
/// <summary>
/// Reads a rectangle's header information, including its encoding. See RFB Doc v. 3.8 section 6.4.1.
/// </summary>
/// <param name="rectangle">The geometry of the rectangle that is about to be sent.</param>
/// <param name="encoding">The encoding used for this rectangle.</param>
public void ReadFramebufferUpdateRectHeader(out Rectangle rectangle, out int encoding)
{
rectangle = new Rectangle();
rectangle.X = (int) reader.ReadUInt16();
rectangle.Y = (int) reader.ReadUInt16();
rectangle.Width = (int) reader.ReadUInt16();
rectangle.Height = (int) reader.ReadUInt16();
encoding = (int) reader.ReadUInt32();
}
// TODO: this colour map code should probably go in Framebuffer.cs
private ushort[,] mapEntries = new ushort[256, 3];
public ushort[,] MapEntries
{
get {
return mapEntries;
}
}
/// <summary>
/// Reads 8-bit RGB colour values (or updated values) into the colour map. See RFB Doc v. 3.8 section 6.5.2.
/// </summary>
public void ReadColourMapEntry()
{
ReadPadding(1);
ushort firstColor = ReadUInt16();
ushort nbColors = ReadUInt16();
for (int i = 0; i < nbColors; i++, firstColor++)
{
mapEntries[firstColor, 0] = (byte)(ReadUInt16() * Byte.MaxValue / UInt16.MaxValue); // R
mapEntries[firstColor, 1] = (byte)(ReadUInt16() * Byte.MaxValue / UInt16.MaxValue); // G
mapEntries[firstColor, 2] = (byte)(ReadUInt16() * Byte.MaxValue / UInt16.MaxValue); // B
}
}
/// <summary>
/// Reads the text from the Cut Buffer on the server. See RFB Doc v. 3.8 section 6.4.4.
/// </summary>
/// <returns>Returns the text in the server's Cut Buffer.</returns>
public string ReadServerCutText()
{
ReadPadding(3);
int length = (int) reader.ReadUInt32();
return GetString(reader.ReadBytes(length));
}
// ---------------------------------------------------------------------------------------
// Here's all the "low-level" protocol stuff so user objects can access the data directly
/// <summary>
/// Reads a single UInt32 value from the server, taking care of Big- to Little-Endian conversion.
/// </summary>
/// <returns>Returns a UInt32 value.</returns>
public uint ReadUint32()
{
return reader.ReadUInt32();
}
/// <summary>
/// Reads a single UInt16 value from the server, taking care of Big- to Little-Endian conversion.
/// </summary>
/// <returns>Returns a UInt16 value.</returns>
public ushort ReadUInt16()
{
return reader.ReadUInt16();
}
/// <summary>
/// Reads a single Byte value from the server.
/// </summary>
/// <returns>Returns a Byte value.</returns>
public byte ReadByte()
{
return reader.ReadByte();
}
/// <summary>
/// Reads the specified number of bytes from the server, taking care of Big- to Little-Endian conversion.
/// </summary>
/// <param name="count">The number of bytes to be read.</param>
/// <returns>Returns a Byte Array containing the values read.</returns>
public byte[] ReadBytes(int count)
{
return reader.ReadBytes(count);
}
/// <summary>
/// Writes a single UInt32 value to the server, taking care of Little- to Big-Endian conversion.
/// </summary>
/// <param name="value">The UInt32 value to be written.</param>
public void WriteUint32(uint value)
{
writer.Write(value);
}
/// <summary>
/// Writes a single UInt16 value to the server, taking care of Little- to Big-Endian conversion.
/// </summary>
/// <param name="value">The UInt16 value to be written.</param>
public void WriteUInt16(ushort value)
{
writer.Write(value);
}
/// <summary>
/// Writes a single Byte value to the server.
/// </summary>
/// <param name="value">The UInt32 value to be written.</param>
public void WriteByte(byte value)
{
writer.Write(value);
}
/// <summary>
/// Reads the specified number of bytes of padding (i.e., garbage bytes) from the server.
/// </summary>
/// <param name="length">The number of bytes of padding to read.</param>
protected void ReadPadding(int length)
{
ReadBytes(length);
}
/// <summary>
/// Writes the specified number of bytes of padding (i.e., garbage bytes) to the server.
/// </summary>
/// <param name="length">The number of bytes of padding to write.</param>
protected void WritePadding(int length)
{
byte [] padding = new byte[length];
writer.Write(padding, 0, padding.Length);
}
/// <summary>
/// Converts a string to bytes for transfer to the server.
/// </summary>
/// <param name="text">The text to be converted to bytes.</param>
/// <returns>Returns a Byte Array containing the text as bytes.</returns>
protected static byte[] GetBytes(string text)
{
return Encoding.ASCII.GetBytes(text);
}
/// <summary>
/// Converts a series of bytes to a string.
/// </summary>
/// <param name="bytes">The Array of Bytes to be converted to a string.</param>
/// <returns>Returns a String representation of bytes.</returns>
protected static string GetString(byte[] bytes)
{
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
/// <summary>
/// BigEndianBinaryReader is a wrapper class used to read .NET integral types from a Big-Endian stream. It inherits from BinaryReader and adds Big- to Little-Endian conversion.
/// </summary>
protected sealed class BigEndianBinaryReader : BinaryReader
{
private byte[] buff = new byte[4];
public BigEndianBinaryReader(Stream input) : base(input)
{
}
public BigEndianBinaryReader(Stream input, Encoding encoding) : base(input, encoding)
{
}
// Since this is being used to communicate with an RFB host, only some of the overrides are provided below.
public override ushort ReadUInt16()
{
FillBuff(2);
return (ushort)(((uint)buff[1]) | ((uint)buff[0]) << 8);
}
public override short ReadInt16()
{
FillBuff(2);
return (short)(buff[1] & 0xFF | buff[0] << 8);
}
public override uint ReadUInt32()
{
FillBuff(4);
return (uint)(((uint)buff[3]) & 0xFF | ((uint)buff[2]) << 8 | ((uint)buff[1]) << 16 | ((uint)buff[0]) << 24);
}
public override int ReadInt32()
{
FillBuff(4);
return (int)(buff[3] | buff[2] << 8 | buff[1] << 16 | buff[0] << 24);
}
private void FillBuff(int totalBytes)
{
int bytesRead = 0;
int n = 0;
do {
n = BaseStream.Read(buff, bytesRead, totalBytes - bytesRead);
if (n == 0)
throw new IOException("Unable to read next byte(s).");
bytesRead += n;
} while (bytesRead < totalBytes);
}
}
/// <summary>
/// BigEndianBinaryWriter is a wrapper class used to write .NET integral types in Big-Endian order to a stream. It inherits from BinaryWriter and adds Little- to Big-Endian conversion.
/// </summary>
protected sealed class BigEndianBinaryWriter : BinaryWriter
{
public BigEndianBinaryWriter(Stream input) : base(input)
{
}
public BigEndianBinaryWriter(Stream input, Encoding encoding) : base(input, encoding)
{
}
// Flip all little-endian .NET types into big-endian order and send
public override void Write(ushort value)
{
FlipAndWrite(BitConverter.GetBytes(value));
}
public override void Write(short value)
{
FlipAndWrite(BitConverter.GetBytes(value));
}
public override void Write(uint value)
{
FlipAndWrite(BitConverter.GetBytes(value));
}
public override void Write(int value)
{
FlipAndWrite(BitConverter.GetBytes(value));
}
public override void Write(ulong value)
{
FlipAndWrite(BitConverter.GetBytes(value));
}
public override void Write(long value)
{
FlipAndWrite(BitConverter.GetBytes(value));
}
private void FlipAndWrite(byte[] b)
{
// Given an array of bytes, flip and write to underlying stream
Array.Reverse(b);
base.Write(b);
}
}
/// <summary>
/// ZRLE compressed binary reader, used by ZrleRectangle.
/// </summary>
public sealed class ZRLECompressedReader : BinaryReader
{
MemoryStream zlibMemoryStream;
ZOutputStream zlibDecompressedStream;
BinaryReader uncompressedReader;
public ZRLECompressedReader(Stream uncompressedStream) : base(uncompressedStream)
{
zlibMemoryStream = new MemoryStream();
zlibDecompressedStream = new ZOutputStream(zlibMemoryStream);
uncompressedReader = new BinaryReader(zlibMemoryStream);
}
public override byte ReadByte()
{
return uncompressedReader.ReadByte();
}
public override byte[] ReadBytes(int count)
{
return uncompressedReader.ReadBytes(count);
}
public void DecodeStream()
{
// Reset position to use same buffer
zlibMemoryStream.Position = 0;
// Get compressed stream length to read
byte[] buff = new byte[4];
if (this.BaseStream.Read(buff, 0, 4) != 4)
throw new Exception("ZRLE decoder: Invalid compressed stream size");
// BigEndian to LittleEndian conversion
int compressedBufferSize = (int)(buff[3] | buff[2] << 8 | buff[1] << 16 | buff[0] << 24);
if (compressedBufferSize > 64 * 1024 * 1024)
throw new Exception("ZRLE decoder: Invalid compressed data size");
// Decode stream
int pos = 0;
while (pos++ < compressedBufferSize)
zlibDecompressedStream.WriteByte(this.BaseStream.ReadByte());
zlibMemoryStream.Position = 0;
}
}
}
}