diff --git a/src/libnpsharp/AuthenticationResult.cs b/src/libnpsharp/AuthenticationResult.cs
new file mode 100644
index 0000000..2b9ba13
--- /dev/null
+++ b/src/libnpsharp/AuthenticationResult.cs
@@ -0,0 +1,35 @@
+using NPSharp.Steam;
+
+namespace NPSharp
+{
+ ///
+ /// Represents details about the outcome of an authentication attempt.
+ ///
+ public class AuthenticationResult
+ {
+ ///
+ /// Constructs an authentication result instance.
+ ///
+ ///
+ /// Set this to null if authentication should fail, otherwise use an instance of a steam ID which is
+ /// unique to the user.
+ ///
+ public AuthenticationResult(CSteamID npid = null)
+ {
+ UserID = npid;
+ }
+
+ ///
+ /// True if authentiation succeeded, otherwise false.
+ ///
+ public bool Result
+ {
+ get { return UserID != null; }
+ }
+
+ ///
+ /// The assigned user ID by the authentication provider. Can be null for failed authentication attempts.
+ ///
+ public CSteamID UserID { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/ClientEventArgs.cs b/src/libnpsharp/ClientEventArgs.cs
index fde6690..0ed969d 100644
--- a/src/libnpsharp/ClientEventArgs.cs
+++ b/src/libnpsharp/ClientEventArgs.cs
@@ -4,11 +4,11 @@ namespace NPSharp
{
public class ClientEventArgs : EventArgs
{
- internal ClientEventArgs(NPServer.NPServerClient client)
+ internal ClientEventArgs(NPServerClient client)
{
Client = client;
}
- public NPServer.NPServerClient Client { get; private set; }
+ public NPServerClient Client { get; private set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/libnpsharp/ClientEventHandler.cs b/src/libnpsharp/ClientEventHandler.cs
new file mode 100644
index 0000000..21d81a5
--- /dev/null
+++ b/src/libnpsharp/ClientEventHandler.cs
@@ -0,0 +1,9 @@
+namespace NPSharp
+{
+ ///
+ /// A delegate for all general client-related events.
+ ///
+ /// The instance of the NP server
+ /// All related arguments to this event
+ public delegate void ClientEventHandler(NPServer sender, ClientEventArgs args);
+}
\ No newline at end of file
diff --git a/src/libnpsharp/IAuthenticationHandler.cs b/src/libnpsharp/IAuthenticationHandler.cs
new file mode 100644
index 0000000..6f1136e
--- /dev/null
+++ b/src/libnpsharp/IAuthenticationHandler.cs
@@ -0,0 +1,41 @@
+namespace NPSharp
+{
+ ///
+ /// Represents a handler for all authentication-related requests.
+ ///
+ public interface IAuthenticationHandler
+ {
+ ///
+ /// Authenticates a user based on username and password.
+ ///
+ /// The NP server client to authenticate
+ /// The username to use for authentication
+ /// The password to use for authentication
+ /// An instance of
+ AuthenticationResult AuthenticateUser(NPServerClient client, string username, string password);
+
+ ///
+ /// Authenticates a user based on a session token.
+ ///
+ /// The NP server client to authenticate
+ /// The session token to use for authentication
+ /// An instance of
+ AuthenticationResult AuthenticateUser(NPServerClient client, string token);
+
+ ///
+ /// Authenticates a dedicated server based on its license key.
+ ///
+ /// The NP server client of the dedicated server to authenticate
+ /// The license key to use for authentication
+ /// An instance of
+ AuthenticationResult AuthenticateServer(NPServerClient client, string licenseKey);
+
+ ///
+ /// Validates a ticket.
+ ///
+ /// The NP server client of the user who is trying to get the ticket validated
+ /// The server that the user wants to connect to using this ticket
+ /// A determining if the ticket is valid
+ TicketValidationResult ValidateTicket(NPServerClient client, NPServerClient server);
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/IFileServingHandler.cs b/src/libnpsharp/IFileServingHandler.cs
new file mode 100644
index 0000000..7fecba9
--- /dev/null
+++ b/src/libnpsharp/IFileServingHandler.cs
@@ -0,0 +1,32 @@
+namespace NPSharp
+{
+ ///
+ /// Represents a handler for all file-related requests.
+ ///
+ public interface IFileServingHandler
+ {
+ ///
+ /// Reads a file assigned to the user.
+ ///
+ /// The file contents as byte array or null if the file could not be read, opened or generated
+ /// NP server client of the user
+ /// The file name
+ byte[] ReadUserFile(NPServerClient client, string file);
+
+ ///
+ /// Reads a publisher file.
+ ///
+ /// The file contents as byte array or null if the file could not be read, opened or generated
+ /// NP server client of the user
+ /// The file name
+ byte[] ReadPublisherFile(NPServerClient client, string file);
+
+ ///
+ /// Writes a file and assigns it to the client user.
+ ///
+ /// NP server client of the user
+ /// The file name
+ /// The file contents as byte array
+ void WriteUserFile(NPServerClient client, string file, byte[] data);
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/IFriendsHandler.cs b/src/libnpsharp/IFriendsHandler.cs
new file mode 100644
index 0000000..916b14d
--- /dev/null
+++ b/src/libnpsharp/IFriendsHandler.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using NPSharp.RPC.Messages;
+
+namespace NPSharp
+{
+ ///
+ /// Represents a handler for all friends-related requests.
+ ///
+ public interface IFriendsHandler
+ {
+ ///
+ /// Fetches all friends of the connected user.
+ ///
+ /// The NP server client of the user
+ /// All friend details found for the user
+ IEnumerable GetFriends(NPServerClient client);
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/IUserAvatarHandler.cs b/src/libnpsharp/IUserAvatarHandler.cs
new file mode 100644
index 0000000..24bfd69
--- /dev/null
+++ b/src/libnpsharp/IUserAvatarHandler.cs
@@ -0,0 +1,14 @@
+using NPSharp.Steam;
+
+namespace NPSharp
+{
+ ///
+ /// Represents a handler for all user avatar-related requests.
+ ///
+ public interface IUserAvatarHandler
+ {
+ byte[] GetUserAvatar(CSteamID id);
+
+ byte[] GetDefaultAvatar();
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/NPClient.cs b/src/libnpsharp/NPClient.cs
index 461c2f9..c0d960b 100644
--- a/src/libnpsharp/NPClient.cs
+++ b/src/libnpsharp/NPClient.cs
@@ -102,7 +102,7 @@ namespace NPSharp
_log.Debug("Disconnect() start");
_cancellationTokenSource.Cancel(true);
- // TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read)
+ // TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read)
//_procTask.Wait(_cancellationToken);
_rpc.Close();
diff --git a/src/libnpsharp/NPServer.cs b/src/libnpsharp/NPServer.cs
index 00002b8..fdeb0b0 100644
--- a/src/libnpsharp/NPServer.cs
+++ b/src/libnpsharp/NPServer.cs
@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using System.Net.Configuration;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
using log4net;
using NPSharp.RPC;
using NPSharp.RPC.Messages;
@@ -12,34 +15,118 @@ namespace NPSharp
{
public class NPServer
{
- public delegate void ClientEventHandler(object sender, ClientEventArgs args);
-
- public IFileServingHandler FileHandler { get; set; }
-
- public IUserAvatarHandler UserAvatarHandler { get; set; }
-
- public interface IUserAvatarHandler
- {
- byte[] GetUserAvatar(CSteamID id);
-
- byte[] GetDefaultAvatar();
- }
-
private readonly List _clients;
private readonly ILog _log;
+ private readonly Socket _socket;
+ private readonly ushort _port;
- public NPServer()
+ ///
+ /// Constructs a new NP server.
+ ///
+ public NPServer(ushort port = 3025)
{
_log = LogManager.GetLogger("NPServer");
_clients = new List();
+
+ _socket = new Socket(SocketType.Stream, ProtocolType.IP);
+ _port = port;
}
- private void _handleClient(NPServerClient client)
+ ///
+ /// Starts up the NP server.
+ ///
+ public void Start()
{
+ if (_socket.IsBound)
+ throw new InvalidOperationException("This server is already running");
+
+ try
+ {
+ // ReSharper disable once ObjectCreationAsStatement
+ // TODO: fix this shit permission code
+ new SocketPermission(NetworkAccess.Accept, TransportType.Tcp, "", _port);
+ }
+ catch
+ {
+ _log.Error("Socket permission request failed, can't start server.");
+ throw new SocketException(10013 /* Permission denied */);
+ }
+
+ _socket.Bind(new IPEndPoint(IPAddress.IPv6Any, _port));
+ _socket.Listen(4);
+
+ Task.Factory.StartNew(() =>
+ {
+ _log.Debug("Listener loop started");
+ var allDone = new ManualResetEvent(false);
+ while (_socket != null && _socket.IsBound)
+ {
+ allDone.Reset();
+ _socket.BeginAccept(ar =>
+ {
+ _log.Debug("Async accept client start");
+ allDone.Set();
+
+ var serverSocket = (Socket) ar.AsyncState;
+ var clientSocket = serverSocket.EndAccept(ar);
+
+ var npsc = new NPServerClient(this, new RPCServerStream(clientSocket));
+
+ _log.Debug("Async accept client end");
+
+ _handleClient(npsc);
+ }, _socket);
+ allDone.WaitOne();
+ }
+ _log.Debug("Listener loop shut down");
+ });
+ }
+
+ ///
+ /// Shuts down all connections and stops the NP server.
+ ///
+ public void Stop()
+ {
+ _socket.Shutdown(SocketShutdown.Both);
+ }
+
+ ///
+ /// The handler to use for file requests to this NP server.
+ ///
+ public IFileServingHandler FileHandler { get; set; }
+
+ ///
+ /// The handler to use for user avatar requests to this NP server.
+ ///
+ public IUserAvatarHandler UserAvatarHandler { get; set; }
+
+ ///
+ /// Returns all currently connected clients
+ ///
+ public NPServerClient[] Clients
+ {
+ get { return _clients.ToArray(); } // Avoid race condition by IEnum changes
+ }
+
+ ///
+ /// The handler to use for authentication requests to this NP server.
+ ///
+ public IAuthenticationHandler AuthenticationHandler { get; set; }
+
+ ///
+ /// The handler to use for friends-related requests to this NP server.
+ ///
+ public IFriendsHandler FriendsHandler { get; set; }
+
+ internal void _handleClient(NPServerClient client)
+ {
+ _log.Debug("Client now being handled");
+
#region RPC authentication message handlers
+
client.RPC.AttachHandlerForMessageType(msg =>
{
- var result = new AuthenticationResult();;
+ var result = new AuthenticationResult();
if (AuthenticationHandler != null)
{
try
@@ -109,11 +196,11 @@ namespace NPSharp
client.UserID = result.UserID;
// Send "online" notification to all friends of this player
- foreach (var fconn in client.FriendConnections)
+ foreach (NPServerClient fconn in client.FriendConnections)
{
fconn.RPC.Send(new FriendsPresenceMessage
{
- CurrentServer = client.DedicatedServer == null ? 0 : client.DedicatedServer.NPID,
+ CurrentServer = client.DedicatedServer == null ? 0 : client.DedicatedServer.UserID,
Friend = client.UserID,
Presence = client.PresenceData,
PresenceState = client.DedicatedServer == null ? 1 : 2
@@ -157,7 +244,7 @@ namespace NPSharp
client.UserID = result.UserID;
// Send "online" notification to all friends of this player
- foreach (var fconn in client.FriendConnections)
+ foreach (NPServerClient fconn in client.FriendConnections)
{
fconn.RPC.Send(new FriendsPresenceMessage
{
@@ -185,7 +272,6 @@ namespace NPSharp
_log.DebugFormat("Ticket[Version={0},ServerID={1},Time={2}]", ticketData.Version,
ticketData.ServerID, ticketData.Time);
-
}
catch (ArgumentException error)
{
@@ -198,7 +284,9 @@ namespace NPSharp
{
if (ticketData.ClientID == client.UserID) // NPID enforcement
{
- var s = _clients.Where(c => c.IsServer && !c.IsDirty && c.UserID == ticketData.ServerID).ToArray();
+ var s =
+ _clients.Where(c => c.IsServer && !c.IsDirty && c.UserID == ticketData.ServerID)
+ .ToArray();
if (s.Any())
{
@@ -236,12 +324,14 @@ namespace NPSharp
Result = validTicket ? 0 : 1
});
});
+
#endregion
#region RPC friend message handlers
+
client.RPC.AttachHandlerForMessageType(msg =>
{
- foreach (var pdata in msg.Presence)
+ foreach (FriendsPresence pdata in msg.Presence)
{
client.SetPresence(pdata.Key, pdata.Value);
_log.DebugFormat("Client says presence \"{0}\" is \"{1}\"", pdata.Key, pdata.Value);
@@ -252,10 +342,10 @@ namespace NPSharp
{
// Why so goddamn complicated, NTA. Fuck.
// TODO: Not compatible with non-public accounts
- var npid = new CSteamID((uint) msg.Guid, EUniverse.Public,
+ ulong npid = new CSteamID((uint) msg.Guid, EUniverse.Public,
EAccountType.Individual).ConvertToUint64();
- var avatar = UserAvatarHandler.GetUserAvatar(npid) ?? UserAvatarHandler.GetDefaultAvatar();
+ byte[] avatar = UserAvatarHandler.GetUserAvatar(npid) ?? UserAvatarHandler.GetDefaultAvatar();
client.RPC.Send(new FriendsGetUserAvatarResultMessage
{
@@ -269,11 +359,197 @@ namespace NPSharp
{
// TODO
});
+
#endregion
- // TODO: RPC message handling for storage
+ #region RPC storage message handlers
+
+ client.RPC.AttachHandlerForMessageType(msg =>
+ {
+ if (FileHandler == null)
+ {
+ client.RPC.Send(new StoragePublisherFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName
+ });
+ return;
+ }
+
+ try
+ {
+ if (client.UserID == null)
+ {
+ client.RPC.Send(new StoragePublisherFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName
+ });
+ _log.WarnFormat("Client tried to read publisher file {0} while not logged in", msg.FileName);
+ return;
+ }
+
+ byte[] data = FileHandler.ReadPublisherFile(client, msg.FileName);
+ if (data == null)
+ {
+ client.RPC.Send(new StoragePublisherFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 1,
+ FileName = msg.FileName
+ });
+ _log.DebugFormat("Could not open publisher file {0}", msg.FileName);
+ return;
+ }
+
+ client.RPC.Send(new StoragePublisherFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 0,
+ FileName = msg.FileName
+ });
+ _log.DebugFormat("Sent publisher file {0}", msg.FileName);
+ }
+ catch (Exception error)
+ {
+ _log.Warn("GetPublisherFile handler error", error);
+ client.RPC.Send(new StoragePublisherFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName
+ });
+ }
+ });
+
+ client.RPC.AttachHandlerForMessageType(msg =>
+ {
+ if (FileHandler == null)
+ {
+ client.RPC.Send(new StorageUserFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName
+ });
+ return;
+ }
+
+ try
+ {
+ if (client.UserID == null)
+ {
+ client.RPC.Send(new StorageUserFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName,
+ NPID = client.UserID
+ });
+ _log.WarnFormat("Client tried to read user file {0} while not logged in", msg.FileName);
+ return;
+ }
+
+ byte[] data = FileHandler.ReadUserFile(client, msg.FileName);
+ if (data == null)
+ {
+ client.RPC.Send(new StorageUserFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 1,
+ FileName = msg.FileName,
+ NPID = client.UserID
+ });
+ _log.DebugFormat("Could not open user file {0}", msg.FileName);
+ return;
+ }
+
+ client.RPC.Send(new StorageUserFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 0,
+ FileName = msg.FileName,
+ FileData = data,
+ NPID = client.UserID
+ });
+ _log.DebugFormat("Sent user file {0}", msg.FileName);
+ }
+ catch (Exception error)
+ {
+ _log.Warn("GetUserFile handler error", error);
+ client.RPC.Send(new StorageUserFileMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName
+ });
+ }
+ });
+
+ client.RPC.AttachHandlerForMessageType(msg =>
+ {
+ // TODO: Handle "random string" messages
+ });
+
+ client.RPC.AttachHandlerForMessageType(msg =>
+ {
+ if (FileHandler == null)
+ {
+ client.RPC.Send(new StorageWriteUserFileResultMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName
+ });
+ return;
+ }
+
+ try
+ {
+ if (client.UserID == null)
+ {
+ client.RPC.Send(new StorageWriteUserFileResultMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName,
+ NPID = client.UserID
+ });
+ _log.WarnFormat("Client tried to write user file {0} while not logged in", msg.FileName);
+ return;
+ }
+
+ FileHandler.WriteUserFile(client, msg.FileName, msg.FileData);
+
+ client.RPC.Send(new StorageWriteUserFileResultMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 0,
+ FileName = msg.FileName,
+ NPID = client.UserID
+ });
+ _log.DebugFormat("Received and wrote user file {0}", msg.FileName);
+ }
+ catch (Exception error)
+ {
+ _log.Warn("WriteUserFile handler error", error);
+ client.RPC.Send(new StorageWriteUserFileResultMessage
+ {
+ MessageId = msg.MessageId,
+ Result = 2,
+ FileName = msg.FileName
+ });
+ }
+ });
// TODO: RPC message handling for MessagingSendData
- // TODO: RPC message handling for server sessions
+
+ #endregion
+
+ #region RPC server session message handler
+
+ #endregion
_clients.Add(client);
try
@@ -292,129 +568,21 @@ namespace NPSharp
catch (Exception error)
{
_log.Error("Error in client handling loop", error);
- client.RPC.Send(new CloseAppMessage{Reason="Server-side error occurred, try again later."});
+ client.RPC.Send(new CloseAppMessage {Reason = "Server-side error occurred, try again later."});
client.RPC.Close();
}
_clients.Remove(client);
}
- public NPServerClient[] Clients
- {
- get { return _clients.ToArray(); } // Avoid race condition by IEnum changes
- }
-
- public interface IFileServingHandler
- {
- Stream ReadUserFile(NPServerClient client, string file);
-
- Stream ReadPublisherFile(NPServerClient client, string file);
- }
-
- public IAuthenticationHandler AuthenticationHandler { get; set; }
-
- public interface IAuthenticationHandler
- {
- AuthenticationResult AuthenticateUser(NPServerClient client, string username, string password);
-
- AuthenticationResult AuthenticateUser(NPServerClient client, string token);
-
- AuthenticationResult AuthenticateServer(NPServerClient client, string licenseKey);
-
- TicketValidationResult ValidateTicket(NPServerClient client, NPServerClient server);
- }
-
- public class AuthenticationResult
- {
- ///
- /// Constructs an authentication result instance.
- ///
- /// Set this to null if authentication should fail, otherwise use an instance of a steam ID which is unique to the user.
- public AuthenticationResult(CSteamID npid = null)
- {
- UserID = npid;
- }
-
- public bool Result { get { return UserID != null; } }
-
- public CSteamID UserID { get; private set; }
- }
-
- public enum TicketValidationResult
- {
- Valid = 0,
- Invalid = 1
- }
-
- public IFriendsHandler FriendsHandler { get; set; }
-
- public interface IFriendsHandler
- {
- IEnumerable GetFriends(NPServerClient client);
-
- /*
- void SetFriendStatus(NPServerClient client, PresenceState presenceState,
- Dictionary presenceData, ulong serverID)
- {
-
- }
- */
- }
-
- public class NPServerClient
- {
- internal NPServerClient(NPServer np, RPCServerStream rpcclient)
- {
- NP = np;
- RPC = rpcclient;
- }
-
- internal readonly RPCServerStream RPC;
-
- internal readonly NPServer NP;
-
- public CSteamID UserID { get; internal set; }
-
- public IEnumerable Friends
- {
- get { return NP.FriendsHandler.GetFriends(this).ToArray(); }
- }
-
- public IEnumerable FriendConnections
- {
- get { return NP.Clients.Where(c => Friends.Any(f => f.NPID == c.NPID)); }
- }
-
- internal NPServerClient DedicatedServer;
-
- private readonly Dictionary _presence = new Dictionary();
-
- public FriendsPresence[] PresenceData { get
- {
- return _presence.Select(i => new FriendsPresence {Key = i.Key, Value = i.Value}).ToArray();
- } }
-
- public bool IsServer { get; set; }
- public bool IsDirty { get; set; }
- public int GroupID { get; set; }
-
- internal void SetPresence(string key, string value)
- {
- if (!_presence.ContainsKey(key))
- _presence.Add(key, value);
- else
- _presence[key] = value;
- }
- }
-
- public enum PresenceState
- {
- Offline = 0,
- Online = 1,
- Playing = 2
- }
-
+ ///
+ /// Triggered when a client has connected but is not authenticating yet.
+ ///
public event ClientEventHandler ClientConnected;
+ ///
+ /// Invokes the event.
+ ///
+ /// The client
protected virtual void OnClientConnected(NPServerClient client)
{
var handler = ClientConnected;
@@ -422,8 +590,15 @@ namespace NPSharp
if (handler != null) handler(this, args);
}
+ ///
+ /// Triggered when a client has disconnected.
+ ///
public event ClientEventHandler ClientDisconnected;
+ ///
+ /// Invokes the event.
+ ///
+ /// The client
protected virtual void OnClientDisconnected(NPServerClient client)
{
var handler = ClientDisconnected;
@@ -431,8 +606,15 @@ namespace NPSharp
if (handler != null) handler(this, args);
}
+ ///
+ /// Triggered when a client has authenticated successfully.
+ ///
public event ClientEventHandler ClientAuthenticated;
+ ///
+ /// Invokes the event.
+ ///
+ /// The client
protected virtual void OnClientAuthenticated(NPServerClient client)
{
var handler = ClientAuthenticated;
@@ -440,4 +622,4 @@ namespace NPSharp
if (handler != null) handler(this, args);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/libnpsharp/NPServerClient.cs b/src/libnpsharp/NPServerClient.cs
new file mode 100644
index 0000000..3b4def0
--- /dev/null
+++ b/src/libnpsharp/NPServerClient.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Linq;
+using NPSharp.RPC;
+using NPSharp.RPC.Messages;
+using NPSharp.Steam;
+
+namespace NPSharp
+{
+ ///
+ /// Represents a remote client connection to an NP server.
+ ///
+ public class NPServerClient
+ {
+ internal readonly NPServer NP;
+ internal readonly RPCServerStream RPC;
+ private readonly Dictionary _presence = new Dictionary();
+ internal NPServerClient DedicatedServer;
+
+ internal NPServerClient(NPServer np, RPCServerStream rpcclient)
+ {
+ NP = np;
+ RPC = rpcclient;
+ }
+
+ public CSteamID UserID { get; internal set; }
+
+ public IEnumerable Friends
+ {
+ get { return NP.FriendsHandler.GetFriends(this).ToArray(); }
+ }
+
+ public IEnumerable FriendConnections
+ {
+ get { return NP.Clients.Where(c => Friends.Any(f => f.NPID == c.UserID)); }
+ }
+
+ public FriendsPresence[] PresenceData
+ {
+ get { return _presence.Select(i => new FriendsPresence {Key = i.Key, Value = i.Value}).ToArray(); }
+ }
+
+ public bool IsServer { get; set; }
+ public bool IsDirty { get; set; }
+ public int GroupID { get; set; }
+
+ internal void SetPresence(string key, string value)
+ {
+ if (!_presence.ContainsKey(key))
+ _presence.Add(key, value);
+ else
+ _presence[key] = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/PresenceState.cs b/src/libnpsharp/PresenceState.cs
new file mode 100644
index 0000000..74f19a0
--- /dev/null
+++ b/src/libnpsharp/PresenceState.cs
@@ -0,0 +1,12 @@
+namespace NPSharp
+{
+ ///
+ /// Represents the friend's current presence activity.
+ ///
+ public enum PresenceState
+ {
+ Offline = 0,
+ Online = 1,
+ Playing = 2
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerMessage.cs
index 075107c..7f2b43b 100644
--- a/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerMessage.cs
+++ b/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerMessage.cs
@@ -6,7 +6,7 @@ namespace NPSharp.RPC.Messages
[ProtoContract]
public sealed class AuthenticateRegisterServerMessage : RPCClientMessage
{
- [ProtoMember(1, IsRequired=false)]
+ [ProtoMember(1, IsRequired = false)]
public string ConfigPath { get; set; }
}
}
\ No newline at end of file
diff --git a/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketResultMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketResultMessage.cs
index 30fa5f3..9015830 100644
--- a/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketResultMessage.cs
+++ b/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketResultMessage.cs
@@ -10,7 +10,7 @@ namespace NPSharp.RPC.Messages
[ProtoMember(1)]
public int Result { get; set; }
- [ProtoMember(2, DataFormat=DataFormat.FixedSize)]
+ [ProtoMember(2, DataFormat = DataFormat.FixedSize)]
public UInt64 NPID { get; set; }
[ProtoMember(3)]
diff --git a/src/libnpsharp/RPC/Messages/RPCMessage.cs b/src/libnpsharp/RPC/Messages/RPCMessage.cs
index bc0d170..d6b066f 100644
--- a/src/libnpsharp/RPC/Messages/RPCMessage.cs
+++ b/src/libnpsharp/RPC/Messages/RPCMessage.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
@@ -33,7 +34,9 @@ namespace NPSharp.RPC.Messages
{
var header = new byte[4*sizeof (uint)];
- while (sock.Connected && !sock.Poll(1000, SelectMode.SelectRead)) { }
+ while (sock.Connected && !sock.Poll(1000, SelectMode.SelectRead))
+ {
+ }
if (!sock.Connected)
{
@@ -41,7 +44,7 @@ namespace NPSharp.RPC.Messages
return null;
}
- var l = sock.Receive(header);
+ int l = sock.Receive(header);
if (l == 0)
{
Log.Debug("Received 0 bytes");
@@ -53,7 +56,7 @@ namespace NPSharp.RPC.Messages
throw new ProtocolViolationException("Received incomplete header");
}
- uint signature, length, type, pid;
+ uint signature, length, type, mid;
using (var ms = new MemoryStream(header))
{
using (var br = new BinaryReader(ms))
@@ -61,7 +64,7 @@ namespace NPSharp.RPC.Messages
signature = br.ReadUInt32();
length = br.ReadUInt32();
type = br.ReadUInt32();
- pid = br.ReadUInt32();
+ mid = br.ReadUInt32();
}
}
@@ -74,11 +77,11 @@ namespace NPSharp.RPC.Messages
throw new ProtocolViolationException("Received packet with invalid signature");
}
- T packet;
+ T message;
using (var ms = new MemoryStream(buffer))
{
- var types = Assembly.GetExecutingAssembly().GetTypes().Where(
+ Type[] types = Assembly.GetExecutingAssembly().GetTypes().Where(
t =>
t.IsSubclassOf(typeof (T))
&&
@@ -96,27 +99,27 @@ namespace NPSharp.RPC.Messages
return null;
#endif
}
- packet = (T) Serializer.NonGeneric.Deserialize(
+ message = (T) Serializer.NonGeneric.Deserialize(
types.Single(),
ms
);
}
- packet.MessageId = pid;
+ message.MessageId = mid;
#if DEBUG
- Log.DebugFormat("{3}[ID={0},Type={1},TypeName={2}] {{", pid, packet.GetTypeId(), packet.GetType().Name,
+ Log.DebugFormat("{3}[ID={0},Type={1},TypeName={2}] {{", mid, message.GetTypeId(), message.GetType().Name,
typeof (T).Name);
foreach (
- var prop in
- packet.GetType().GetProperties().Where(p => !(p.DeclaringType == typeof (RPCServerMessage))))
+ PropertyInfo prop in
+ message.GetType().GetProperties().Where(p => !(p.DeclaringType == typeof (RPCServerMessage))))
{
- Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(packet));
+ Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(message));
}
Log.DebugFormat("}} // deserialized from {0} bytes", header.Length + buffer.Length);
#endif
- return packet;
+ return message;
}
public byte[] Serialize()
@@ -149,7 +152,7 @@ namespace NPSharp.RPC.Messages
#if DEBUG
Log.DebugFormat("{3}[ID={0},Type={1},TypeName={2}] {{", MessageId, GetTypeId(), GetType().Name,
GetType().Name);
- foreach (var prop in GetType().GetProperties())
+ foreach (PropertyInfo prop in GetType().GetProperties())
{
Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(this));
}
diff --git a/src/libnpsharp/RPC/RPCStream.cs b/src/libnpsharp/RPC/RPCStream.cs
index 6a6f803..c272639 100644
--- a/src/libnpsharp/RPC/RPCStream.cs
+++ b/src/libnpsharp/RPC/RPCStream.cs
@@ -100,7 +100,7 @@ namespace NPSharp.RPC
TypeCallbacks.Add(
new KeyValuePair>(
((PacketAttribute) typeof (T).GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type,
- (Action)callback));
+ msg => callback.Invoke((T)msg)));
}
///
@@ -131,13 +131,15 @@ namespace NPSharp.RPC
if (_sock == null)
throw new InvalidOperationException("You need to open the stream first.");
- message.MessageId = MessageID;
+ if (message.MessageId == default(uint))
+ message.MessageId = MessageID;
- var buffer = message.Serialize();
+ byte[] buffer = message.Serialize();
_sock.Send(buffer);
- IterateMessageID();
+ if (typeof (TSend) == typeof (RPCClientMessage))
+ IterateMessageID();
}
///
@@ -156,6 +158,9 @@ namespace NPSharp.RPC
return null;
}
+ if (typeof (TRecv) == typeof (RPCClientMessage))
+ MessageID = message.MessageId;
+
// Callbacks
foreach (var cbi in IDCallbacks.Where(p => p.Key == message.MessageId))
cbi.Value.Invoke(message);
diff --git a/src/libnpsharp/Steam/CSteamID.cs b/src/libnpsharp/Steam/CSteamID.cs
index b8336c4..924a0e0 100644
--- a/src/libnpsharp/Steam/CSteamID.cs
+++ b/src/libnpsharp/Steam/CSteamID.cs
@@ -174,7 +174,9 @@ namespace NPSharp.Steam
{
case EAccountType.Invalid:
case EAccountType.Individual:
- return AccountUniverse <= EUniverse.Public ? String.Format("STEAM_0:{0}:{1}", AccountID & 1, AccountID >> 1) : String.Format("STEAM_{2}:{0}:{1}", AccountID & 1, AccountID >> 1, (int) AccountUniverse);
+ return AccountUniverse <= EUniverse.Public
+ ? String.Format("STEAM_0:{0}:{1}", AccountID & 1, AccountID >> 1)
+ : String.Format("STEAM_{2}:{0}:{1}", AccountID & 1, AccountID >> 1, (int) AccountUniverse);
default:
return Convert.ToString(this);
}
diff --git a/src/libnpsharp/Ticket.cs b/src/libnpsharp/Ticket.cs
index ddf61d3..551c613 100644
--- a/src/libnpsharp/Ticket.cs
+++ b/src/libnpsharp/Ticket.cs
@@ -1,10 +1,5 @@
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using log4net.Appender;
namespace NPSharp
{
@@ -12,7 +7,7 @@ namespace NPSharp
{
public Ticket(byte[] data)
{
- if (data.Length < sizeof(uint) + (sizeof(ulong) * 2) + sizeof(uint))
+ if (data.Length < sizeof (uint) + (sizeof (ulong)*2) + sizeof (uint))
{
throw new ArgumentException("Data buffer too short");
}
@@ -35,4 +30,4 @@ namespace NPSharp
public uint Time { get; private set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/libnpsharp/TicketValidationResult.cs b/src/libnpsharp/TicketValidationResult.cs
new file mode 100644
index 0000000..015fc04
--- /dev/null
+++ b/src/libnpsharp/TicketValidationResult.cs
@@ -0,0 +1,11 @@
+namespace NPSharp
+{
+ ///
+ /// Represents the outcome of a ticket validation attempt.
+ ///
+ public enum TicketValidationResult
+ {
+ Valid = 0,
+ Invalid = 1
+ }
+}
\ No newline at end of file
diff --git a/src/libnpsharp/libnpsharp.csproj b/src/libnpsharp/libnpsharp.csproj
index 9ba3807..ef672be 100644
--- a/src/libnpsharp/libnpsharp.csproj
+++ b/src/libnpsharp/libnpsharp.csproj
@@ -54,10 +54,18 @@
+
+
+
+
+
+
+
+
@@ -104,6 +112,7 @@
+
diff --git a/src/npserv/DummyAuthenticationHandler.cs b/src/npserv/DummyAuthenticationHandler.cs
new file mode 100644
index 0000000..bc98bf2
--- /dev/null
+++ b/src/npserv/DummyAuthenticationHandler.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NPSharp.Steam;
+
+namespace NPSharp.CommandLine.Server
+{
+ class DummyAuthenticationHandler : IAuthenticationHandler
+ {
+ private uint _userID = 0;
+
+ public DummyAuthenticationHandler()
+ {
+ // TODO: Listener on port 12003 accepting HTTP token retrievals
+ }
+
+ public AuthenticationResult AuthenticateUser(NPServerClient client, string username, string password)
+ {
+ return new AuthenticationResult(new CSteamID()
+ {
+ AccountID = _userID++
+ });
+ }
+
+ public AuthenticationResult AuthenticateUser(NPServerClient client, string token)
+ {
+ return new AuthenticationResult(new CSteamID()
+ {
+ AccountID = _userID++
+ });
+ }
+
+ public AuthenticationResult AuthenticateServer(NPServerClient client, string licenseKey)
+ {
+ throw new NotImplementedException();
+ }
+
+ public TicketValidationResult ValidateTicket(NPServerClient client, NPServerClient server)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/npserv/Program.cs b/src/npserv/Program.cs
index ec39285..32fd45c 100644
--- a/src/npserv/Program.cs
+++ b/src/npserv/Program.cs
@@ -1,9 +1,85 @@
-namespace NPSharp.CommandLine.Server
+using System;
+using System.Threading;
+using log4net;
+using log4net.Appender;
+using log4net.Config;
+using log4net.Core;
+using log4net.Layout;
+
+namespace NPSharp.CommandLine.Server
{
class Program
{
- static void Main(string[] args)
+ static void Main()
{
+
+ // log4net setup
+ if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
+ {
+ var appender = new ConsoleAppender
+ {
+#if DEBUG
+ Threshold = Level.Debug,
+#else
+ Threshold = Level.Info,
+#endif
+ Layout = new PatternLayout("<%d{HH:mm:ss}> [%logger:%thread] %level: %message%newline"),
+ };
+ BasicConfigurator.Configure(new IAppender[] { appender, new DebugAppender { Layout = appender.Layout, Threshold = Level.All } });
+ }
+ else
+ {
+ var appender = new ColoredConsoleAppender
+ {
+#if DEBUG
+ Threshold = Level.Debug,
+#else
+ Threshold = Level.Info,
+#endif
+ Layout = new PatternLayout("<%d{HH:mm:ss}> [%logger:%thread] %level: %message%newline"),
+ };
+ appender.AddMapping(new ColoredConsoleAppender.LevelColors
+ {
+ Level = Level.Debug,
+ ForeColor = ColoredConsoleAppender.Colors.Cyan | ColoredConsoleAppender.Colors.HighIntensity
+ });
+ appender.AddMapping(new ColoredConsoleAppender.LevelColors
+ {
+ Level = Level.Info,
+ ForeColor = ColoredConsoleAppender.Colors.Green | ColoredConsoleAppender.Colors.HighIntensity
+ });
+ appender.AddMapping(new ColoredConsoleAppender.LevelColors
+ {
+ Level = Level.Warn,
+ ForeColor = ColoredConsoleAppender.Colors.Purple | ColoredConsoleAppender.Colors.HighIntensity
+ });
+ appender.AddMapping(new ColoredConsoleAppender.LevelColors
+ {
+ Level = Level.Error,
+ ForeColor = ColoredConsoleAppender.Colors.Red | ColoredConsoleAppender.Colors.HighIntensity
+ });
+ appender.AddMapping(new ColoredConsoleAppender.LevelColors
+ {
+ Level = Level.Fatal,
+ ForeColor = ColoredConsoleAppender.Colors.White | ColoredConsoleAppender.Colors.HighIntensity,
+ BackColor = ColoredConsoleAppender.Colors.Red
+ });
+ appender.ActivateOptions();
+ BasicConfigurator.Configure(new IAppender[] { appender, new DebugAppender { Layout = appender.Layout, Threshold = Level.All } });
+ }
+
+ var log = LogManager.GetLogger("Main");
+
+ log.Info("Now starting NP server...");
+ var np = new NPServer(3036)
+ {
+ AuthenticationHandler = new DummyAuthenticationHandler(),
+ // TODO: Implement the other handlers
+ };
+ np.Start();
+ log.Info("NP server started up and is now ready.");
+
+ Thread.Sleep(Timeout.Infinite);
}
}
}
diff --git a/src/npserv/npserv.csproj b/src/npserv/npserv.csproj
index 6329db3..43c3d62 100644
--- a/src/npserv/npserv.csproj
+++ b/src/npserv/npserv.csproj
@@ -11,6 +11,8 @@
npserv
v4.5
512
+ ..\..\
+ true
true
@@ -37,22 +39,29 @@
$(SolutionDir)\bin\$(Configuration)\$(Platform)\
+
+ ..\..\packages\log4net.2.0.3\lib\net40-full\log4net.dll
+
-
-
-
-
-
+
+
+
+
+
+ {1a5ac63a-250e-4bc8-b81a-822ac31f5e37}
+ libnpsharp
+
+