From b13d462f0d6d4ca59471f83940f5cd380da62d33 Mon Sep 17 00:00:00 2001 From: icedream Date: Fri, 9 May 2014 11:36:44 +0200 Subject: [PATCH] Very slowly getting there. --- src/libnpsharp/AuthenticationResult.cs | 35 ++ src/libnpsharp/ClientEventArgs.cs | 6 +- src/libnpsharp/ClientEventHandler.cs | 9 + src/libnpsharp/IAuthenticationHandler.cs | 41 ++ src/libnpsharp/IFileServingHandler.cs | 32 ++ src/libnpsharp/IFriendsHandler.cs | 18 + src/libnpsharp/IUserAvatarHandler.cs | 14 + src/libnpsharp/NPClient.cs | 2 +- src/libnpsharp/NPServer.cs | 472 ++++++++++++------ src/libnpsharp/NPServerClient.cs | 54 ++ src/libnpsharp/PresenceState.cs | 12 + .../AuthenticateRegisterServerMessage.cs | 2 +- ...AuthenticateValidateTicketResultMessage.cs | 2 +- src/libnpsharp/RPC/Messages/RPCMessage.cs | 33 +- src/libnpsharp/RPC/RPCStream.cs | 13 +- src/libnpsharp/Steam/CSteamID.cs | 4 +- src/libnpsharp/Ticket.cs | 9 +- src/libnpsharp/TicketValidationResult.cs | 11 + src/libnpsharp/libnpsharp.csproj | 9 + src/npserv/DummyAuthenticationHandler.cs | 45 ++ src/npserv/Program.cs | 80 ++- src/npserv/npserv.csproj | 19 +- src/npserv/packages.config | 4 + 23 files changed, 741 insertions(+), 185 deletions(-) create mode 100644 src/libnpsharp/AuthenticationResult.cs create mode 100644 src/libnpsharp/ClientEventHandler.cs create mode 100644 src/libnpsharp/IAuthenticationHandler.cs create mode 100644 src/libnpsharp/IFileServingHandler.cs create mode 100644 src/libnpsharp/IFriendsHandler.cs create mode 100644 src/libnpsharp/IUserAvatarHandler.cs create mode 100644 src/libnpsharp/NPServerClient.cs create mode 100644 src/libnpsharp/PresenceState.cs create mode 100644 src/libnpsharp/TicketValidationResult.cs create mode 100644 src/npserv/DummyAuthenticationHandler.cs create mode 100644 src/npserv/packages.config 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 + +