Very slowly getting there.

feature-npv2
Icedream 2014-05-09 11:36:44 +02:00
parent 5ce198cbf6
commit b13d462f0d
23 changed files with 741 additions and 185 deletions

View File

@ -0,0 +1,35 @@
using NPSharp.Steam;
namespace NPSharp
{
/// <summary>
/// Represents details about the outcome of an authentication attempt.
/// </summary>
public class AuthenticationResult
{
/// <summary>
/// Constructs an authentication result instance.
/// </summary>
/// <param name="npid">
/// Set this to null if authentication should fail, otherwise use an instance of a steam ID which is
/// unique to the user.
/// </param>
public AuthenticationResult(CSteamID npid = null)
{
UserID = npid;
}
/// <summary>
/// True if authentiation succeeded, otherwise false.
/// </summary>
public bool Result
{
get { return UserID != null; }
}
/// <summary>
/// The assigned user ID by the authentication provider. Can be null for failed authentication attempts.
/// </summary>
public CSteamID UserID { get; private set; }
}
}

View File

@ -4,11 +4,11 @@ namespace NPSharp
{ {
public class ClientEventArgs : EventArgs public class ClientEventArgs : EventArgs
{ {
internal ClientEventArgs(NPServer.NPServerClient client) internal ClientEventArgs(NPServerClient client)
{ {
Client = client; Client = client;
} }
public NPServer.NPServerClient Client { get; private set; } public NPServerClient Client { get; private set; }
} }
} }

View File

@ -0,0 +1,9 @@
namespace NPSharp
{
/// <summary>
/// A delegate for all general client-related events.
/// </summary>
/// <param name="sender">The instance of the NP server</param>
/// <param name="args">All related arguments to this event</param>
public delegate void ClientEventHandler(NPServer sender, ClientEventArgs args);
}

View File

@ -0,0 +1,41 @@
namespace NPSharp
{
/// <summary>
/// Represents a handler for all authentication-related requests.
/// </summary>
public interface IAuthenticationHandler
{
/// <summary>
/// Authenticates a user based on username and password.
/// </summary>
/// <param name="client">The NP server client to authenticate</param>
/// <param name="username">The username to use for authentication</param>
/// <param name="password">The password to use for authentication</param>
/// <returns>An instance of <seealso cref="AuthenticationResult" /></returns>
AuthenticationResult AuthenticateUser(NPServerClient client, string username, string password);
/// <summary>
/// Authenticates a user based on a session token.
/// </summary>
/// <param name="client">The NP server client to authenticate</param>
/// <param name="token">The session token to use for authentication</param>
/// <returns>An instance of <seealso cref="AuthenticationResult" /></returns>
AuthenticationResult AuthenticateUser(NPServerClient client, string token);
/// <summary>
/// Authenticates a dedicated server based on its license key.
/// </summary>
/// <param name="client">The NP server client of the dedicated server to authenticate</param>
/// <param name="licenseKey">The license key to use for authentication</param>
/// <returns>An instance of <see cref="AuthenticationResult" /></returns>
AuthenticationResult AuthenticateServer(NPServerClient client, string licenseKey);
/// <summary>
/// Validates a ticket.
/// </summary>
/// <param name="client">The NP server client of the user who is trying to get the ticket validated</param>
/// <param name="server">The server that the user wants to connect to using this ticket</param>
/// <returns>A <see cref="TicketValidationResult" /> determining if the ticket is valid</returns>
TicketValidationResult ValidateTicket(NPServerClient client, NPServerClient server);
}
}

View File

@ -0,0 +1,32 @@
namespace NPSharp
{
/// <summary>
/// Represents a handler for all file-related requests.
/// </summary>
public interface IFileServingHandler
{
/// <summary>
/// Reads a file assigned to the user.
/// </summary>
/// <returns>The file contents as byte array or null if the file could not be read, opened or generated</returns>
/// <param name="client">NP server client of the user</param>
/// <param name="file">The file name</param>
byte[] ReadUserFile(NPServerClient client, string file);
/// <summary>
/// Reads a publisher file.
/// </summary>
/// <returns>The file contents as byte array or null if the file could not be read, opened or generated</returns>
/// <param name="client">NP server client of the user</param>
/// <param name="file">The file name</param>
byte[] ReadPublisherFile(NPServerClient client, string file);
/// <summary>
/// Writes a file and assigns it to the client user.
/// </summary>
/// <param name="client">NP server client of the user</param>
/// <param name="file">The file name</param>
/// <param name="data">The file contents as byte array</param>
void WriteUserFile(NPServerClient client, string file, byte[] data);
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using NPSharp.RPC.Messages;
namespace NPSharp
{
/// <summary>
/// Represents a handler for all friends-related requests.
/// </summary>
public interface IFriendsHandler
{
/// <summary>
/// Fetches all friends of the connected user.
/// </summary>
/// <param name="client">The NP server client of the user</param>
/// <returns>All friend details found for the user</returns>
IEnumerable<FriendDetails> GetFriends(NPServerClient client);
}
}

View File

@ -0,0 +1,14 @@
using NPSharp.Steam;
namespace NPSharp
{
/// <summary>
/// Represents a handler for all user avatar-related requests.
/// </summary>
public interface IUserAvatarHandler
{
byte[] GetUserAvatar(CSteamID id);
byte[] GetDefaultAvatar();
}
}

View File

@ -1,8 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; 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 log4net;
using NPSharp.RPC; using NPSharp.RPC;
using NPSharp.RPC.Messages; using NPSharp.RPC.Messages;
@ -12,34 +15,118 @@ namespace NPSharp
{ {
public class NPServer 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<NPServerClient> _clients; private readonly List<NPServerClient> _clients;
private readonly ILog _log; private readonly ILog _log;
private readonly Socket _socket;
private readonly ushort _port;
public NPServer() /// <summary>
/// Constructs a new NP server.
/// </summary>
public NPServer(ushort port = 3025)
{ {
_log = LogManager.GetLogger("NPServer"); _log = LogManager.GetLogger("NPServer");
_clients = new List<NPServerClient>(); _clients = new List<NPServerClient>();
_socket = new Socket(SocketType.Stream, ProtocolType.IP);
_port = port;
} }
private void _handleClient(NPServerClient client) /// <summary>
/// Starts up the NP server.
/// </summary>
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");
});
}
/// <summary>
/// Shuts down all connections and stops the NP server.
/// </summary>
public void Stop()
{
_socket.Shutdown(SocketShutdown.Both);
}
/// <summary>
/// The handler to use for file requests to this NP server.
/// </summary>
public IFileServingHandler FileHandler { get; set; }
/// <summary>
/// The handler to use for user avatar requests to this NP server.
/// </summary>
public IUserAvatarHandler UserAvatarHandler { get; set; }
/// <summary>
/// Returns all currently connected clients
/// </summary>
public NPServerClient[] Clients
{
get { return _clients.ToArray(); } // Avoid race condition by IEnum changes
}
/// <summary>
/// The handler to use for authentication requests to this NP server.
/// </summary>
public IAuthenticationHandler AuthenticationHandler { get; set; }
/// <summary>
/// The handler to use for friends-related requests to this NP server.
/// </summary>
public IFriendsHandler FriendsHandler { get; set; }
internal void _handleClient(NPServerClient client)
{
_log.Debug("Client now being handled");
#region RPC authentication message handlers #region RPC authentication message handlers
client.RPC.AttachHandlerForMessageType<AuthenticateWithKeyMessage>(msg => client.RPC.AttachHandlerForMessageType<AuthenticateWithKeyMessage>(msg =>
{ {
var result = new AuthenticationResult();; var result = new AuthenticationResult();
if (AuthenticationHandler != null) if (AuthenticationHandler != null)
{ {
try try
@ -109,11 +196,11 @@ namespace NPSharp
client.UserID = result.UserID; client.UserID = result.UserID;
// Send "online" notification to all friends of this player // 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 fconn.RPC.Send(new FriendsPresenceMessage
{ {
CurrentServer = client.DedicatedServer == null ? 0 : client.DedicatedServer.NPID, CurrentServer = client.DedicatedServer == null ? 0 : client.DedicatedServer.UserID,
Friend = client.UserID, Friend = client.UserID,
Presence = client.PresenceData, Presence = client.PresenceData,
PresenceState = client.DedicatedServer == null ? 1 : 2 PresenceState = client.DedicatedServer == null ? 1 : 2
@ -157,7 +244,7 @@ namespace NPSharp
client.UserID = result.UserID; client.UserID = result.UserID;
// Send "online" notification to all friends of this player // 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 fconn.RPC.Send(new FriendsPresenceMessage
{ {
@ -185,7 +272,6 @@ namespace NPSharp
_log.DebugFormat("Ticket[Version={0},ServerID={1},Time={2}]", ticketData.Version, _log.DebugFormat("Ticket[Version={0},ServerID={1},Time={2}]", ticketData.Version,
ticketData.ServerID, ticketData.Time); ticketData.ServerID, ticketData.Time);
} }
catch (ArgumentException error) catch (ArgumentException error)
{ {
@ -198,7 +284,9 @@ namespace NPSharp
{ {
if (ticketData.ClientID == client.UserID) // NPID enforcement 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()) if (s.Any())
{ {
@ -236,12 +324,14 @@ namespace NPSharp
Result = validTicket ? 0 : 1 Result = validTicket ? 0 : 1
}); });
}); });
#endregion #endregion
#region RPC friend message handlers #region RPC friend message handlers
client.RPC.AttachHandlerForMessageType<FriendsSetPresenceMessage>(msg => client.RPC.AttachHandlerForMessageType<FriendsSetPresenceMessage>(msg =>
{ {
foreach (var pdata in msg.Presence) foreach (FriendsPresence pdata in msg.Presence)
{ {
client.SetPresence(pdata.Key, pdata.Value); client.SetPresence(pdata.Key, pdata.Value);
_log.DebugFormat("Client says presence \"{0}\" is \"{1}\"", 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. // Why so goddamn complicated, NTA. Fuck.
// TODO: Not compatible with non-public accounts // 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(); EAccountType.Individual).ConvertToUint64();
var avatar = UserAvatarHandler.GetUserAvatar(npid) ?? UserAvatarHandler.GetDefaultAvatar(); byte[] avatar = UserAvatarHandler.GetUserAvatar(npid) ?? UserAvatarHandler.GetDefaultAvatar();
client.RPC.Send(new FriendsGetUserAvatarResultMessage client.RPC.Send(new FriendsGetUserAvatarResultMessage
{ {
@ -269,11 +359,197 @@ namespace NPSharp
{ {
// TODO // TODO
}); });
#endregion #endregion
// TODO: RPC message handling for storage #region RPC storage message handlers
client.RPC.AttachHandlerForMessageType<StorageGetPublisherFileMessage>(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<StorageGetUserFileMessage>(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<StorageSendRandomStringMessage>(msg =>
{
// TODO: Handle "random string" messages
});
client.RPC.AttachHandlerForMessageType<StorageWriteUserFileMessage>(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 MessagingSendData
// TODO: RPC message handling for server sessions
#endregion
#region RPC server session message handler
#endregion
_clients.Add(client); _clients.Add(client);
try try
@ -298,123 +574,15 @@ namespace NPSharp
_clients.Remove(client); _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
{
/// <summary> /// <summary>
/// Constructs an authentication result instance. /// Triggered when a client has connected but is not authenticating yet.
/// </summary> /// </summary>
/// <param name="npid">Set this to null if authentication should fail, otherwise use an instance of a steam ID which is unique to the user.</param>
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<FriendDetails> GetFriends(NPServerClient client);
/*
void SetFriendStatus(NPServerClient client, PresenceState presenceState,
Dictionary<string, string> 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<FriendDetails> Friends
{
get { return NP.FriendsHandler.GetFriends(this).ToArray(); }
}
public IEnumerable<NPServerClient> FriendConnections
{
get { return NP.Clients.Where(c => Friends.Any(f => f.NPID == c.NPID)); }
}
internal NPServerClient DedicatedServer;
private readonly Dictionary<string, string> _presence = new Dictionary<string, string>();
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
}
public event ClientEventHandler ClientConnected; public event ClientEventHandler ClientConnected;
/// <summary>
/// Invokes the <see cref="ClientConnected" /> event.
/// </summary>
/// <param name="client">The client</param>
protected virtual void OnClientConnected(NPServerClient client) protected virtual void OnClientConnected(NPServerClient client)
{ {
var handler = ClientConnected; var handler = ClientConnected;
@ -422,8 +590,15 @@ namespace NPSharp
if (handler != null) handler(this, args); if (handler != null) handler(this, args);
} }
/// <summary>
/// Triggered when a client has disconnected.
/// </summary>
public event ClientEventHandler ClientDisconnected; public event ClientEventHandler ClientDisconnected;
/// <summary>
/// Invokes the <see cref="ClientDisconnected" /> event.
/// </summary>
/// <param name="client">The client</param>
protected virtual void OnClientDisconnected(NPServerClient client) protected virtual void OnClientDisconnected(NPServerClient client)
{ {
var handler = ClientDisconnected; var handler = ClientDisconnected;
@ -431,8 +606,15 @@ namespace NPSharp
if (handler != null) handler(this, args); if (handler != null) handler(this, args);
} }
/// <summary>
/// Triggered when a client has authenticated successfully.
/// </summary>
public event ClientEventHandler ClientAuthenticated; public event ClientEventHandler ClientAuthenticated;
/// <summary>
/// Invokes the <see cref="ClientAuthenticated" /> event.
/// </summary>
/// <param name="client">The client</param>
protected virtual void OnClientAuthenticated(NPServerClient client) protected virtual void OnClientAuthenticated(NPServerClient client)
{ {
var handler = ClientAuthenticated; var handler = ClientAuthenticated;

View File

@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;
using NPSharp.RPC;
using NPSharp.RPC.Messages;
using NPSharp.Steam;
namespace NPSharp
{
/// <summary>
/// Represents a remote client connection to an NP server.
/// </summary>
public class NPServerClient
{
internal readonly NPServer NP;
internal readonly RPCServerStream RPC;
private readonly Dictionary<string, string> _presence = new Dictionary<string, string>();
internal NPServerClient DedicatedServer;
internal NPServerClient(NPServer np, RPCServerStream rpcclient)
{
NP = np;
RPC = rpcclient;
}
public CSteamID UserID { get; internal set; }
public IEnumerable<FriendDetails> Friends
{
get { return NP.FriendsHandler.GetFriends(this).ToArray(); }
}
public IEnumerable<NPServerClient> 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;
}
}
}

View File

@ -0,0 +1,12 @@
namespace NPSharp
{
/// <summary>
/// Represents the friend's current presence activity.
/// </summary>
public enum PresenceState
{
Offline = 0,
Online = 1,
Playing = 2
}
}

View File

@ -1,4 +1,5 @@
using System.Diagnostics; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -33,7 +34,9 @@ namespace NPSharp.RPC.Messages
{ {
var header = new byte[4*sizeof (uint)]; 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) if (!sock.Connected)
{ {
@ -41,7 +44,7 @@ namespace NPSharp.RPC.Messages
return null; return null;
} }
var l = sock.Receive(header); int l = sock.Receive(header);
if (l == 0) if (l == 0)
{ {
Log.Debug("Received 0 bytes"); Log.Debug("Received 0 bytes");
@ -53,7 +56,7 @@ namespace NPSharp.RPC.Messages
throw new ProtocolViolationException("Received incomplete header"); throw new ProtocolViolationException("Received incomplete header");
} }
uint signature, length, type, pid; uint signature, length, type, mid;
using (var ms = new MemoryStream(header)) using (var ms = new MemoryStream(header))
{ {
using (var br = new BinaryReader(ms)) using (var br = new BinaryReader(ms))
@ -61,7 +64,7 @@ namespace NPSharp.RPC.Messages
signature = br.ReadUInt32(); signature = br.ReadUInt32();
length = br.ReadUInt32(); length = br.ReadUInt32();
type = 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"); throw new ProtocolViolationException("Received packet with invalid signature");
} }
T packet; T message;
using (var ms = new MemoryStream(buffer)) using (var ms = new MemoryStream(buffer))
{ {
var types = Assembly.GetExecutingAssembly().GetTypes().Where( Type[] types = Assembly.GetExecutingAssembly().GetTypes().Where(
t => t =>
t.IsSubclassOf(typeof (T)) t.IsSubclassOf(typeof (T))
&& &&
@ -96,27 +99,27 @@ namespace NPSharp.RPC.Messages
return null; return null;
#endif #endif
} }
packet = (T) Serializer.NonGeneric.Deserialize( message = (T) Serializer.NonGeneric.Deserialize(
types.Single(), types.Single(),
ms ms
); );
} }
packet.MessageId = pid; message.MessageId = mid;
#if DEBUG #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); typeof (T).Name);
foreach ( foreach (
var prop in PropertyInfo prop in
packet.GetType().GetProperties().Where(p => !(p.DeclaringType == typeof (RPCServerMessage)))) 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); Log.DebugFormat("}} // deserialized from {0} bytes", header.Length + buffer.Length);
#endif #endif
return packet; return message;
} }
public byte[] Serialize() public byte[] Serialize()
@ -149,7 +152,7 @@ namespace NPSharp.RPC.Messages
#if DEBUG #if DEBUG
Log.DebugFormat("{3}[ID={0},Type={1},TypeName={2}] {{", MessageId, GetTypeId(), GetType().Name, Log.DebugFormat("{3}[ID={0},Type={1},TypeName={2}] {{", MessageId, GetTypeId(), GetType().Name,
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)); Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(this));
} }

View File

@ -100,7 +100,7 @@ namespace NPSharp.RPC
TypeCallbacks.Add( TypeCallbacks.Add(
new KeyValuePair<uint, Action<TRecv>>( new KeyValuePair<uint, Action<TRecv>>(
((PacketAttribute) typeof (T).GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type, ((PacketAttribute) typeof (T).GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type,
(Action<TRecv>)callback)); msg => callback.Invoke((T)msg)));
} }
/// <summary> /// <summary>
@ -131,12 +131,14 @@ namespace NPSharp.RPC
if (_sock == null) if (_sock == null)
throw new InvalidOperationException("You need to open the stream first."); throw new InvalidOperationException("You need to open the stream first.");
if (message.MessageId == default(uint))
message.MessageId = MessageID; message.MessageId = MessageID;
var buffer = message.Serialize(); byte[] buffer = message.Serialize();
_sock.Send(buffer); _sock.Send(buffer);
if (typeof (TSend) == typeof (RPCClientMessage))
IterateMessageID(); IterateMessageID();
} }
@ -156,6 +158,9 @@ namespace NPSharp.RPC
return null; return null;
} }
if (typeof (TRecv) == typeof (RPCClientMessage))
MessageID = message.MessageId;
// Callbacks // Callbacks
foreach (var cbi in IDCallbacks.Where(p => p.Key == message.MessageId)) foreach (var cbi in IDCallbacks.Where(p => p.Key == message.MessageId))
cbi.Value.Invoke(message); cbi.Value.Invoke(message);

View File

@ -174,7 +174,9 @@ namespace NPSharp.Steam
{ {
case EAccountType.Invalid: case EAccountType.Invalid:
case EAccountType.Individual: 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: default:
return Convert.ToString(this); return Convert.ToString(this);
} }

View File

@ -1,10 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net.Appender;
namespace NPSharp namespace NPSharp
{ {

View File

@ -0,0 +1,11 @@
namespace NPSharp
{
/// <summary>
/// Represents the outcome of a ticket validation attempt.
/// </summary>
public enum TicketValidationResult
{
Valid = 0,
Invalid = 1
}
}

View File

@ -54,10 +54,18 @@
<Reference Include="System.Xml.Serialization" /> <Reference Include="System.Xml.Serialization" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="AuthenticationResult.cs" />
<Compile Include="ClientEventArgs.cs" /> <Compile Include="ClientEventArgs.cs" />
<Compile Include="ClientEventHandler.cs" />
<Compile Include="IAuthenticationHandler.cs" />
<Compile Include="IFileServingHandler.cs" />
<Compile Include="IFriendsHandler.cs" />
<Compile Include="IUserAvatarHandler.cs" />
<Compile Include="NPClient.cs" /> <Compile Include="NPClient.cs" />
<Compile Include="NPFileException.cs" /> <Compile Include="NPFileException.cs" />
<Compile Include="NPServer.cs" /> <Compile Include="NPServer.cs" />
<Compile Include="NPServerClient.cs" />
<Compile Include="PresenceState.cs" />
<Compile Include="RPC\Messages\AuthenticateRegisterServerMessage.cs" /> <Compile Include="RPC\Messages\AuthenticateRegisterServerMessage.cs" />
<Compile Include="RPC\Messages\AuthenticateRegisterServerResultMessage.cs" /> <Compile Include="RPC\Messages\AuthenticateRegisterServerResultMessage.cs" />
<Compile Include="RPC\Messages\AuthenticateUserGroupMessage.cs" /> <Compile Include="RPC\Messages\AuthenticateUserGroupMessage.cs" />
@ -104,6 +112,7 @@
<Compile Include="Steam\InteropHelp.cs" /> <Compile Include="Steam\InteropHelp.cs" />
<Compile Include="Steam\SteamID_t.cs" /> <Compile Include="Steam\SteamID_t.cs" />
<Compile Include="Ticket.cs" /> <Compile Include="Ticket.cs" />
<Compile Include="TicketValidationResult.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Steam\README.txt" /> <Content Include="Steam\README.txt" />

View File

@ -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();
}
}
}

View File

@ -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 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);
} }
} }
} }

View File

@ -11,6 +11,8 @@
<AssemblyName>npserv</AssemblyName> <AssemblyName>npserv</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -37,22 +39,29 @@
<OutputPath>$(SolutionDir)\bin\$(Configuration)\$(Platform)\</OutputPath> <OutputPath>$(SolutionDir)\bin\$(Configuration)\$(Platform)\</OutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.3\lib\net40-full\log4net.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="DummyAuthenticationHandler.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libnpsharp\libnpsharp.csproj">
<Project>{1a5ac63a-250e-4bc8-b81a-822ac31f5e37}</Project>
<Name>libnpsharp</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.3" targetFramework="net45" />
</packages>