Tons of server code work. This library has potential.

feature-npv2
Icedream 2014-05-09 09:03:15 +02:00
parent 483842b7d9
commit d3a44df55b
56 changed files with 1755 additions and 526 deletions

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />

View File

@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{587B7B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "npfile", "src\npfile\npfile.csproj", "{19EBF339-E076-4962-A671-5B44A978687D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "npserv", "npserv\npserv.csproj", "{1FF77692-D07C-4131-95AE-21AD2A74CA11}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -34,6 +36,10 @@ Global
{19EBF339-E076-4962-A671-5B44A978687D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19EBF339-E076-4962-A671-5B44A978687D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19EBF339-E076-4962-A671-5B44A978687D}.Release|Any CPU.Build.0 = Release|Any CPU
{1FF77692-D07C-4131-95AE-21AD2A74CA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FF77692-D07C-4131-95AE-21AD2A74CA11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FF77692-D07C-4131-95AE-21AD2A74CA11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FF77692-D07C-4131-95AE-21AD2A74CA11}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -3,4 +3,5 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NP/@EntryIndexedValue">NP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NPID/@EntryIndexedValue">NPID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPC/@EntryIndexedValue">RPC</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPC/@EntryIndexedValue">RPC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UTF/@EntryIndexedValue">UTF</s:String></wpf:ResourceDictionary>

6
npserv/App.config Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>

9
npserv/Program.cs Normal file
View File

@ -0,0 +1,9 @@
namespace NPSharp.CommandLine.Server
{
class Program
{
static void Main(string[] args)
{
}
}
}

View File

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die mit einer Assembly verknüpft sind.
[assembly: AssemblyTitle("npserv")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Hewlett-Packard")]
[assembly: AssemblyProduct("npserv")]
[assembly: AssemblyCopyright("Copyright © Hewlett-Packard 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("098bf075-4a4b-437c-9283-011ffb6ff277")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

58
npserv/npserv.csproj Normal file
View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1FF77692-D07C-4131-95AE-21AD2A74CA11}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NPSharp.CommandLine.Server</RootNamespace>
<AssemblyName>npserv</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<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>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -1,115 +1,125 @@
using System;
using System.Net;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
namespace NPSharp.Authentication
{
public class AuthenticationHelper
{
private string _path;
private ushort _port;
private string _host;
/// <summary>
/// Represents a client which can communicate with an authentication endpoint in order to retrieve session
/// information, including tokens for authentication with NP servers.
/// </summary>
public class AuthenticationHelper
{
private readonly string _host;
private readonly string _path;
private readonly ushort _port;
/// <summary>
/// Gets the username.
/// </summary>
/// <value>The username.</value>
public string Username { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="NPSharp.Authentication.AuthenticationHelper" /> class.
/// </summary>
/// <param name="host">Hostname of the authentication endpoint.</param>
/// <param name="port">Port of the authentication endpoint.</param>
/// <param name="path">Path of the authentication endpoint.</param>
public AuthenticationHelper(string host, ushort port = 12003, string path = "/authenticate")
{
_host = host;
_port = port;
_path = path;
}
/// <summary>
/// Gets the user's e-mail address.
/// </summary>
/// <value>The user's e-mail address.</value>
public string UserEMail { get; private set; }
/// <summary>
/// Gets the username.
/// </summary>
/// <value>The username.</value>
public string Username { get; private set; }
/// <summary>
/// Gets the session token.
/// </summary>
/// <value>The session token.</value>
public string SessionToken { get; private set; }
/// <summary>
/// Gets the user's e-mail address.
/// </summary>
/// <value>The user's e-mail address.</value>
public string UserEMail { get; private set; }
/// <summary>
/// Gets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public uint UserId { get; private set; }
/// <summary>
/// Gets the session token.
/// </summary>
/// <value>The session token.</value>
public string SessionToken { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="NPSharp.Authentication.TokenRetriever"/> class.
/// </summary>
/// <param name="host">Hostname of the authentication endpoint.</param>
/// <param name="port">Port of the authentication endpoint.</param>
/// <param name="path">Path of the authentication endpoint.</param>
public AuthenticationHelper (string host, ushort port = 12003, string path = "/authenticate")
{
_host = host;
_port = port;
_path = path;
}
/// <summary>
/// Gets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public uint UserId { get; private set; }
/// <summary>
/// Authenticate the specified username and password.
/// </summary>
/// <param name="username">The username to use for authentication.</param>
/// <param name="password">The password to use for authentication.</param>
public void Authenticate (string username, string password)
{
var post = string.Format ("{0}&&{1}", username, password);
/// <summary>
/// Authenticate the specified username and password.
/// </summary>
/// <param name="username">The username to use for authentication.</param>
/// <param name="password">The password to use for authentication.</param>
public void Authenticate(string username, string password)
{
string post = string.Format("{0}&&{1}", username, password);
var uri = new UriBuilder {
Scheme = "http",
Port = _port,
Host = _host,
Path = _path
}.Uri;
Uri uri = new UriBuilder
{
Scheme = "http",
Port = _port,
Host = _host,
Path = _path
}.Uri;
var req = (HttpWebRequest)WebRequest.Create(uri);
var req = (HttpWebRequest) WebRequest.Create(uri);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.AllowAutoRedirect = true;
using (var reqStream = req.GetRequestStream()) {
var buffer = Encoding.UTF8.GetBytes (post);
reqStream.Write (buffer, 0, post.Length);
reqStream.Flush ();
}
using (Stream reqStream = req.GetRequestStream())
{
byte[] buffer = Encoding.UTF8.GetBytes(post);
reqStream.Write(buffer, 0, post.Length);
reqStream.Flush();
}
// Response will be in this syntax:
// (ok|fail)#text#userid#username#email#sessiontoken
var rx = new Regex("^(?<status>ok|fail)#(?<text>.+)#(?<userid>[0-9]+)#(?<username>.+)#(?<usermail>.+)#(?<sessiontoken>[^#]+)[#]*$");
var resp = (HttpWebResponse)req.GetResponse ();
using (var respStream = resp.GetResponseStream()) {
// Response will be in this syntax:
// (ok|fail)#text#userid#username#email#sessiontoken
var rx =
new Regex(
"^(?<status>ok|fail)#(?<text>.+)#(?<userid>[0-9]+)#(?<username>.+)#(?<usermail>.+)#(?<sessiontoken>[^#]+)[#]*$");
var resp = (HttpWebResponse) req.GetResponse();
using (Stream respStream = resp.GetResponseStream())
{
if (respStream == null)
throw new Exception(@"No answer from server");
using (var respReader = new StreamReader(respStream)) {
while (!respReader.EndOfStream) {
var line = respReader.ReadLine ();
using (var respReader = new StreamReader(respStream))
{
while (!respReader.EndOfStream)
{
string line = respReader.ReadLine();
// No answer?
if (string.IsNullOrEmpty(line))
continue;
if (string.IsNullOrEmpty(line))
continue;
// DW response line found?
if (!rx.IsMatch (line))
continue;
// DW response line found?
if (!rx.IsMatch(line))
continue;
// This is a DW response line, analyze
var rxm = rx.Match (line);
// This is a DW response line, analyze
Match rxm = rx.Match(line);
// Login succeeded?
if (rxm.Groups ["status"].Value != "ok")
throw new Exception (rxm.Groups ["text"].Value);
// Login succeeded?
if (rxm.Groups["status"].Value != "ok")
throw new Exception(rxm.Groups["text"].Value);
// Store all data
Username = rxm.Groups ["username"].Value;
UserEMail = rxm.Groups ["usermail"].Value;
SessionToken = rxm.Groups ["sessiontoken"].Value;
UserId = uint.Parse (rxm.Groups ["userid"].Value);
}
}
}
}
}
// Store all data
Username = rxm.Groups["username"].Value;
UserEMail = rxm.Groups["usermail"].Value;
SessionToken = rxm.Groups["sessiontoken"].Value;
UserId = uint.Parse(rxm.Groups["userid"].Value);
}
}
}
}
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace NPSharp
{
public class ClientEventArgs : EventArgs
{
internal ClientEventArgs(NPServer.NPServerClient client)
{
Client = client;
}
public NPServer.NPServerClient Client { get; private set; }
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
@ -11,40 +10,42 @@ using NPSharp.RPC.Messages;
namespace NPSharp
{
/// <summary>
/// Represents a high-level network platform client.
/// Represents a high-level network platform client.
/// </summary>
public class NPClient
{
private readonly RPCClientStream _rpc;
private CancellationTokenSource _cancellationTokenSource;
private readonly string _host;
private readonly ILog _log;
private readonly ushort _port;
private CancellationToken _cancellationToken;
private Task _procTask;
private readonly ILog _log;
private CancellationTokenSource _cancellationTokenSource;
private RPCClientStream _rpc;
/// <summary>
/// Initializes the NP client with a specified host and port.
/// Initializes the NP client with a specified host and port.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to use. Default: 3025.</param>
public NPClient(string host, ushort port = 3025)
{
_rpc = new RPCClientStream(host, port);
_log = LogManager.GetLogger ("NPClient");
_host = host;
_port = port;
_log = LogManager.GetLogger("NPClient");
}
/// <summary>
/// The assigned NP user ID. Will be set on successful authentication.
/// The assigned NP user ID. Will be set on successful authentication.
/// </summary>
public ulong LoginId { get; private set; }
/// <summary>
/// The assigned session token for this client. Will be set on successful authentication.
/// The assigned session token for this client. Will be set on successful authentication.
/// </summary>
public string SessionToken { get; private set; }
// TODO: Handle connection failures via exception
/// <summary>
/// Connects the client to the NP server.
/// Connects the client to the NP server.
/// </summary>
/// <returns>True if the connection succeeded, otherwise false.</returns>
public bool Connect()
@ -54,26 +55,39 @@ namespace NPSharp
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
if (!_rpc.Open())
return false;
_procTask = Task.Factory.StartNew(() =>
try
{
_log.Debug("Now receiving RPC messages");
_rpc = RPCClientStream.Open(_host, _port);
}
catch (Exception err)
{
#if DEBUG
_log.ErrorFormat(@"Could not initialize RPC: {0}", err);
#else
_log.ErrorFormat(@"Could not initialize RPC: {0}", err.Message);
#endif
return false;
}
Task.Factory.StartNew(() =>
{
_log.Debug("Now receiving RPC messages");
try
{
while (true)
{
_rpc.Read();
if (_rpc.Read() == null)
break;
_log.Debug("Disconnected.");
}
}
catch (ProtocolViolationException error)
{
_log.ErrorFormat("Protocol violation: {0}. Disconnect imminent.", error.Message);
Disconnect();
Disconnect();
}
_log.Debug("Now not receiving RPC messages anymore");
_log.Debug("Now not receiving RPC messages anymore");
}, _cancellationToken);
_log.Debug("Connect() done");
@ -81,13 +95,14 @@ namespace NPSharp
}
/// <summary>
/// Disconnects the client from the NP server.
/// Disconnects the client from the NP server.
/// </summary>
public void Disconnect()
{
_log.Debug("Disconnect() start");
_cancellationTokenSource.Cancel(true); // TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read)
_cancellationTokenSource.Cancel(true);
// TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read)
//_procTask.Wait(_cancellationToken);
_rpc.Close();
@ -98,7 +113,8 @@ namespace NPSharp
// TODO: Try to use an exception for failed action instead
/// <summary>
/// Authenticates this connection via a token. This token has to be requested via an external interface like remauth.php.
/// Authenticates this connection via a token. This token has to be requested via an external interface like
/// remauth.php.
/// </summary>
/// <param name="token">The token to use for authentication</param>
/// <returns>True if the login succeeded, otherwise false.</returns>
@ -106,7 +122,7 @@ namespace NPSharp
{
var tcs = new TaskCompletionSource<bool>();
_rpc.AttachCallback(packet =>
_rpc.AttachHandlerForNextMessage(packet =>
{
var result = packet as AuthenticateResultMessage;
if (result == null)
@ -117,7 +133,7 @@ namespace NPSharp
LoginId = result.NPID;
SessionToken = result.SessionToken;
tcs.SetResult(true);
}, 10);
});
_rpc.Send(new AuthenticateWithTokenMessage {Token = token});
return await tcs.Task;
@ -125,7 +141,7 @@ namespace NPSharp
// TODO: Try to use an exception for failed action instead
/// <summary>
/// Uploads a user file.
/// Uploads a user file.
/// </summary>
/// <param name="filename">The file name to save the contents to on the server</param>
/// <param name="contents">The raw byte contents</param>
@ -134,20 +150,20 @@ namespace NPSharp
{
var tcs = new TaskCompletionSource<bool>();
_rpc.AttachCallback(packet =>
_rpc.AttachHandlerForNextMessage(packet =>
{
var result = (StorageWriteUserFileResultMessage) packet;
if (result.Result != 0)
tcs.SetResult(false);
tcs.SetResult(true);
}, 10);
});
_rpc.Send(new StorageWriteUserFileMessage {FileData = contents, FileName = filename, NPID = LoginId});
return await tcs.Task;
}
/// <summary>
/// Downloads a user file and returns its contents.
/// Downloads a user file and returns its contents.
/// </summary>
/// <param name="filename">The file to download</param>
/// <returns>File contents as byte array</returns>
@ -155,7 +171,7 @@ namespace NPSharp
{
var tcs = new TaskCompletionSource<byte[]>();
_rpc.AttachCallback(packet =>
_rpc.AttachHandlerForNextMessage(packet =>
{
var result = (StorageUserFileMessage) packet;
if (result.Result != 0)
@ -164,7 +180,7 @@ namespace NPSharp
return;
}
tcs.SetResult(result.FileData);
}, 10);
});
_rpc.Send(new StorageGetUserFileMessage {FileName = filename, NPID = LoginId});
return await tcs.Task;
@ -172,20 +188,20 @@ namespace NPSharp
/// <summary>
/// Downloads a user file onto the harddisk.
/// Downloads a user file onto the harddisk.
/// </summary>
/// <param name="filename">The file to download</param>
/// <param name="targetpath">Path where to save the file</param>
public async void DownloadUserFileTo(string filename, string targetpath)
{
var contents = await GetUserFile(filename);
byte[] contents = await GetUserFile(filename);
File.WriteAllBytes(targetpath, contents);
}
/// <summary>
/// Downloads a publisher file and returns its contents.
/// Downloads a publisher file and returns its contents.
/// </summary>
/// <param name="filename">The file to download</param>
/// <returns>File contents as byte array</returns>
@ -193,7 +209,7 @@ namespace NPSharp
{
var tcs = new TaskCompletionSource<byte[]>();
_rpc.AttachCallback(packet =>
_rpc.AttachHandlerForNextMessage(packet =>
{
var result = (StoragePublisherFileMessage) packet;
if (result.Result != 0)
@ -202,27 +218,27 @@ namespace NPSharp
return;
}
tcs.SetResult(result.FileData);
}, 10);
});
_rpc.Send(new StorageGetPublisherFileMessage {FileName = filename});
return await tcs.Task;
}
/// <summary>
/// Downloads a publisher file onto the harddisk.
/// Downloads a publisher file onto the harddisk.
/// </summary>
/// <param name="filename">The file to download</param>
/// <param name="targetpath">Path where to save the file</param>
public async void DownloadPublisherFileTo(string filename, string targetpath)
{
var contents = await GetPublisherFile(filename);
byte[] contents = await GetPublisherFile(filename);
File.WriteAllBytes(targetpath, contents);
}
public void SendRandomString(string data)
{
_rpc.Send(new StorageSendRandomStringMessage() { RandomString=data });
_rpc.Send(new StorageSendRandomStringMessage {RandomString = data});
}
}
}

View File

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NPSharp
{
class NpFileException : Exception
internal class NpFileException : Exception
{
internal NpFileException(int error)
: base(error == 1 ? @"File not found on NP server" : @"Internal error on NP server")

443
src/libnpsharp/NPServer.cs Normal file
View File

@ -0,0 +1,443 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Configuration;
using log4net;
using NPSharp.RPC;
using NPSharp.RPC.Messages;
using NPSharp.Steam;
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<NPServerClient> _clients;
private readonly ILog _log;
public NPServer()
{
_log = LogManager.GetLogger("NPServer");
_clients = new List<NPServerClient>();
}
private void _handleClient(NPServerClient client)
{
#region RPC authentication message handlers
client.RPC.AttachHandlerForMessageType<AuthenticateWithKeyMessage>(msg =>
{
var result = new AuthenticationResult();;
if (AuthenticationHandler != null)
{
try
{
result = AuthenticationHandler.AuthenticateServer(client, msg.LicenseKey);
}
catch (Exception error)
{
_log.Error("Error occurred in authentication handler", error);
}
}
// Send authentication result directly to client
client.RPC.Send(new AuthenticateResultMessage
{
NPID = result.UserID,
Result = result.Result ? 0 : 1,
SessionToken = string.Empty
});
// Authentication failed => close connection
if (!result.Result)
{
client.RPC.Close();
return;
}
// Assign login ID
client.UserID = result.UserID;
client.IsServer = true;
OnClientAuthenticated(client);
});
client.RPC.AttachHandlerForMessageType<AuthenticateWithDetailsMessage>(msg =>
{
var result = new AuthenticationResult();
if (AuthenticationHandler != null)
{
try
{
result = AuthenticationHandler.AuthenticateUser(client, msg.Username, msg.Password);
}
catch (Exception error)
{
_log.Error("Error occurred in authentication handler", error);
result = new AuthenticationResult();
}
}
// Send authentication result directly to client
client.RPC.Send(new AuthenticateResultMessage
{
NPID = result.UserID,
Result = result.Result ? 0 : 1,
SessionToken = string.Empty
});
// Authentication failed => close connection
if (!result.Result)
{
client.RPC.Close();
return;
}
// Assign login ID
client.UserID = result.UserID;
// Send "online" notification to all friends of this player
foreach (var fconn in client.FriendConnections)
{
fconn.RPC.Send(new FriendsPresenceMessage
{
CurrentServer = client.DedicatedServer == null ? 0 : client.DedicatedServer.NPID,
Friend = client.UserID,
Presence = client.PresenceData,
PresenceState = client.DedicatedServer == null ? 1 : 2
});
}
OnClientAuthenticated(client);
});
client.RPC.AttachHandlerForMessageType<AuthenticateWithTokenMessage>(msg =>
{
var result = new AuthenticationResult();
if (AuthenticationHandler != null)
{
try
{
result = AuthenticationHandler.AuthenticateUser(client, msg.Token);
}
catch (Exception error)
{
_log.Error("Error occurred in authentication handler", error);
}
}
// Send authentication result directly to client
client.RPC.Send(new AuthenticateResultMessage
{
NPID = result.UserID,
Result = result.Result ? 0 : 1,
SessionToken = msg.Token
});
// Authentication failed => close connection
if (!result.Result)
{
client.RPC.Close();
return;
}
// Assign login ID
client.UserID = result.UserID;
// Send "online" notification to all friends of this player
foreach (var fconn in client.FriendConnections)
{
fconn.RPC.Send(new FriendsPresenceMessage
{
CurrentServer = client.DedicatedServer == null ? 0 : client.DedicatedServer.UserID,
Friend = client.UserID,
Presence = client.PresenceData,
PresenceState = client.DedicatedServer == null ? 1 : 2
});
}
OnClientAuthenticated(client);
});
client.RPC.AttachHandlerForMessageType<AuthenticateValidateTicketMessage>(msg =>
{
var validTicket = false;
if (!client.IsDirty)
{
Ticket ticketData = null;
try
{
ticketData = new Ticket(msg.Ticket);
_log.DebugFormat("Ticket[Version={0},ServerID={1},Time={2}]", ticketData.Version,
ticketData.ServerID, ticketData.Time);
}
catch (ArgumentException error)
{
_log.Warn("Got some weird-length ticket data", error);
}
if (ticketData != null)
{
if (ticketData.Version == 1) // Version 1 enforcement
{
if (ticketData.ClientID == client.UserID) // NPID enforcement
{
var s = _clients.Where(c => c.IsServer && !c.IsDirty && c.UserID == ticketData.ServerID).ToArray();
if (s.Any())
{
// TODO: Time validation. Problem is some clocks go wrong by minutes!
client.DedicatedServer = s.First();
validTicket = true;
_log.Debug("Ticket validated");
}
else
{
_log.Warn("Ticket invalid, could not find any sane servers with requested server ID");
}
}
else
{
_log.Warn("Ticket invalid, found NPID spoofing attempt");
}
}
else
{
_log.Warn("Ticket invalid, found invalid version");
}
}
}
else
{
_log.Warn("Ticket invalid, client is marked as dirty");
}
// Invalid data buffer
client.RPC.Send(new AuthenticateValidateTicketResultMessage
{
GroupID = client.GroupID,
NPID = client.UserID,
Result = validTicket ? 0 : 1
});
});
#endregion
#region RPC friend message handlers
client.RPC.AttachHandlerForMessageType<FriendsSetPresenceMessage>(msg =>
{
foreach (var pdata in msg.Presence)
{
client.SetPresence(pdata.Key, pdata.Value);
_log.DebugFormat("Client says presence \"{0}\" is \"{1}\"", pdata.Key, pdata.Value);
}
});
client.RPC.AttachHandlerForMessageType<FriendsGetUserAvatarMessage>(msg =>
{
// Why so goddamn complicated, NTA. Fuck.
// TODO: Not compatible with non-public accounts
var npid = new CSteamID((uint) msg.Guid, EUniverse.Public,
EAccountType.Individual).ConvertToUint64();
var avatar = UserAvatarHandler.GetUserAvatar(npid) ?? UserAvatarHandler.GetDefaultAvatar();
client.RPC.Send(new FriendsGetUserAvatarResultMessage
{
FileData = avatar,
Guid = msg.Guid,
Result = avatar != null ? 0 : 1
});
});
client.RPC.AttachHandlerForMessageType<FriendsGetProfileDataMessage>(msg =>
{
// TODO
});
#endregion
// TODO: RPC message handling for storage
// TODO: RPC message handling for MessagingSendData
// TODO: RPC message handling for server sessions
_clients.Add(client);
try
{
_log.Debug("Client connected");
OnClientConnected(client);
while (true)
{
var msg = client.RPC.Read();
if (msg == null)
break;
}
_log.Debug("Client disconnected");
OnClientDisconnected(client);
}
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.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
{
/// <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;
}
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;
protected virtual void OnClientConnected(NPServerClient client)
{
var handler = ClientConnected;
var args = new ClientEventArgs(client);
if (handler != null) handler(this, args);
}
public event ClientEventHandler ClientDisconnected;
protected virtual void OnClientDisconnected(NPServerClient client)
{
var handler = ClientDisconnected;
var args = new ClientEventArgs(client);
if (handler != null) handler(this, args);
}
public event ClientEventHandler ClientAuthenticated;
protected virtual void OnClientAuthenticated(NPServerClient client)
{
var handler = ClientAuthenticated;
var args = new ClientEventArgs(client);
if (handler != null) handler(this, args);
}
}
}

View File

@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die mit einer Assembly verknüpft sind.
[assembly: AssemblyTitle("NPSharp Library")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
@ -16,9 +17,11 @@ using System.Runtime.InteropServices;
// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("0efdd0b5-fd69-48fd-b714-f4f0e032f417")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
@ -31,5 +34,6 @@ using System.Runtime.InteropServices;
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.*")]
[assembly: AssemblyFileVersion("0.1")]

View File

@ -0,0 +1,12 @@
using ProtoBuf;
namespace NPSharp.RPC.Messages
{
[Packet(1004)]
[ProtoContract]
public sealed class AuthenticateRegisterServerMessage : RPCClientMessage
{
[ProtoMember(1, IsRequired=false)]
public string ConfigPath { get; set; }
}
}

View File

@ -0,0 +1,18 @@
using ProtoBuf;
namespace NPSharp.RPC.Messages
{
[Packet(1022)]
[ProtoContract]
public sealed class AuthenticateRegisterServerResultMessage : RPCServerMessage
{
[ProtoMember(1)]
public int Result { get; set; }
[ProtoMember(2)]
public string LicenseKey { get; set; }
[ProtoMember(3)]
public int ServerID { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using ProtoBuf;
namespace NPSharp.RPC.Messages
{
[Packet(1011)]
[ProtoContract]
public sealed class AuthenticateUserGroupMessage : RPCServerMessage
{
[ProtoMember(1)]
public int GroupID { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using ProtoBuf;
namespace NPSharp.RPC.Messages
{
[Packet(1003)]
[ProtoContract]
public sealed class AuthenticateValidateTicketMessage : RPCClientMessage
{
[ProtoMember(1, DataFormat = DataFormat.FixedSize)]
public UInt32 ClientIP { get; set; }
[ProtoMember(2, DataFormat = DataFormat.FixedSize)]
public UInt64 NPID { get; set; }
[ProtoMember(3)]
public byte[] Ticket { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using ProtoBuf;
namespace NPSharp.RPC.Messages
{
[Packet(1012)]
[ProtoContract]
public sealed class AuthenticateValidateTicketResultMessage : RPCServerMessage
{
[ProtoMember(1)]
public int Result { get; set; }
[ProtoMember(2, DataFormat=DataFormat.FixedSize)]
public UInt64 NPID { get; set; }
[ProtoMember(3)]
public int GroupID { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using ProtoBuf;
namespace NPSharp.RPC.Messages
{
[Packet(1002)]
[ProtoContract]
public sealed class AuthenticateWithDetailsMessage : RPCClientMessage
{
[ProtoMember(1)]
public string Username { get; set; }
[ProtoMember(2)]
public string Password { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using ProtoBuf;
namespace NPSharp.RPC.Messages
{
[Packet(1001)]
[ProtoContract]
public sealed class AuthenticateWithKeyMessage : RPCClientMessage
{
[ProtoMember(1)]
public string LicenseKey { get; set; }
}
}

View File

@ -6,7 +6,9 @@ namespace NPSharp.RPC.Messages
[ProtoContract]
public sealed class FriendDetails
{
internal FriendDetails() { }
internal FriendDetails()
{
}
[ProtoMember(1)]
public UInt64 NPID { get; set; }

View File

@ -8,7 +8,7 @@ namespace NPSharp.RPC.Messages
public sealed class FriendsGetProfileDataMessage : RPCClientMessage
{
[ProtoMember(1)]
public UInt64[] IDs { get; set; }
public UInt64[] FriendIDs { get; set; }
[ProtoMember(2)]
public string ProfileType { get; set; }

View File

@ -6,7 +6,9 @@ namespace NPSharp.RPC.Messages
[Packet(1215)]
public sealed class FriendsGetUserAvatarResultMessage : RPCServerMessage
{
internal FriendsGetUserAvatarResultMessage() { }
internal FriendsGetUserAvatarResultMessage()
{
}
[ProtoMember(1)]
public int Result { get; internal set; }

View File

@ -5,7 +5,7 @@ namespace NPSharp.RPC.Messages
{
[ProtoContract]
[Packet(1212)]
public sealed class FriendsPresenceMessage
public sealed class FriendsPresenceMessage : RPCServerMessage
{
internal FriendsPresenceMessage()
{

View File

@ -6,7 +6,9 @@ namespace NPSharp.RPC.Messages
[Packet(1211)]
public sealed class FriendsRosterMessage : RPCServerMessage
{
internal FriendsRosterMessage() { }
internal FriendsRosterMessage()
{
}
[ProtoMember(1)]
public FriendDetails[] Friends { get; set; }

View File

@ -6,7 +6,9 @@ namespace NPSharp.RPC.Messages
[ProtoContract]
public sealed class ProfileDataResult
{
internal ProfileDataResult() { }
internal ProfileDataResult()
{
}
[ProtoMember(1)]
public UInt64 NPID { get; set; }

View File

@ -1,83 +1,6 @@
using System;
using System.IO;
using log4net;
namespace NPSharp.RPC.Messages
{
public abstract class RPCClientMessage : RPCMessage
{
private static readonly ILog Log;
static RPCClientMessage()
{
Log = LogManager.GetLogger("RPCClientMessage");
}
public byte[] Serialize(uint id)
{
#if DEBUG
Log.DebugFormat("RPCClientMessage[ID={0},Type={1},TypeName={2}] {{", id, GetTypeId(), GetType().Name);
foreach (var prop in GetType().GetProperties())
{
Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(this));
}
#endif
byte[] content;
using (var bufferStream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(bufferStream, this);
bufferStream.Seek(0, SeekOrigin.Begin);
content = bufferStream.ToArray();
}
Log.DebugFormat("}} => Serialized to {0} bytes", content.Length);
byte[] buffArray;
using (var ms = new MemoryStream())
{
using (var bw = new BinaryWriter(ms))
{
bw.Write(Signature);
bw.Write((uint)content.Length);
bw.Write(GetTypeId());
bw.Write(id);
bw.Write(content);
bw.Flush();
ms.Seek(0, SeekOrigin.Begin);
buffArray = ms.ToArray();
}
}
#if DEBUG
Console.Write("\t");
Console.ForegroundColor = ConsoleColor.Gray;
Console.Write(BitConverter.ToString(buffArray, 0, sizeof(uint)).Replace("-", ""));
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.Write(BitConverter.ToString(buffArray, 1 * sizeof(uint), sizeof(uint)).Replace("-", ""));
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write(BitConverter.ToString(buffArray, 2 * sizeof(uint), sizeof(uint)).Replace("-", ""));
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write(BitConverter.ToString(buffArray, 3 * sizeof(uint), sizeof(uint)).Replace("-", ""));
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write(BitConverter.ToString(buffArray, 4 * sizeof(uint)).Replace("-", ""));
Console.ResetColor();
Console.WriteLine();
#endif
return buffArray;
}
}
}

View File

@ -1,15 +1,162 @@
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using log4net;
using ProtoBuf;
namespace NPSharp.RPC.Messages
{
public abstract class RPCMessage
{
internal const uint Signature = 0xDEADC0DE; // I wonder if aiw3 changed this since kernal noted it in his source code.
internal const uint Signature = 0xDEADC0DE;
// I wonder if aiw3 changed this since kernal noted it in his source code.
private static readonly ILog Log;
static RPCMessage()
{
Log = LogManager.GetLogger("RPC");
}
internal uint MessageId { get; set; }
public uint GetTypeId()
{
var packet = (PacketAttribute) GetType().GetCustomAttributes(typeof (PacketAttribute), false).Single();
return packet.Type;
}
public static T Deserialize<T>(Socket sock) where T : RPCMessage
{
var header = new byte[4*sizeof (uint)];
while (sock.Connected && !sock.Poll(1000, SelectMode.SelectRead)) { }
if (!sock.Connected)
{
// Socket disconnected
return null;
}
var l = sock.Receive(header);
if (l == 0)
{
Log.Debug("Received 0 bytes");
return null;
}
if (l < 16)
{
Log.ErrorFormat("Received incomplete header ({0} bytes of 16 wanted bytes)", l);
throw new ProtocolViolationException("Received incomplete header");
}
uint signature, length, type, pid;
using (var ms = new MemoryStream(header))
{
using (var br = new BinaryReader(ms))
{
signature = br.ReadUInt32();
length = br.ReadUInt32();
type = br.ReadUInt32();
pid = br.ReadUInt32();
}
}
var buffer = new byte[length];
sock.Receive(buffer);
if (signature != Signature)
{
Log.Error("Received invalid signature");
throw new ProtocolViolationException("Received packet with invalid signature");
}
T packet;
using (var ms = new MemoryStream(buffer))
{
var types = Assembly.GetExecutingAssembly().GetTypes().Where(
t =>
t.IsSubclassOf(typeof (T))
&&
((PacketAttribute) t.GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type == type
).ToArray();
if (!types.Any())
{
throw new ProtocolViolationException(string.Format("Received packet of unknown type ({0})", type));
}
if (types.Count() > 1)
{
#if DEBUG
Debug.Fail(string.Format("Bug in program code: Found more than 1 type for packet ID {0}", type));
#else
return null;
#endif
}
packet = (T) Serializer.NonGeneric.Deserialize(
types.Single(),
ms
);
}
packet.MessageId = pid;
#if DEBUG
Log.DebugFormat("{3}[ID={0},Type={1},TypeName={2}] {{", pid, packet.GetTypeId(), packet.GetType().Name,
typeof (T).Name);
foreach (
var prop in
packet.GetType().GetProperties().Where(p => !(p.DeclaringType == typeof (RPCServerMessage))))
{
Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(packet));
}
Log.DebugFormat("}} // deserialized from {0} bytes", header.Length + buffer.Length);
#endif
return packet;
}
public byte[] Serialize()
{
byte[] content;
using (var bufferStream = new MemoryStream())
{
Serializer.Serialize(bufferStream, this);
bufferStream.Seek(0, SeekOrigin.Begin);
content = bufferStream.ToArray();
}
byte[] buffArray;
using (var ms = new MemoryStream())
{
using (var bw = new BinaryWriter(ms))
{
bw.Write(Signature);
bw.Write((uint) content.Length);
bw.Write(GetTypeId());
bw.Write(MessageId);
bw.Write(content);
bw.Flush();
ms.Seek(0, SeekOrigin.Begin);
buffArray = ms.ToArray();
}
}
#if DEBUG
Log.DebugFormat("{3}[ID={0},Type={1},TypeName={2}] {{", MessageId, GetTypeId(), GetType().Name,
GetType().Name);
foreach (var prop in GetType().GetProperties())
{
Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(this));
}
Log.DebugFormat("}} // serialized to {0} bytes", buffArray.Length);
#endif
return buffArray;
}
}
}

View File

@ -1,105 +1,6 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using log4net;
namespace NPSharp.RPC.Messages
{
public abstract class RPCServerMessage : RPCMessage
{
private static readonly ILog Log;
static RPCServerMessage()
{
Log = LogManager.GetLogger("RPCServerMessage");
}
// Internal constructor to make classes unconstructible from outside
internal RPCServerMessage() { }
public uint MessageId { get; private set; }
public static RPCServerMessage Deserialize(NetworkStream ns)
{
var header = new byte[4 * sizeof(uint)];
Log.Debug("Reading...");
var l = ns.Read(header, 0, header.Length);
if (l == 0)
{
Log.Debug("Received 0 bytes");
return null;
}
if (l < 16)
{
Log.ErrorFormat("Received incomplete header ({0} bytes of 16 wanted bytes)", l);
throw new ProtocolViolationException("Received incomplete header");
}
uint signature, length, type, pid;
using (var ms = new MemoryStream(header))
{
using (var br = new BinaryReader(ms))
{
signature = br.ReadUInt32();
length = br.ReadUInt32();
type = br.ReadUInt32();
pid = br.ReadUInt32();
}
}
var buffer = new byte[length];
ns.Read(buffer, 0, buffer.Length);
if (signature != Signature)
{
Log.Error("Received invalid signature");
throw new ProtocolViolationException("Received packet with invalid signature");
}
RPCServerMessage packet;
using (var ms = new MemoryStream(buffer))
{
var types = Assembly.GetExecutingAssembly().GetTypes().Where(
t =>
t.IsSubclassOf(typeof (RPCServerMessage))
&&
((PacketAttribute) t.GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type == type
).ToArray();
if (!types.Any())
{
throw new ProtocolViolationException(string.Format("Received packet of unknown type ({0})", type));
}
if (types.Count() > 1)
{
#if DEBUG
Debug.Fail(string.Format("Bug in program code: Found more than 1 type for packet ID {0}", type));
#else
return null;
#endif
}
packet = (RPCServerMessage)ProtoBuf.Serializer.NonGeneric.Deserialize(
types.Single(),
ms
);
}
packet.MessageId = pid;
#if DEBUG
Log.DebugFormat("RPCServerMessage[ID={0},Type={1},TypeName={2}] {{", pid, packet.GetTypeId(), packet.GetType().Name);
foreach (var prop in packet.GetType().GetProperties().Where(p => !(p.DeclaringType == typeof (RPCServerMessage))))
{
Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(packet));
}
Log.DebugFormat("}} => Read from {0} bytes", header.Length + buffer.Length);
#endif
return packet;
}
}
}

View File

@ -1,154 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using log4net;
using System.Net.Sockets;
using NPSharp.RPC.Messages;
namespace NPSharp.RPC
{
/// <summary>
/// Represents a low-level client stream which can communicate with an NP server using RPC packets.
/// Represents a low-level client stream which can communicate with an NP server using RPC messages.
/// </summary>
public class RPCClientStream
public class RPCClientStream : RPCStream<RPCClientMessage, RPCServerMessage>
{
private NetworkStream _ns;
private uint _id;
private readonly ILog _log;
private readonly string _host;
private readonly ushort _port;
private readonly Dictionary<uint, Tuple<DateTime, Action<RPCServerMessage>>> _callbacks = new Dictionary<uint, Tuple<DateTime, Action<RPCServerMessage>>>();
/// <summary>
/// Initializes an RPC connection stream with a specified host and port.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to use. Default: 3025.</param>
public RPCClientStream(string host, ushort port = 3025)
public RPCClientStream(Socket sock) : base(sock)
{
_host = host;
_port = port;
_log = LogManager.GetLogger("RPC");
}
/// <summary>
/// Opens the RPC stream to the NP server.
/// </summary>
/// <returns>True if the connection succeeded, otherwise false.</returns>
public bool Open()
public static RPCClientStream Open(string host, ushort port = 3025)
{
_log.Debug("Open() start");
// Connection already established?
if (_ns != null)
throw new InvalidOperationException("Connection already opened");
var tcp = new TcpClient();
try
{
tcp.Connect(_host, _port);
}
catch
{
return false;
}
_ns = tcp.GetStream();
_log.Debug("Open() end");
return true;
}
/// <summary>
/// Closes the connection with the NP server.
/// </summary>
/// <param name="timeout"></param>
public void Close(int timeout = 2000)
{
// Connection already closed?
if (_ns == null)
throw new InvalidOperationException("Connection already closed");
try
{
_callbacks.Clear();
_ns.Close(timeout);
_ns.Dispose();
}
finally
{
_ns = null;
}
}
/// <summary>
/// Attaches a callback to the next message being sent out. This allows handling response packets.
/// </summary>
/// <param name="callback">The method to call when we receive a response to the next message</param>
/// <param name="timeout">Time in seconds from now in which this callback will expire for the next packet</param>
public void AttachCallback(Action<RPCServerMessage> callback, double timeout)
{
_cleanupCallbacks();
_log.DebugFormat("AttachCallback for packet id {0}", _id);
if (_callbacks.ContainsKey(_id))
throw new Exception("There is already a callback for the current message. You can only add max. one callback.");
_callbacks.Add(_id, new Tuple<DateTime, Action<RPCServerMessage>>(DateTime.Now + TimeSpan.FromSeconds(timeout), callback));
}
// TODO: Exposure of message ID needed or no?
/// <summary>
/// Sends out an RPC message.
/// </summary>
/// <param name="message">The RPC message to send out.</param>
/// <returns>The new ID of the message.</returns>
public uint Send(RPCClientMessage message)
{
if (_ns == null)
throw new InvalidOperationException("You need to open the stream first.");
var buffer = message.Serialize(_id);
_ns.Write(buffer, 0, buffer.Length);
_ns.Flush();
_log.DebugFormat("Sent packet ID {1} (type {0})", message.GetType().Name, _id);
return _id++;
}
/// <summary>
/// Waits for the next RPC message from the server and reads it.
/// </summary>
/// <returns>The received server message.</returns>
public RPCServerMessage Read()
{
if (_ns == null)
throw new InvalidOperationException("You need to open the stream first.");
var message = RPCServerMessage.Deserialize(_ns);
if (message == null)
{
_log.Debug("Recv NULL message");
return null;
}
if (!_callbacks.ContainsKey(message.MessageId))
return message;
_cleanupCallbacks();
if (_callbacks.ContainsKey(message.MessageId))
_callbacks[message.MessageId].Item2.Invoke(message);
return message;
}
private void _cleanupCallbacks()
{
var cbr = (from item in _callbacks where item.Value.Item1 < DateTime.Now select item.Key).ToArray();
foreach (var cb in cbr)
_callbacks.Remove(cb);
var sock = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
sock.Connect(host, port);
return new RPCClientStream(sock);
}
}
}

View File

@ -0,0 +1,15 @@
using System.Net.Sockets;
using NPSharp.RPC.Messages;
namespace NPSharp.RPC
{
/// <summary>
/// Represents a low-level stream which communicates with an NP client using RPC messages.
/// </summary>
public class RPCServerStream : RPCStream<RPCServerMessage, RPCClientMessage>
{
public RPCServerStream(Socket sock) : base(sock)
{
}
}
}

View File

@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using log4net;
using NPSharp.RPC.Messages;
namespace NPSharp.RPC
{
/// <summary>
/// Represents a low-level client stream which can communicate using RPC packets.
/// </summary>
public abstract class RPCStream<TSend, TRecv>
where TSend : RPCMessage
where TRecv : RPCMessage
{
/// <summary>
/// Registered callbacks for all received messages.
/// </summary>
protected readonly List<Action<TRecv>> GeneralCallbacks =
new List<Action<TRecv>>();
/// <summary>
/// Registered callbacks for specific received message IDs.
/// </summary>
protected readonly List<KeyValuePair<uint, Action<TRecv>>> IDCallbacks =
new List<KeyValuePair<uint, Action<TRecv>>>();
/// <summary>
/// Registered callbacks for specific received message type IDs.
/// </summary>
protected readonly List<KeyValuePair<uint, Action<TRecv>>> TypeCallbacks =
new List<KeyValuePair<uint, Action<TRecv>>>();
/// <summary>
/// Logger instance.
/// </summary>
private readonly ILog _log;
/// <summary>
/// ID of the next message.
/// </summary>
protected uint MessageID;
/// <summary>
/// Base stream.
/// </summary>
private Socket _sock;
/// <summary>
/// Initializes an RPC connection stream from an already established network connection.
/// </summary>
/// <param name="sock">Client's network stream</param>
protected RPCStream(Socket sock)
{
_log = LogManager.GetLogger("RPC");
_sock = sock;
}
/// <summary>
/// Sets the next message's ID.
/// </summary>
protected void IterateMessageID()
{
MessageID++;
}
/// <summary>
/// Closes the connection.
/// </summary>
/// <param name="timeout"></param>
public void Close(int timeout = 2000)
{
// Connection already closed?
if (_sock == null)
throw new InvalidOperationException("Connection already closed");
try
{
GeneralCallbacks.Clear();
IDCallbacks.Clear();
TypeCallbacks.Clear();
_sock.Close(timeout);
_sock.Dispose();
}
finally
{
_sock = null;
}
}
/// <summary>
/// Attaches a callback to the connection which handles a specific incoming RPC message type.
/// </summary>
/// <typeparam name="T">The message type to handle, must be a subtype of RPCMessage</typeparam>
/// <param name="callback"></param>
public void AttachHandlerForMessageType<T>(Action<T> callback) where T : TRecv
{
TypeCallbacks.Add(
new KeyValuePair<uint, Action<TRecv>>(
((PacketAttribute) typeof (T).GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type,
(Action<TRecv>)callback));
}
/// <summary>
/// Attaches a callback to the connection which handles a response to the next message we send.
/// </summary>
/// <param name="callback"></param>
public void AttachHandlerForNextMessage(Action<TRecv> callback)
{
IDCallbacks.Add(new KeyValuePair<uint, Action<TRecv>>(MessageID, callback));
}
/// <summary>
/// Attaches a callback to the connection for all incoming RPC messages.
/// </summary>
/// <param name="callback"></param>
public void AttachHandler(Action<TRecv> callback)
{
GeneralCallbacks.Add(callback);
}
/// <summary>
/// Sends out an RPC message to the remote endpoint.
/// </summary>
/// <param name="message">The RPC message to send out.</param>
/// <returns>The new ID of the message.</returns>
public void Send(TSend message)
{
if (_sock == null)
throw new InvalidOperationException("You need to open the stream first.");
message.MessageId = MessageID;
var buffer = message.Serialize();
_sock.Send(buffer);
IterateMessageID();
}
/// <summary>
/// Waits for the next RPC message from the remote end and reads it.
/// </summary>
/// <returns>The received server message.</returns>
public TRecv Read()
{
if (_sock == null)
throw new InvalidOperationException("You need to open the stream first.");
var message = RPCMessage.Deserialize<TRecv>(_sock);
if (message == null)
{
return null;
}
// Callbacks
foreach (var cbi in IDCallbacks.Where(p => p.Key == message.MessageId))
cbi.Value.Invoke(message);
foreach (var cbi in TypeCallbacks.Where(p => p.Key == message.GetTypeId()))
cbi.Value.Invoke(message);
foreach (var callback in GeneralCallbacks)
callback.Invoke(message);
return message;
}
}
}

View File

@ -0,0 +1,229 @@
using System;
namespace NPSharp.Steam
{
public class CSteamID
{
private readonly InteropHelp.BitVector64 steamid;
public CSteamID()
: this(0)
{
}
internal CSteamID(UInt32 unAccountID, EUniverse eUniverse, EAccountType eAccountType)
: this()
{
Set(unAccountID, eUniverse, eAccountType);
}
internal CSteamID(UInt32 unAccountID, UInt32 unInstance, EUniverse eUniverse, EAccountType eAccountType)
: this()
{
InstancedSet(unAccountID, unInstance, eUniverse, eAccountType);
}
internal CSteamID(UInt64 id)
{
steamid = new InteropHelp.BitVector64(id);
}
internal CSteamID(SteamID_t sid)
: this(
sid.low32Bits, sid.high32Bits & 0xFFFFF, (EUniverse) (sid.high32Bits >> 24),
(EAccountType) ((sid.high32Bits >> 20) & 0xF))
{
}
public UInt32 AccountID
{
get { return (UInt32) steamid[0, 0xFFFFFFFF]; }
set { steamid[0, 0xFFFFFFFF] = value; }
}
public UInt32 AccountInstance
{
get { return (UInt32) steamid[32, 0xFFFFF]; }
set { steamid[32, 0xFFFFF] = value; }
}
public EAccountType AccountType
{
get { return (EAccountType) steamid[52, 0xF]; }
set { steamid[52, 0xF] = (UInt64) value; }
}
public EUniverse AccountUniverse
{
get { return (EUniverse) steamid[56, 0xFF]; }
set { steamid[56, 0xFF] = (UInt64) value; }
}
public static implicit operator UInt64(CSteamID sid)
{
return sid.steamid.Data;
}
public static implicit operator CSteamID(UInt64 id)
{
return new CSteamID(id);
}
public void Set(UInt32 unAccountID, EUniverse eUniverse, EAccountType eAccountType)
{
AccountID = unAccountID;
AccountUniverse = eUniverse;
AccountType = eAccountType;
if (eAccountType == EAccountType.Clan)
{
AccountInstance = 0;
}
else
{
AccountInstance = 1;
}
}
public void InstancedSet(UInt32 unAccountID, UInt32 unInstance, EUniverse eUniverse, EAccountType eAccountType)
{
AccountID = unAccountID;
AccountUniverse = eUniverse;
AccountType = eAccountType;
AccountInstance = unInstance;
}
public void SetFromUint64(UInt64 ulSteamID)
{
steamid.Data = ulSteamID;
}
public UInt64 ConvertToUint64()
{
return steamid.Data;
}
public bool BBlankAnonAccount()
{
return AccountID == 0 && BAnonAccount() && AccountInstance == 0;
}
public bool BGameServerAccount()
{
return AccountType == EAccountType.GameServer ||
AccountType == EAccountType.AnonGameServer;
}
public bool BContentServerAccount()
{
return AccountType == EAccountType.ContentServer;
}
public bool BClanAccount()
{
return AccountType == EAccountType.Clan;
}
public bool BChatAccount()
{
return AccountType == EAccountType.Chat;
}
public bool IsLobby()
{
return (AccountType == EAccountType.Chat) && ((AccountInstance & (0x000FFFFF + 1) >> 2) != 0);
}
public bool BAnonAccount()
{
return AccountType == EAccountType.AnonUser ||
AccountType == EAccountType.AnonGameServer;
}
public bool BAnonUserAccount()
{
return AccountType == EAccountType.AnonUser;
}
public bool IsValid()
{
if (AccountType <= EAccountType.Invalid || AccountType >= EAccountType.Max)
return false;
if (AccountUniverse <= EUniverse.Invalid || AccountUniverse >= EUniverse.Max)
return false;
if (AccountType == EAccountType.Individual)
{
if (AccountID == 0 || AccountInstance != 1)
return false;
}
if (AccountType == EAccountType.Clan)
{
if (AccountID == 0 || AccountInstance != 0)
return false;
}
return true;
}
public string Render()
{
switch (AccountType)
{
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);
default:
return Convert.ToString(this);
}
}
public override string ToString()
{
return Render();
}
public override bool Equals(Object obj)
{
if (obj == null)
return false;
var sid = obj as CSteamID;
if (sid == null)
return false;
return steamid.Data == sid.steamid.Data;
}
public bool Equals(CSteamID sid)
{
if (sid == null)
return false;
return steamid.Data == sid.steamid.Data;
}
public static bool operator ==(CSteamID a, CSteamID b)
{
if (ReferenceEquals(a, b))
return true;
if ((a == null) || (b == null))
return false;
return a.steamid.Data == b.steamid.Data;
}
public static bool operator !=(CSteamID a, CSteamID b)
{
return !(a == b);
}
public override int GetHashCode()
{
return steamid.Data.GetHashCode();
}
}
}

View File

@ -0,0 +1,18 @@
namespace NPSharp.Steam
{
public enum EAccountType
{
Invalid = 0,
Individual = 1,
Multiseat = 2,
GameServer = 3,
AnonGameServer = 4,
Pending = 5,
ContentServer = 6,
Clan = 7,
Chat = 8,
ConsoleUser = 9,
AnonUser = 10,
Max = 11,
};
}

View File

@ -0,0 +1,12 @@
namespace NPSharp.Steam
{
public enum EUniverse
{
Invalid = 0,
Public = 1,
Beta = 2,
Internal = 3,
Dev = 4,
Max = 5,
};
}

View File

@ -0,0 +1,87 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace NPSharp.Steam
{
internal class InteropHelp
{
private static readonly GCHandle NullHandle = GCHandle.Alloc(new byte[0], GCHandleType.Pinned);
/// <summary>
/// Decodes IntPtr as if it were a UTF-8 string
/// </summary>
public static string DecodeUTF8String(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
return null;
int len = 0;
while (Marshal.ReadByte(ptr, len) != 0) len++;
if (len == 0)
return string.Empty;
var buffer = new byte[len];
Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Encodes string as an IntPtr
/// </summary>
public static IntPtr EncodeUTF8String(string str, out GCHandle handle)
{
if (str == null)
{
handle = NullHandle;
return IntPtr.Zero;
}
int length = Encoding.UTF8.GetByteCount(str);
var buffer = new byte[length + 1];
Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, 0);
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
return handle.AddrOfPinnedObject();
}
public static void FreeString(ref GCHandle handle)
{
if (handle == NullHandle)
return;
handle.Free();
}
public class BitVector64
{
private UInt64 data;
public BitVector64()
{
}
public BitVector64(UInt64 value)
{
data = value;
}
public UInt64 Data
{
get { return data; }
set { data = value; }
}
public UInt64 this[uint bitoffset, UInt64 valuemask]
{
get { return (data >> (ushort) bitoffset) & valuemask; }
set
{
data = (data & ~(valuemask << (ushort) bitoffset)) | ((value & valuemask) << (ushort) bitoffset);
}
}
}
}
}

View File

@ -0,0 +1,3 @@
The code in this folder is copied over and reformatted (slightly edited) from Steam4NET.
The source code for Steam4NET is available at https://github.com/SteamRE/Steam4NET.

View File

@ -0,0 +1,15 @@
using System;
using System.Runtime.InteropServices;
namespace NPSharp.Steam
{
// Summary:
// Used to store a SteamID in callbacks (With proper alignment / padding).
// You probably don't want to use this type directly, convert it to CSteamID.
[StructLayout(LayoutKind.Sequential, Pack = 8)]
internal struct SteamID_t
{
public UInt32 low32Bits; // m_unAccountID (32)
public UInt32 high32Bits; // m_unAccountInstance (20), m_EAccountType (4), m_EUniverse (8)
}
}

38
src/libnpsharp/Ticket.cs Normal file
View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net.Appender;
namespace NPSharp
{
internal class Ticket
{
public Ticket(byte[] data)
{
if (data.Length < sizeof(uint) + (sizeof(ulong) * 2) + sizeof(uint))
{
throw new ArgumentException("Data buffer too short");
}
using (var ms = new MemoryStream(data))
using (var br = new BinaryReader(ms))
{
Version = br.ReadUInt32();
ClientID = br.ReadUInt64();
ServerID = br.ReadUInt64();
Time = br.ReadUInt32();
}
}
public uint Version { get; private set; }
public ulong ClientID { get; private set; }
public ulong ServerID { get; private set; }
public uint Time { get; private set; }
}
}

View File

@ -54,8 +54,17 @@
<Reference Include="System.Xml.Serialization" />
</ItemGroup>
<ItemGroup>
<Compile Include="ClientEventArgs.cs" />
<Compile Include="NPClient.cs" />
<Compile Include="NPFileException.cs" />
<Compile Include="NPServer.cs" />
<Compile Include="RPC\Messages\AuthenticateRegisterServerMessage.cs" />
<Compile Include="RPC\Messages\AuthenticateRegisterServerResultMessage.cs" />
<Compile Include="RPC\Messages\AuthenticateUserGroupMessage.cs" />
<Compile Include="RPC\Messages\AuthenticateValidateTicketMessage.cs" />
<Compile Include="RPC\Messages\AuthenticateValidateTicketResultMessage.cs" />
<Compile Include="RPC\Messages\AuthenticateWithDetailsMessage.cs" />
<Compile Include="RPC\Messages\AuthenticateWithKeyMessage.cs" />
<Compile Include="RPC\Messages\FriendDetails.cs" />
<Compile Include="RPC\Messages\FriendsSetSteamIDMessage.cs" />
<Compile Include="RPC\Messages\FriendsGetProfileDataMessage.cs" />
@ -87,8 +96,18 @@
<Compile Include="RPC\Messages\StorageWriteUserFileResultMessage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Authentication\AuthenticationHelper.cs" />
<Compile Include="RPC\RPCServerStream.cs" />
<Compile Include="RPC\RPCStream.cs" />
<Compile Include="Steam\CSteamID.cs" />
<Compile Include="Steam\EAccountType.cs" />
<Compile Include="Steam\EUniverse.cs" />
<Compile Include="Steam\InteropHelp.cs" />
<Compile Include="Steam\SteamID_t.cs" />
<Compile Include="Ticket.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Steam\README.txt" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.

View File

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

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>

View File

@ -5,6 +5,7 @@ using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using log4net;
using log4net.Appender;
using log4net.Config;
@ -16,12 +17,13 @@ using uhttpsharp.Handlers;
using uhttpsharp.Headers;
using uhttpsharp.Listeners;
using uhttpsharp.RequestProviders;
using HttpResponse = uhttpsharp.HttpResponse;
namespace NPSharp.CommandLine.File
{
class Program
internal class Program
{
static void Main(string[] args)
private static void Main(string[] args)
{
// log4net setup
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
@ -35,7 +37,8 @@ namespace NPSharp.CommandLine.File
#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 } });
BasicConfigurator.Configure(new IAppender[]
{appender, new DebugAppender {Layout = appender.Layout, Threshold = Level.All}});
}
else
{
@ -48,16 +51,38 @@ namespace NPSharp.CommandLine.File
#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.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 } });
BasicConfigurator.Configure(new IAppender[]
{appender, new DebugAppender {Layout = appender.Layout, Threshold = Level.All}});
}
var log = LogManager.GetLogger("Main");
ILog log = LogManager.GetLogger("Main");
// Arguments
if (args.Length < 4)
@ -66,11 +91,11 @@ namespace NPSharp.CommandLine.File
return;
}
var hostname = args[0];
var port = ushort.Parse(args[1]);
var username = args[2];
var password = args[3];
var hport = args.Length > 4 ? ushort.Parse(args[4]) : 5680;
string hostname = args[0];
ushort port = ushort.Parse(args[1]);
string username = args[2];
string password = args[3];
int hport = args.Length > 4 ? ushort.Parse(args[4]) : 5680;
// NP connection setup
log.DebugFormat("Connecting to {0}:{1}...", hostname, port);
@ -112,7 +137,8 @@ namespace NPSharp.CommandLine.File
);
httpServer.Use(new AnonymousHttpRequestHandler((context, next) =>
{
context.Response = new HttpResponse(HttpResponseCode.NotFound, "File not found", context.Request.Headers.KeepAliveConnection());
context.Response = new HttpResponse(HttpResponseCode.NotFound, "File not found",
context.Request.Headers.KeepAliveConnection());
return Task.Factory.GetCompleted();
}));
httpServer.Start();
@ -126,8 +152,8 @@ namespace NPSharp.CommandLine.File
internal class NP2HTTPUserFileHandler : IHttpRequestHandler
{
private readonly NPClient _np;
private readonly ILog _log;
private readonly NPClient _np;
public NP2HTTPUserFileHandler(NPClient np)
{
@ -137,7 +163,9 @@ namespace NPSharp.CommandLine.File
public Task Handle(IHttpContext context, Func<Task> next)
{
var uri = context.Request.QueryString.Any() ? null : string.Join("/", context.Request.Uri.OriginalString.Split('/').Skip(2));
string uri = context.Request.QueryString.Any()
? null
: string.Join("/", context.Request.Uri.OriginalString.Split('/').Skip(2));
if (uri == null)
if (!context.Request.QueryString.TryGetByName("uri", out uri) || uri == null)
{
@ -149,7 +177,7 @@ namespace NPSharp.CommandLine.File
}
_log.InfoFormat("Requesting user file {0}", uri);
var task = _np.GetUserFile(uri);
Task<byte[]> task = _np.GetUserFile(uri);
try
{
task.Wait();
@ -158,13 +186,15 @@ namespace NPSharp.CommandLine.File
{
context.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "File not accessible",
context.Request.Headers.KeepAliveConnection(),
string.Format("<pre><tt><code>{0}</code></tt></pre>", task.Exception == null ? "Unknown error" : task.Exception.ToString())
string.Format("<pre><tt><code>{0}</code></tt></pre>",
task.Exception == null ? "Unknown error" : task.Exception.ToString())
);
return Task.Factory.GetCompleted();
}
// Return file contents
context.Response = new HttpResponse(HttpResponseCode.Ok, System.Web.MimeMapping.GetMimeMapping(uri), new MemoryStream(task.Result), context.Request.Headers.KeepAliveConnection());
context.Response = new HttpResponse(HttpResponseCode.Ok, MimeMapping.GetMimeMapping(uri),
new MemoryStream(task.Result), context.Request.Headers.KeepAliveConnection());
return Task.Factory.GetCompleted();
}
@ -172,8 +202,8 @@ namespace NPSharp.CommandLine.File
internal class NP2HTTPPublisherFileHandler : IHttpRequestHandler
{
private readonly NPClient _np;
private readonly ILog _log;
private readonly NPClient _np;
public NP2HTTPPublisherFileHandler(NPClient np)
{
@ -183,7 +213,9 @@ namespace NPSharp.CommandLine.File
public Task Handle(IHttpContext context, Func<Task> next)
{
var uri = context.Request.QueryString.Any() ? null : string.Join("/", context.Request.Uri.OriginalString.Split('/').Skip(2));
string uri = context.Request.QueryString.Any()
? null
: string.Join("/", context.Request.Uri.OriginalString.Split('/').Skip(2));
if (uri == null)
if (!context.Request.QueryString.TryGetByName("uri", out uri) || uri == null)
{
@ -195,7 +227,7 @@ namespace NPSharp.CommandLine.File
}
_log.InfoFormat("Requesting publisher file {0}", uri);
var task = _np.GetPublisherFile(uri);
Task<byte[]> task = _np.GetPublisherFile(uri);
try
{
task.Wait();
@ -204,13 +236,15 @@ namespace NPSharp.CommandLine.File
{
context.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "File not accessible",
context.Request.Headers.KeepAliveConnection(),
string.Format("<pre><tt><code>{0}</code></tt></pre>", task.Exception == null ? "Unknown error" : task.Exception.ToString())
string.Format("<pre><tt><code>{0}</code></tt></pre>",
task.Exception == null ? "Unknown error" : task.Exception.ToString())
);
return Task.Factory.GetCompleted();
}
// Return file contents
context.Response = new HttpResponse(HttpResponseCode.Ok, System.Web.MimeMapping.GetMimeMapping(uri), new MemoryStream(task.Result), context.Request.Headers.KeepAliveConnection());
context.Response = new HttpResponse(HttpResponseCode.Ok, MimeMapping.GetMimeMapping(uri),
new MemoryStream(task.Result), context.Request.Headers.KeepAliveConnection());
return Task.Factory.GetCompleted();
}

View File

@ -1,10 +1,10 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die mit einer Assembly verknüpft sind.
[assembly: AssemblyTitle("npfile")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
@ -17,9 +17,11 @@ using System.Runtime.InteropServices;
// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("549e5fde-a94d-4154-9577-5743f8be3ed3")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
@ -32,5 +34,6 @@ using System.Runtime.InteropServices;
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

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

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>

View File

@ -9,9 +9,9 @@ using NPSharp.Authentication;
namespace NPSharp.CommandLine.MOTD
{
class Program
internal class Program
{
static void Main(string[] args)
private static void Main(string[] args)
{
// log4net setup
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
@ -25,7 +25,8 @@ namespace NPSharp.CommandLine.MOTD
#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 } });
BasicConfigurator.Configure(new IAppender[]
{appender, new DebugAppender {Layout = appender.Layout, Threshold = Level.All}});
}
else
{
@ -38,16 +39,38 @@ namespace NPSharp.CommandLine.MOTD
#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.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 } });
BasicConfigurator.Configure(new IAppender[]
{appender, new DebugAppender {Layout = appender.Layout, Threshold = Level.All}});
}
var log = LogManager.GetLogger("Main");
ILog log = LogManager.GetLogger("Main");
// Arguments
if (args.Length < 4)
@ -56,10 +79,10 @@ namespace NPSharp.CommandLine.MOTD
return;
}
var hostname = args[0];
var port = ushort.Parse(args[1]);
var username = args[2];
var password = args[3];
string hostname = args[0];
ushort port = ushort.Parse(args[1]);
string username = args[2];
string password = args[3];
// NP connection setup
log.DebugFormat("Connecting to {0}:{1}...", hostname, port);
@ -104,15 +127,13 @@ namespace NPSharp.CommandLine.MOTD
try
{
log.InfoFormat("Server says: {0}", Encoding.UTF8.GetString(np.GetPublisherFile("motd-english.txt").Result));
log.InfoFormat("Server says: {0}",
Encoding.UTF8.GetString(np.GetPublisherFile("motd-english.txt").Result));
}
catch
{
log.ErrorFormat("Could not read MOTD from NP server.");
}
}
}
}

View File

@ -1,10 +1,10 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die mit einer Assembly verknüpft sind.
[assembly: AssemblyTitle("NPSharp MOTD test client")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
@ -17,9 +17,11 @@ using System.Runtime.InteropServices;
// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("b91f9ba4-757a-4c72-b12a-c3e1f1b05715")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
@ -32,5 +34,6 @@ using System.Runtime.InteropServices;
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.*")]
[assembly: AssemblyFileVersion("0.1")]

View File

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