diff --git a/src/libnpsharp/NPClient.cs b/src/libnpsharp/NPClient.cs
new file mode 100644
index 0000000..c61698a
--- /dev/null
+++ b/src/libnpsharp/NPClient.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using log4net;
+using NPSharp.RPC;
+using NPSharp.RPC.Packets;
+
+namespace NPSharp
+{
+ ///
+ /// Represents a high-level network platform client.
+ ///
+ public class NPClient
+ {
+ private readonly RPCClientStream _rpc;
+ private CancellationTokenSource _cancellationTokenSource;
+ private CancellationToken _cancellationToken;
+ private ILog _log;
+
+ ///
+ /// Initializes the NP client with a specified host and port.
+ ///
+ /// The host to connect to.
+ /// The port to use. Default: 3025.
+ public NPClient(string host, ushort port = 3025)
+ {
+ _rpc = new RPCClientStream(host, port);
+ _log = LogManager.GetLogger ("NPClient");
+ }
+
+ ///
+ /// The assigned NP user ID. Will be set on successful authentication.
+ ///
+ public ulong LoginId { get; private set; }
+
+ ///
+ /// The assigned session token for this client. Will be set on successful authentication.
+ ///
+ public string SessionToken { get; private set; }
+
+ // TODO: Handle connection failures via exception
+ ///
+ /// Connects the client to the NP server.
+ ///
+ /// True if the connection succeeded, otherwise false.
+ public bool Connect()
+ {
+ _cancellationTokenSource = new CancellationTokenSource();
+ _cancellationToken = _cancellationTokenSource.Token;
+
+ if (!_rpc.Open())
+ return false;
+
+ Task.Factory.StartNew(() =>
+ {
+ _log.Debug("Now receiving RPC messages");
+ try
+ {
+ while (true)
+ {
+ var message = _rpc.Read();
+ if (message == null)
+ continue;
+
+ // TODO: log4net
+ Console.WriteLine("Received packet ID {1} (type {0})", message.GetType().Name, message.MessageId);
+ }
+ }
+ catch (ProtocolViolationException error)
+ {
+ _log.ErrorFormat("Protocol violation: {0}. Disconnect imminent.", error.Message);
+ Disconnect();
+ }
+
+ _log.Debug("Now not receiving RPC messages anymore");
+ }, _cancellationToken);
+
+ return true;
+ }
+
+ ///
+ /// Disconnects the client from the NP server.
+ ///
+ public void Disconnect()
+ {
+ _cancellationTokenSource.Cancel(true); // TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read)
+ _rpc.Close();
+
+ LoginId = 0;
+ }
+
+ // TODO: Try to use an exception for failed action instead
+ ///
+ /// Authenticates this connection via a token. This token has to be requested via an external interface like remauth.php.
+ ///
+ /// The token to use for authentication
+ /// True if the login succeeded, otherwise false.
+ public async Task AuthenticateWithToken(string token)
+ {
+ var tcs = new TaskCompletionSource();
+
+ _rpc.AttachCallback(packet =>
+ {
+ var result = (AuthenticateResultMessage) packet;
+ if (result.Result != 0)
+ tcs.SetResult(false);
+ LoginId = result.NPID;
+ SessionToken = result.SessionToken;
+ tcs.SetResult(true);
+ });
+ _rpc.Send(new AuthenticateWithTokenMessage {Token = token});
+
+ return await tcs.Task;
+ }
+
+ // TODO: Try to use an exception for failed action instead
+ ///
+ /// Uploads a user file.
+ ///
+ /// The file name to save the contents to on the server
+ /// The raw byte contents
+ /// True if the upload succeeded, otherwise false.
+ public async Task UploadUserFile(string filename, byte[] contents)
+ {
+ var tcs = new TaskCompletionSource();
+
+ _rpc.AttachCallback(packet =>
+ {
+ var result = (StorageWriteUserFileResultMessage) packet;
+ if (result.Result != 0)
+ tcs.SetResult(false);
+ tcs.SetResult(true);
+ });
+ _rpc.Send(new StorageWriteUserFileMessage {FileData = contents, FileName = filename, NPID = LoginId});
+
+ return await tcs.Task;
+ }
+
+ ///
+ /// Downloads a user file and returns its contents.
+ ///
+ /// The file to download
+ /// File contents as byte array
+ public async Task GetUserFile(string filename)
+ {
+ var tcs = new TaskCompletionSource();
+
+ _rpc.AttachCallback(packet =>
+ {
+ var result = (StorageUserFileMessage) packet;
+ if (result.Result != 0)
+ {
+ tcs.SetException(new NpFileException());
+ return;
+ }
+ tcs.SetResult(result.FileData);
+ });
+ _rpc.Send(new StorageGetUserFileMessage {FileName = filename, NPID = LoginId});
+
+ return await tcs.Task;
+ }
+
+
+ ///
+ /// Downloads a user file onto the harddisk.
+ ///
+ /// The file to download
+ /// Path where to save the file
+ public async void DownloadUserFileTo(string filename, string targetpath)
+ {
+ var contents = await GetUserFile(filename);
+
+ File.WriteAllBytes(targetpath, contents);
+ }
+
+
+ ///
+ /// Downloads a publisher file and returns its contents.
+ ///
+ /// The file to download
+ /// File contents as byte array
+ public async Task GetPublisherFile(string filename)
+ {
+ var tcs = new TaskCompletionSource();
+
+ _rpc.AttachCallback(packet =>
+ {
+ var result = (StoragePublisherFileMessage) packet;
+ if (result.Result != 0)
+ {
+ tcs.SetException(new NpFileException());
+ return;
+ }
+ tcs.SetResult(result.FileData);
+ });
+ _rpc.Send(new StorageGetPublisherFileMessage {FileName = filename});
+
+ return await tcs.Task;
+ }
+
+ ///
+ /// Downloads a publisher file onto the harddisk.
+ ///
+ /// The file to download
+ /// Path where to save the file
+ public async void DownloadPublisherFileTo(string filename, string targetpath)
+ {
+ var contents = await GetPublisherFile(filename);
+
+ File.WriteAllBytes(targetpath, contents);
+ }
+
+ public void SendRandomString(string data)
+ {
+ _rpc.Send(new StorageSendRandomStringMessage() { RandomString=data });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/AuthenticateExternalStatusMessage.cs b/src/libnpsharp/RPC/Packets/AuthenticateExternalStatusMessage.cs
new file mode 100644
index 0000000..78f2f56
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/AuthenticateExternalStatusMessage.cs
@@ -0,0 +1,12 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1006)]
+ [ProtoContract]
+ class AuthenticateExternalStatusMessage : RPCServerMessage
+ {
+ [ProtoMember(1)]
+ public int Status { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/AuthenticateResultMessage.cs b/src/libnpsharp/RPC/Packets/AuthenticateResultMessage.cs
new file mode 100644
index 0000000..42cb81c
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/AuthenticateResultMessage.cs
@@ -0,0 +1,18 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1010)]
+ [ProtoContract]
+ class AuthenticateResultMessage : RPCServerMessage
+ {
+ [ProtoMember(1)]
+ public int Result { get; set; }
+
+ [ProtoMember(2)]
+ public ulong NPID { get; set; }
+
+ [ProtoMember(3)]
+ public string SessionToken { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/AuthenticateWithTokenMessage.cs b/src/libnpsharp/RPC/Packets/AuthenticateWithTokenMessage.cs
new file mode 100644
index 0000000..eff3199
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/AuthenticateWithTokenMessage.cs
@@ -0,0 +1,12 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1003)]
+ [ProtoContract]
+ class AuthenticateWithTokenMessage : RPCClientMessage
+ {
+ [ProtoMember(1)]
+ public string Token { get; set; }
+ }
+}
diff --git a/src/libnpsharp/RPC/Packets/CloseAppMessage.cs b/src/libnpsharp/RPC/Packets/CloseAppMessage.cs
new file mode 100644
index 0000000..2dd2710
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/CloseAppMessage.cs
@@ -0,0 +1,12 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(2001)]
+ [ProtoContract]
+ class CloseAppMessage : RPCServerMessage
+ {
+ [ProtoMember(1)]
+ public string Reason { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/HelloMessage.cs b/src/libnpsharp/RPC/Packets/HelloMessage.cs
new file mode 100644
index 0000000..42cb333
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/HelloMessage.cs
@@ -0,0 +1,22 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1000)]
+ [ProtoContract]
+ class HelloMessage : RPCServerMessage
+ {
+ // I seriously have no idea where in the code this is used but whatever
+ [ProtoMember(1)]
+ public int Number1 { get; set; }
+
+ [ProtoMember(2)]
+ public int Number2 { get; set; }
+
+ [ProtoMember(3)]
+ public string Name { get; set; }
+
+ [ProtoMember(4)]
+ public string String2 { get; set; }
+ }
+}
diff --git a/src/libnpsharp/RPC/Packets/MessagingSendDataMessage.cs b/src/libnpsharp/RPC/Packets/MessagingSendDataMessage.cs
new file mode 100644
index 0000000..360787c
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/MessagingSendDataMessage.cs
@@ -0,0 +1,15 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(2002)]
+ [ProtoContract]
+ class MessagingSendDataMessage : RPCClientMessage
+ {
+ [ProtoMember(1)]
+ public ulong NPID { get; set; }
+
+ [ProtoMember(2)]
+ public byte[] Data { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/PacketAttribute.cs b/src/libnpsharp/RPC/Packets/PacketAttribute.cs
new file mode 100644
index 0000000..ed5124b
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/PacketAttribute.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace NPSharp.RPC.Packets
+{
+ class PacketAttribute : Attribute
+ {
+ public PacketAttribute(uint type)
+ {
+ Type = type;
+ }
+
+ public uint Type { get; set; }
+ }
+}
diff --git a/src/libnpsharp/RPC/Packets/RPCClientMessage.cs b/src/libnpsharp/RPC/Packets/RPCClientMessage.cs
new file mode 100644
index 0000000..d08ae6f
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/RPCClientMessage.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+
+namespace NPSharp.RPC.Packets
+{
+ public class RPCClientMessage : RPCMessage
+ {
+ public byte[] Serialize(uint id)
+ {
+ byte[] content;
+ using (var bufferStream = new MemoryStream())
+ {
+ ProtoBuf.Serializer.Serialize(bufferStream, this);
+ bufferStream.Seek(0, SeekOrigin.Begin);
+ content = bufferStream.ToArray();
+ }
+
+ var buffer = new List();
+ buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(Signature)));
+ buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(content.Length)));
+ buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(GetTypeId())));
+ buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(id)));
+ buffer.AddRange(content);
+
+ return buffer.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/RPCMessage.cs b/src/libnpsharp/RPC/Packets/RPCMessage.cs
new file mode 100644
index 0000000..7712425
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/RPCMessage.cs
@@ -0,0 +1,15 @@
+using System.Linq;
+
+namespace NPSharp.RPC.Packets
+{
+ public class RPCMessage
+ {
+ internal const uint Signature = 0xDEADC0DE; // I wonder if aiw3 changed this since kernal noted it in his source code.
+
+ public uint GetTypeId()
+ {
+ var packet = (PacketAttribute) GetType().GetCustomAttributes(typeof (PacketAttribute), false).Single();
+ return packet.Type;
+ }
+ }
+}
diff --git a/src/libnpsharp/RPC/Packets/RPCServerMessage.cs b/src/libnpsharp/RPC/Packets/RPCServerMessage.cs
new file mode 100644
index 0000000..84de050
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/RPCServerMessage.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+
+namespace NPSharp.RPC.Packets
+{
+ public class RPCServerMessage : RPCMessage
+ {
+ public uint MessageId { get; private set; }
+
+ public static RPCServerMessage Deserialize(NetworkStream ns)
+ {
+ var header = new byte[16];
+ var l = ns.Read(header, 0, header.Length);
+ if (l == 0)
+ return null;
+ if (l < 16)
+ throw new ProtocolViolationException("Received incomplete header");
+
+ var signature = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 0));
+ var length = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 4));
+ var type = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 8));
+ var buffer = new byte[length];
+ ns.Read(buffer, 0, buffer.Length);
+
+ if (signature != Signature)
+ throw new ProtocolViolationException("Received packet with invalid signature");
+
+ RPCServerMessage packet;
+
+ using (var ms = new MemoryStream(buffer))
+ {
+ var types = Assembly.GetExecutingAssembly().GetTypes().Where(
+ t =>
+ t.IsSubclassOf(typeof (RPCServerMessage))
+ &&
+ ((PacketAttribute) t.GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type == type
+ ).ToArray();
+ if (!types.Any())
+ {
+ throw new ProtocolViolationException("Received packet of unknown type");
+ }
+ if (types.Count() > 1)
+ {
+#if DEBUG
+ Debug.Fail(string.Format("Bug in program code: Found more than 1 type for packet ID {0}", type));
+#else
+ // TODO: log4net
+ return null;
+#endif
+ }
+ packet = (RPCServerMessage)ProtoBuf.Serializer.NonGeneric.Deserialize(
+ types.Single(),
+ ms
+ );
+ }
+
+ packet.MessageId = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 12));
+
+ return packet;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/StorageGetPublisherFileMessage.cs b/src/libnpsharp/RPC/Packets/StorageGetPublisherFileMessage.cs
new file mode 100644
index 0000000..86b6732
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/StorageGetPublisherFileMessage.cs
@@ -0,0 +1,12 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [ProtoContract]
+ [Packet(1101)]
+ class StorageGetPublisherFileMessage : RPCClientMessage
+ {
+ [ProtoMember(1)]
+ public string FileName { get; set; }
+ }
+}
diff --git a/src/libnpsharp/RPC/Packets/StorageGetUserFileMessage.cs b/src/libnpsharp/RPC/Packets/StorageGetUserFileMessage.cs
new file mode 100644
index 0000000..0b27e3b
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/StorageGetUserFileMessage.cs
@@ -0,0 +1,15 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1102)]
+ [ProtoContract]
+ class StorageGetUserFileMessage : RPCClientMessage
+ {
+ [ProtoMember(1)]
+ public string FileName { get; set; }
+
+ [ProtoMember(2)]
+ public ulong NPID { get; set; } // SERIOUSLY WHY IS THIS EVEN HERE
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/StoragePublisherFileMessage.cs b/src/libnpsharp/RPC/Packets/StoragePublisherFileMessage.cs
new file mode 100644
index 0000000..77881b2
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/StoragePublisherFileMessage.cs
@@ -0,0 +1,18 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1111)]
+ [ProtoContract]
+ class StoragePublisherFileMessage : RPCServerMessage
+ {
+ [ProtoMember(1)]
+ public int Result { get; set; }
+
+ [ProtoMember(2)]
+ public string FileName { get; set; }
+
+ [ProtoMember(3)]
+ public byte[] FileData { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/StorageSendRandomStringMessage.cs b/src/libnpsharp/RPC/Packets/StorageSendRandomStringMessage.cs
new file mode 100644
index 0000000..6943bf7
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/StorageSendRandomStringMessage.cs
@@ -0,0 +1,12 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1104)]
+ [ProtoContract]
+ class StorageSendRandomStringMessage : RPCClientMessage
+ {
+ [ProtoMember(1)]
+ public string RandomString { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/StorageUserFileMessage.cs b/src/libnpsharp/RPC/Packets/StorageUserFileMessage.cs
new file mode 100644
index 0000000..3a2b85a
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/StorageUserFileMessage.cs
@@ -0,0 +1,21 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1112)]
+ [ProtoContract]
+ class StorageUserFileMessage : RPCServerMessage
+ {
+ [ProtoMember(1)]
+ public int Result { get; set; }
+
+ [ProtoMember(2)]
+ public string FileName { get; set; }
+
+ [ProtoMember(3)]
+ public ulong NPID { get; set; }
+
+ [ProtoMember(4)]
+ public byte[] FileData { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/StorageWriteUserFileMessage.cs b/src/libnpsharp/RPC/Packets/StorageWriteUserFileMessage.cs
new file mode 100644
index 0000000..b9a9dd9
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/StorageWriteUserFileMessage.cs
@@ -0,0 +1,18 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1103)]
+ [ProtoContract]
+ class StorageWriteUserFileMessage : RPCClientMessage
+ {
+ [ProtoMember(1)]
+ public string FileName { get; set; }
+
+ [ProtoMember(2)]
+ public ulong NPID { get; set; }
+
+ [ProtoMember(3)]
+ public byte[] FileData { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Packets/StorageWriteUserFileResultMessage.cs b/src/libnpsharp/RPC/Packets/StorageWriteUserFileResultMessage.cs
new file mode 100644
index 0000000..8c7f463
--- /dev/null
+++ b/src/libnpsharp/RPC/Packets/StorageWriteUserFileResultMessage.cs
@@ -0,0 +1,18 @@
+using ProtoBuf;
+
+namespace NPSharp.RPC.Packets
+{
+ [Packet(1113)]
+ [ProtoContract]
+ class StorageWriteUserFileResultMessage : RPCServerMessage
+ {
+ [ProtoMember(1)]
+ public int Result { get; set; }
+
+ [ProtoMember(2)]
+ public string FileName { get; set; }
+
+ [ProtoMember(3)]
+ public ulong NPID { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/RPCClientStream.cs b/src/libnpsharp/RPC/RPCClientStream.cs
new file mode 100644
index 0000000..c09522c
--- /dev/null
+++ b/src/libnpsharp/RPC/RPCClientStream.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using NPSharp.RPC.Packets;
+
+namespace NPSharp.RPC
+{
+ ///
+ /// Represents a low-level client stream which can communicate with an NP server using RPC packets.
+ ///
+ public class RPCClientStream
+ {
+ private NetworkStream _ns;
+ private uint _id;
+
+ private readonly string _host;
+ private readonly ushort _port;
+
+ private readonly Dictionary> _callbacks = new Dictionary>();
+
+ ///
+ /// Initializes an RPC connection stream with a specified host and port.
+ ///
+ /// The host to connect to.
+ /// The port to use. Default: 3025.
+ public RPCClientStream(string host, ushort port = 3025)
+ {
+ _host = host;
+ _port = port;
+ }
+
+ ///
+ /// Opens the RPC stream to the NP server.
+ ///
+ /// True if the connection succeeded, otherwise false.
+ public bool Open()
+ {
+ // Connection already established?
+ if (_ns != null)
+ throw new InvalidOperationException("Connection already opened");
+
+ var tcp = new TcpClient();
+ try
+ {
+ tcp.Connect(_host, _port);
+ }
+ catch
+ {
+ return false;
+ }
+ _ns = tcp.GetStream();
+ return true;
+ }
+
+ ///
+ /// Closes the connection with the NP server.
+ ///
+ ///
+ public void Close(int timeout = 2000)
+ {
+ // Connection already closed?
+ if (_ns == null)
+ throw new InvalidOperationException("Connection already closed");
+
+ try
+ {
+ _ns.Close(timeout);
+ _ns.Dispose();
+ }
+ finally
+ {
+ _ns = null;
+ }
+ }
+
+ ///
+ /// Attaches a callback to the next message being sent out. This allows handling response packets.
+ ///
+ /// The method to call when we receive a response to the next message
+ public void AttachCallback(Action callback)
+ {
+ if (_callbacks.ContainsKey(_id))
+ throw new Exception("There is already a callback for the current message. You can only add max. one callback.");
+ _callbacks.Add(_id, callback);
+ }
+
+ // TODO: Exposure of message ID needed or no?
+ ///
+ /// Sends out an RPC message.
+ ///
+ /// The RPC message to send out.
+ /// The new ID of the message.
+ public uint Send(RPCClientMessage message)
+ {
+ if (_ns == null)
+ throw new InvalidOperationException("You need to open the stream first.");
+
+ var buffer = message.Serialize(_id);
+
+ _ns.Write(buffer, 0, buffer.Length);
+
+ return _id++;
+ }
+
+ ///
+ /// Waits for the next RPC message from the server and reads it.
+ ///
+ /// The received server message.
+ public RPCServerMessage Read()
+ {
+ if (_ns == null)
+ throw new InvalidOperationException("You need to open the stream first.");
+
+ var message = RPCServerMessage.Deserialize(_ns);
+
+ if (message == null)
+ return null;
+
+ if (!_callbacks.ContainsKey(message.MessageId))
+ return message;
+
+ _callbacks[message.MessageId].Invoke(message);
+ _callbacks.Remove(message.MessageId);
+
+ return message;
+ }
+ }
+}