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"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<solution> <solution>
<add key="disableSourceControlIntegration" value="true" /> <add key="disableSourceControlIntegration" value="true" />

View File

@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{587B7B
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "npfile", "src\npfile\npfile.csproj", "{19EBF339-E076-4962-A671-5B44A978687D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "npfile", "src\npfile\npfile.csproj", "{19EBF339-E076-4962-A671-5B44A978687D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "npserv", "npserv\npserv.csproj", "{1FF77692-D07C-4131-95AE-21AD2A74CA11}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{19EBF339-E076-4962-A671-5B44A978687D}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE 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/=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/=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/=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,16 +1,33 @@
using System; using System;
using System.Net;
using System.IO; using System.IO;
using System.Net;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace NPSharp.Authentication namespace NPSharp.Authentication
{ {
/// <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 public class AuthenticationHelper
{ {
private string _path; private readonly string _host;
private ushort _port; private readonly string _path;
private string _host; private readonly ushort _port;
/// <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> /// <summary>
/// Gets the username. /// Gets the username.
@ -36,80 +53,73 @@ namespace NPSharp.Authentication
/// <value>The user identifier.</value> /// <value>The user identifier.</value>
public uint UserId { get; private set; } public uint UserId { 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> /// <summary>
/// Authenticate the specified username and password. /// Authenticate the specified username and password.
/// </summary> /// </summary>
/// <param name="username">The username to use for authentication.</param> /// <param name="username">The username to use for authentication.</param>
/// <param name="password">The password to use for authentication.</param> /// <param name="password">The password to use for authentication.</param>
public void Authenticate (string username, string password) public void Authenticate(string username, string password)
{ {
var post = string.Format ("{0}&&{1}", username, password); string post = string.Format("{0}&&{1}", username, password);
var uri = new UriBuilder { Uri uri = new UriBuilder
{
Scheme = "http", Scheme = "http",
Port = _port, Port = _port,
Host = _host, Host = _host,
Path = _path Path = _path
}.Uri; }.Uri;
var req = (HttpWebRequest)WebRequest.Create(uri); var req = (HttpWebRequest) WebRequest.Create(uri);
req.Method = "POST"; req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded"; req.ContentType = "application/x-www-form-urlencoded";
req.AllowAutoRedirect = true; req.AllowAutoRedirect = true;
using (var reqStream = req.GetRequestStream()) { using (Stream reqStream = req.GetRequestStream())
var buffer = Encoding.UTF8.GetBytes (post); {
reqStream.Write (buffer, 0, post.Length); byte[] buffer = Encoding.UTF8.GetBytes(post);
reqStream.Flush (); reqStream.Write(buffer, 0, post.Length);
reqStream.Flush();
} }
// Response will be in this syntax: // Response will be in this syntax:
// (ok|fail)#text#userid#username#email#sessiontoken // (ok|fail)#text#userid#username#email#sessiontoken
var rx = new Regex("^(?<status>ok|fail)#(?<text>.+)#(?<userid>[0-9]+)#(?<username>.+)#(?<usermail>.+)#(?<sessiontoken>[^#]+)[#]*$"); var rx =
var resp = (HttpWebResponse)req.GetResponse (); new Regex(
using (var respStream = resp.GetResponseStream()) { "^(?<status>ok|fail)#(?<text>.+)#(?<userid>[0-9]+)#(?<username>.+)#(?<usermail>.+)#(?<sessiontoken>[^#]+)[#]*$");
var resp = (HttpWebResponse) req.GetResponse();
using (Stream respStream = resp.GetResponseStream())
{
if (respStream == null) if (respStream == null)
throw new Exception(@"No answer from server"); throw new Exception(@"No answer from server");
using (var respReader = new StreamReader(respStream)) { using (var respReader = new StreamReader(respStream))
while (!respReader.EndOfStream) { {
var line = respReader.ReadLine (); while (!respReader.EndOfStream)
{
string line = respReader.ReadLine();
// No answer? // No answer?
if (string.IsNullOrEmpty(line)) if (string.IsNullOrEmpty(line))
continue; continue;
// DW response line found? // DW response line found?
if (!rx.IsMatch (line)) if (!rx.IsMatch(line))
continue; continue;
// This is a DW response line, analyze // This is a DW response line, analyze
var rxm = rx.Match (line); Match rxm = rx.Match(line);
// Login succeeded? // Login succeeded?
if (rxm.Groups ["status"].Value != "ok") if (rxm.Groups["status"].Value != "ok")
throw new Exception (rxm.Groups ["text"].Value); throw new Exception(rxm.Groups["text"].Value);
// Store all data // Store all data
Username = rxm.Groups ["username"].Value; Username = rxm.Groups["username"].Value;
UserEMail = rxm.Groups ["usermail"].Value; UserEMail = rxm.Groups["usermail"].Value;
SessionToken = rxm.Groups ["sessiontoken"].Value; SessionToken = rxm.Groups["sessiontoken"].Value;
UserId = uint.Parse (rxm.Groups ["userid"].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;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
@ -15,11 +14,12 @@ namespace NPSharp
/// </summary> /// </summary>
public class NPClient public class NPClient
{ {
private readonly RPCClientStream _rpc; private readonly string _host;
private CancellationTokenSource _cancellationTokenSource;
private CancellationToken _cancellationToken;
private Task _procTask;
private readonly ILog _log; private readonly ILog _log;
private readonly ushort _port;
private CancellationToken _cancellationToken;
private CancellationTokenSource _cancellationTokenSource;
private RPCClientStream _rpc;
/// <summary> /// <summary>
/// Initializes the NP client with a specified host and port. /// Initializes the NP client with a specified host and port.
@ -28,8 +28,9 @@ namespace NPSharp
/// <param name="port">The port to use. Default: 3025.</param> /// <param name="port">The port to use. Default: 3025.</param>
public NPClient(string host, ushort port = 3025) public NPClient(string host, ushort port = 3025)
{ {
_rpc = new RPCClientStream(host, port); _host = host;
_log = LogManager.GetLogger ("NPClient"); _port = port;
_log = LogManager.GetLogger("NPClient");
} }
/// <summary> /// <summary>
@ -54,17 +55,30 @@ namespace NPSharp
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token; _cancellationToken = _cancellationTokenSource.Token;
if (!_rpc.Open()) try
{
_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; return false;
}
_procTask = Task.Factory.StartNew(() => Task.Factory.StartNew(() =>
{ {
_log.Debug("Now receiving RPC messages"); _log.Debug("Now receiving RPC messages");
try try
{ {
while (true) while (true)
{ {
_rpc.Read(); if (_rpc.Read() == null)
break;
_log.Debug("Disconnected.");
} }
} }
catch (ProtocolViolationException error) catch (ProtocolViolationException error)
@ -87,7 +101,8 @@ namespace NPSharp
{ {
_log.Debug("Disconnect() start"); _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); //_procTask.Wait(_cancellationToken);
_rpc.Close(); _rpc.Close();
@ -98,7 +113,8 @@ namespace NPSharp
// TODO: Try to use an exception for failed action instead // TODO: Try to use an exception for failed action instead
/// <summary> /// <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> /// </summary>
/// <param name="token">The token to use for authentication</param> /// <param name="token">The token to use for authentication</param>
/// <returns>True if the login succeeded, otherwise false.</returns> /// <returns>True if the login succeeded, otherwise false.</returns>
@ -106,7 +122,7 @@ namespace NPSharp
{ {
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
_rpc.AttachCallback(packet => _rpc.AttachHandlerForNextMessage(packet =>
{ {
var result = packet as AuthenticateResultMessage; var result = packet as AuthenticateResultMessage;
if (result == null) if (result == null)
@ -117,7 +133,7 @@ namespace NPSharp
LoginId = result.NPID; LoginId = result.NPID;
SessionToken = result.SessionToken; SessionToken = result.SessionToken;
tcs.SetResult(true); tcs.SetResult(true);
}, 10); });
_rpc.Send(new AuthenticateWithTokenMessage {Token = token}); _rpc.Send(new AuthenticateWithTokenMessage {Token = token});
return await tcs.Task; return await tcs.Task;
@ -134,13 +150,13 @@ namespace NPSharp
{ {
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
_rpc.AttachCallback(packet => _rpc.AttachHandlerForNextMessage(packet =>
{ {
var result = (StorageWriteUserFileResultMessage) packet; var result = (StorageWriteUserFileResultMessage) packet;
if (result.Result != 0) if (result.Result != 0)
tcs.SetResult(false); tcs.SetResult(false);
tcs.SetResult(true); tcs.SetResult(true);
}, 10); });
_rpc.Send(new StorageWriteUserFileMessage {FileData = contents, FileName = filename, NPID = LoginId}); _rpc.Send(new StorageWriteUserFileMessage {FileData = contents, FileName = filename, NPID = LoginId});
return await tcs.Task; return await tcs.Task;
@ -155,7 +171,7 @@ namespace NPSharp
{ {
var tcs = new TaskCompletionSource<byte[]>(); var tcs = new TaskCompletionSource<byte[]>();
_rpc.AttachCallback(packet => _rpc.AttachHandlerForNextMessage(packet =>
{ {
var result = (StorageUserFileMessage) packet; var result = (StorageUserFileMessage) packet;
if (result.Result != 0) if (result.Result != 0)
@ -164,7 +180,7 @@ namespace NPSharp
return; return;
} }
tcs.SetResult(result.FileData); tcs.SetResult(result.FileData);
}, 10); });
_rpc.Send(new StorageGetUserFileMessage {FileName = filename, NPID = LoginId}); _rpc.Send(new StorageGetUserFileMessage {FileName = filename, NPID = LoginId});
return await tcs.Task; return await tcs.Task;
@ -178,7 +194,7 @@ namespace NPSharp
/// <param name="targetpath">Path where to save the file</param> /// <param name="targetpath">Path where to save the file</param>
public async void DownloadUserFileTo(string filename, string targetpath) public async void DownloadUserFileTo(string filename, string targetpath)
{ {
var contents = await GetUserFile(filename); byte[] contents = await GetUserFile(filename);
File.WriteAllBytes(targetpath, contents); File.WriteAllBytes(targetpath, contents);
} }
@ -193,7 +209,7 @@ namespace NPSharp
{ {
var tcs = new TaskCompletionSource<byte[]>(); var tcs = new TaskCompletionSource<byte[]>();
_rpc.AttachCallback(packet => _rpc.AttachHandlerForNextMessage(packet =>
{ {
var result = (StoragePublisherFileMessage) packet; var result = (StoragePublisherFileMessage) packet;
if (result.Result != 0) if (result.Result != 0)
@ -202,7 +218,7 @@ namespace NPSharp
return; return;
} }
tcs.SetResult(result.FileData); tcs.SetResult(result.FileData);
}, 10); });
_rpc.Send(new StorageGetPublisherFileMessage {FileName = filename}); _rpc.Send(new StorageGetPublisherFileMessage {FileName = filename});
return await tcs.Task; return await tcs.Task;
@ -215,14 +231,14 @@ namespace NPSharp
/// <param name="targetpath">Path where to save the file</param> /// <param name="targetpath">Path where to save the file</param>
public async void DownloadPublisherFileTo(string filename, string targetpath) public async void DownloadPublisherFileTo(string filename, string targetpath)
{ {
var contents = await GetPublisherFile(filename); byte[] contents = await GetPublisherFile(filename);
File.WriteAllBytes(targetpath, contents); File.WriteAllBytes(targetpath, contents);
} }
public void SendRandomString(string data) 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;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NPSharp namespace NPSharp
{ {
class NpFileException : Exception internal class NpFileException : Exception
{ {
internal NpFileException(int error) internal NpFileException(int error)
: base(error == 1 ? @"File not found on NP server" : @"Internal error on NP server") : 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 // Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die mit einer Assembly verknüpft sind. // die mit einer Assembly verknüpft sind.
[assembly: AssemblyTitle("NPSharp Library")] [assembly: AssemblyTitle("NPSharp Library")]
[assembly: AssemblyDescription("")] [assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
@ -16,9 +17,11 @@ using System.Runtime.InteropServices;
// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar // 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 // 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. // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird // 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")] [assembly: Guid("0efdd0b5-fd69-48fd-b714-f4f0e032f417")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: // 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 // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben: // übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.*")] [assembly: AssemblyVersion("0.1.*")]
[assembly: AssemblyFileVersion("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] [ProtoContract]
public sealed class FriendDetails public sealed class FriendDetails
{ {
internal FriendDetails() { } internal FriendDetails()
{
}
[ProtoMember(1)] [ProtoMember(1)]
public UInt64 NPID { get; set; } public UInt64 NPID { get; set; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,83 +1,6 @@
using System;
using System.IO;
using log4net;
namespace NPSharp.RPC.Messages namespace NPSharp.RPC.Messages
{ {
public abstract class RPCClientMessage : RPCMessage 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 namespace NPSharp.RPC.Messages
{ {
public abstract class RPCMessage 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() public uint GetTypeId()
{ {
var packet = (PacketAttribute) GetType().GetCustomAttributes(typeof (PacketAttribute), false).Single(); var packet = (PacketAttribute) GetType().GetCustomAttributes(typeof (PacketAttribute), false).Single();
return packet.Type; 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 namespace NPSharp.RPC.Messages
{ {
public abstract class RPCServerMessage : RPCMessage 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.Net.Sockets;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using log4net;
using NPSharp.RPC.Messages; using NPSharp.RPC.Messages;
namespace NPSharp.RPC namespace NPSharp.RPC
{ {
/// <summary> /// <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> /// </summary>
public class RPCClientStream public class RPCClientStream : RPCStream<RPCClientMessage, RPCServerMessage>
{ {
private NetworkStream _ns; public RPCClientStream(Socket sock) : base(sock)
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)
{ {
_host = host;
_port = port;
_log = LogManager.GetLogger("RPC");
} }
/// <summary> public static RPCClientStream Open(string host, ushort port = 3025)
/// Opens the RPC stream to the NP server.
/// </summary>
/// <returns>True if the connection succeeded, otherwise false.</returns>
public bool Open()
{ {
_log.Debug("Open() start"); var sock = new Socket(
AddressFamily.InterNetwork,
// Connection already established? SocketType.Stream,
if (_ns != null) ProtocolType.Tcp);
throw new InvalidOperationException("Connection already opened"); sock.Connect(host, port);
return new RPCClientStream(sock);
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);
} }
} }
} }

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" /> <Reference Include="System.Xml.Serialization" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ClientEventArgs.cs" />
<Compile Include="NPClient.cs" /> <Compile Include="NPClient.cs" />
<Compile Include="NPFileException.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\FriendDetails.cs" />
<Compile Include="RPC\Messages\FriendsSetSteamIDMessage.cs" /> <Compile Include="RPC\Messages\FriendsSetSteamIDMessage.cs" />
<Compile Include="RPC\Messages\FriendsGetProfileDataMessage.cs" /> <Compile Include="RPC\Messages\FriendsGetProfileDataMessage.cs" />
@ -87,8 +96,18 @@
<Compile Include="RPC\Messages\StorageWriteUserFileResultMessage.cs" /> <Compile Include="RPC\Messages\StorageWriteUserFileResultMessage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Authentication\AuthenticationHelper.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>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.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.

View File

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

View File

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

View File

@ -5,6 +5,7 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
using log4net; using log4net;
using log4net.Appender; using log4net.Appender;
using log4net.Config; using log4net.Config;
@ -16,12 +17,13 @@ using uhttpsharp.Handlers;
using uhttpsharp.Headers; using uhttpsharp.Headers;
using uhttpsharp.Listeners; using uhttpsharp.Listeners;
using uhttpsharp.RequestProviders; using uhttpsharp.RequestProviders;
using HttpResponse = uhttpsharp.HttpResponse;
namespace NPSharp.CommandLine.File namespace NPSharp.CommandLine.File
{ {
class Program internal class Program
{ {
static void Main(string[] args) private static void Main(string[] args)
{ {
// log4net setup // log4net setup
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
@ -35,7 +37,8 @@ namespace NPSharp.CommandLine.File
#endif #endif
Layout = new PatternLayout("<%d{HH:mm:ss}> [%logger:%thread] %level: %message%newline"), 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 else
{ {
@ -48,16 +51,38 @@ namespace NPSharp.CommandLine.File
#endif #endif
Layout = new PatternLayout("<%d{HH:mm:ss}> [%logger:%thread] %level: %message%newline"), 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
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 }); Level = Level.Debug,
appender.AddMapping(new ColoredConsoleAppender.LevelColors { Level = Level.Error, ForeColor = ColoredConsoleAppender.Colors.Red | ColoredConsoleAppender.Colors.HighIntensity }); ForeColor = ColoredConsoleAppender.Colors.Cyan | 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.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(); 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 // Arguments
if (args.Length < 4) if (args.Length < 4)
@ -66,11 +91,11 @@ namespace NPSharp.CommandLine.File
return; return;
} }
var hostname = args[0]; string hostname = args[0];
var port = ushort.Parse(args[1]); ushort port = ushort.Parse(args[1]);
var username = args[2]; string username = args[2];
var password = args[3]; string password = args[3];
var hport = args.Length > 4 ? ushort.Parse(args[4]) : 5680; int hport = args.Length > 4 ? ushort.Parse(args[4]) : 5680;
// NP connection setup // NP connection setup
log.DebugFormat("Connecting to {0}:{1}...", hostname, port); log.DebugFormat("Connecting to {0}:{1}...", hostname, port);
@ -112,7 +137,8 @@ namespace NPSharp.CommandLine.File
); );
httpServer.Use(new AnonymousHttpRequestHandler((context, next) => 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(); return Task.Factory.GetCompleted();
})); }));
httpServer.Start(); httpServer.Start();
@ -126,8 +152,8 @@ namespace NPSharp.CommandLine.File
internal class NP2HTTPUserFileHandler : IHttpRequestHandler internal class NP2HTTPUserFileHandler : IHttpRequestHandler
{ {
private readonly NPClient _np;
private readonly ILog _log; private readonly ILog _log;
private readonly NPClient _np;
public NP2HTTPUserFileHandler(NPClient np) public NP2HTTPUserFileHandler(NPClient np)
{ {
@ -137,7 +163,9 @@ namespace NPSharp.CommandLine.File
public Task Handle(IHttpContext context, Func<Task> next) 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 (uri == null)
if (!context.Request.QueryString.TryGetByName("uri", out uri) || 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); _log.InfoFormat("Requesting user file {0}", uri);
var task = _np.GetUserFile(uri); Task<byte[]> task = _np.GetUserFile(uri);
try try
{ {
task.Wait(); task.Wait();
@ -158,13 +186,15 @@ namespace NPSharp.CommandLine.File
{ {
context.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "File not accessible", context.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "File not accessible",
context.Request.Headers.KeepAliveConnection(), 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 Task.Factory.GetCompleted();
} }
// Return file contents // 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(); return Task.Factory.GetCompleted();
} }
@ -172,8 +202,8 @@ namespace NPSharp.CommandLine.File
internal class NP2HTTPPublisherFileHandler : IHttpRequestHandler internal class NP2HTTPPublisherFileHandler : IHttpRequestHandler
{ {
private readonly NPClient _np;
private readonly ILog _log; private readonly ILog _log;
private readonly NPClient _np;
public NP2HTTPPublisherFileHandler(NPClient np) public NP2HTTPPublisherFileHandler(NPClient np)
{ {
@ -183,7 +213,9 @@ namespace NPSharp.CommandLine.File
public Task Handle(IHttpContext context, Func<Task> next) 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 (uri == null)
if (!context.Request.QueryString.TryGetByName("uri", out uri) || 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); _log.InfoFormat("Requesting publisher file {0}", uri);
var task = _np.GetPublisherFile(uri); Task<byte[]> task = _np.GetPublisherFile(uri);
try try
{ {
task.Wait(); task.Wait();
@ -204,13 +236,15 @@ namespace NPSharp.CommandLine.File
{ {
context.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "File not accessible", context.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "File not accessible",
context.Request.Headers.KeepAliveConnection(), 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 Task.Factory.GetCompleted();
} }
// Return file contents // 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(); return Task.Factory.GetCompleted();
} }

View File

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

View File

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

View File

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

View File

@ -9,9 +9,9 @@ using NPSharp.Authentication;
namespace NPSharp.CommandLine.MOTD namespace NPSharp.CommandLine.MOTD
{ {
class Program internal class Program
{ {
static void Main(string[] args) private static void Main(string[] args)
{ {
// log4net setup // log4net setup
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
@ -25,7 +25,8 @@ namespace NPSharp.CommandLine.MOTD
#endif #endif
Layout = new PatternLayout("<%d{HH:mm:ss}> [%logger:%thread] %level: %message%newline"), 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 else
{ {
@ -38,16 +39,38 @@ namespace NPSharp.CommandLine.MOTD
#endif #endif
Layout = new PatternLayout("<%d{HH:mm:ss}> [%logger:%thread] %level: %message%newline"), 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
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 }); Level = Level.Debug,
appender.AddMapping(new ColoredConsoleAppender.LevelColors { Level = Level.Error, ForeColor = ColoredConsoleAppender.Colors.Red | ColoredConsoleAppender.Colors.HighIntensity }); ForeColor = ColoredConsoleAppender.Colors.Cyan | 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.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(); 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 // Arguments
if (args.Length < 4) if (args.Length < 4)
@ -56,10 +79,10 @@ namespace NPSharp.CommandLine.MOTD
return; return;
} }
var hostname = args[0]; string hostname = args[0];
var port = ushort.Parse(args[1]); ushort port = ushort.Parse(args[1]);
var username = args[2]; string username = args[2];
var password = args[3]; string password = args[3];
// NP connection setup // NP connection setup
log.DebugFormat("Connecting to {0}:{1}...", hostname, port); log.DebugFormat("Connecting to {0}:{1}...", hostname, port);
@ -104,15 +127,13 @@ namespace NPSharp.CommandLine.MOTD
try 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 catch
{ {
log.ErrorFormat("Could not read MOTD from NP server."); log.ErrorFormat("Could not read MOTD from NP server.");
} }
} }
} }
} }

View File

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

View File

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