commit 11be50dd4cd46fb9c2e39a858910dfb78c3a64bf Author: icedream Date: Wed May 7 16:17:39 2014 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7414ecf --- /dev/null +++ b/.gitignore @@ -0,0 +1,185 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +*.pubxml + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ diff --git a/libnpsharp.sln b/libnpsharp.sln new file mode 100644 index 0000000..4685577 --- /dev/null +++ b/libnpsharp.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libnpsharp", "src\libnpsharp\libnpsharp.csproj", "{1A5AC63A-250E-4BC8-B81A-822AC31F5E37}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A5AC63A-250E-4BC8-B81A-822AC31F5E37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A5AC63A-250E-4BC8-B81A-822AC31F5E37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A5AC63A-250E-4BC8-B81A-822AC31F5E37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A5AC63A-250E-4BC8-B81A-822AC31F5E37}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/libnpsharp.sln.DotSettings b/libnpsharp.sln.DotSettings new file mode 100644 index 0000000..f94f86f --- /dev/null +++ b/libnpsharp.sln.DotSettings @@ -0,0 +1,2 @@ + + NPID \ No newline at end of file diff --git a/src/libnpsharp/NPFileException.cs b/src/libnpsharp/NPFileException.cs new file mode 100644 index 0000000..75c1153 --- /dev/null +++ b/src/libnpsharp/NPFileException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NPSharp +{ + class NpFileException : Exception + { + public NpFileException() + :base(@"Could not fetch file from NP server.") + { + } + } +} diff --git a/src/libnpsharp/NpClient.cs b/src/libnpsharp/NpClient.cs new file mode 100644 index 0000000..4bb76cc --- /dev/null +++ b/src/libnpsharp/NpClient.cs @@ -0,0 +1,218 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using NPSharp.Rpc; +using NPSharp.Rpc.Packets; + +namespace NPSharp +{ + /// + /// Represents a high-level network platform client. + /// + public class NpClient + { + private readonly RpcClientStream _rpc; + private CancellationTokenSource _cancellationTokenSource; + private CancellationToken _cancellationToken; + + /// + /// Initializes the NP client with a specified host and port. + /// + /// The host to connect to. + /// The port to use. Default: 3025. + public NpClient(string host, ushort port = 3025) + { + _rpc = new RpcClientStream(host, port); + } + + /// + /// The assigned NP user ID. Will be set on successful authentication. + /// + public ulong LoginId { get; private set; } + + /// + /// The assigned session token for this client. Will be set on successful authentication. + /// + public string SessionToken { get; private set; } + + // TODO: Handle connection failures via exception + /// + /// Connects the client to the NP server. + /// + /// True if the connection succeeded, otherwise false. + public bool Connect() + { + _cancellationTokenSource = new CancellationTokenSource(); + _cancellationToken = _cancellationTokenSource.Token; + + if (!_rpc.Open()) + return false; + + Task.Factory.StartNew(() => + { + Debug.WriteLine("Processing thread start"); + + try + { + while (true) + { + var message = _rpc.Read(); + if (message == null) + continue; + + // TODO: log4net + Console.WriteLine("Received packet ID {1} (type {0})", message.GetType().Name, message.MessageId); + } + } + catch (ProtocolViolationException error) + { + Console.WriteLine("Protocol violation: {0}. Disconnect imminent.", error.Message); + } + + Debug.WriteLine("Processing thread exit"); + }, _cancellationToken); + + return true; + } + + /// + /// Disconnects the client from the NP server. + /// + public void Disconnect() + { + _cancellationTokenSource.Cancel(true); // TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read) + _rpc.Close(); + + LoginId = 0; + } + + // TODO: Try to use an exception for failed action instead + /// + /// Authenticates this connection via a token. This token has to be requested via an external interface like remauth.php. + /// + /// The token to use for authentication + /// True if the login succeeded, otherwise false. + public async Task AuthenticateWithToken(string token) + { + var tcs = new TaskCompletionSource(); + + _rpc.AttachCallback(packet => + { + var result = (AuthenticateResultMessage) packet; + if (result.Result != 0) + tcs.SetResult(false); + LoginId = result.NPID; + SessionToken = result.SessionToken; + tcs.SetResult(true); + }); + _rpc.Send(new AuthenticateWithTokenMessage {Token = token}); + + return await tcs.Task; + } + + // TODO: Try to use an exception for failed action instead + /// + /// Uploads a user file. + /// + /// The file name to save the contents to on the server + /// The raw byte contents + /// True if the upload succeeded, otherwise false. + public async Task UploadUserFile(string filename, byte[] contents) + { + var tcs = new TaskCompletionSource(); + + _rpc.AttachCallback(packet => + { + var result = (StorageWriteUserFileResultMessage) packet; + if (result.Result != 0) + tcs.SetResult(false); + tcs.SetResult(true); + }); + _rpc.Send(new StorageWriteUserFileMessage {FileData = contents, FileName = filename, NPID = LoginId}); + + return await tcs.Task; + } + + /// + /// Downloads a user file and returns its contents. + /// + /// The file to download + /// File contents as byte array + public async Task GetUserFile(string filename) + { + var tcs = new TaskCompletionSource(); + + _rpc.AttachCallback(packet => + { + var result = (StorageUserFileMessage) packet; + if (result.Result != 0) + { + tcs.SetException(new NpFileException()); + return; + } + tcs.SetResult(result.FileData); + }); + _rpc.Send(new StorageGetUserFileMessage {FileName = filename, NPID = LoginId}); + + return await tcs.Task; + } + + + /// + /// Downloads a user file onto the harddisk. + /// + /// The file to download + /// Path where to save the file + public async void DownloadUserFileTo(string filename, string targetpath) + { + var contents = await GetUserFile(filename); + + File.WriteAllBytes(targetpath, contents); + } + + + /// + /// Downloads a publisher file and returns its contents. + /// + /// The file to download + /// File contents as byte array + public async Task GetPublisherFile(string filename) + { + var tcs = new TaskCompletionSource(); + + _rpc.AttachCallback(packet => + { + var result = (StoragePublisherFileMessage) packet; + if (result.Result != 0) + { + tcs.SetException(new NpFileException()); + return; + } + tcs.SetResult(result.FileData); + }); + _rpc.Send(new StorageGetPublisherFileMessage {FileName = filename}); + + return await tcs.Task; + } + + /// + /// Downloads a publisher file onto the harddisk. + /// + /// The file to download + /// Path where to save the file + public async void DownloadPublisherFileTo(string filename, string targetpath) + { + var contents = await GetPublisherFile(filename); + + File.WriteAllBytes(targetpath, contents); + } + + public void SendRandomString(string data) + { + _rpc.Send(new StorageSendRandomStringMessage() { RandomString=data }); + } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Properties/AssemblyInfo.cs b/src/libnpsharp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1244116 --- /dev/null +++ b/src/libnpsharp/Properties/AssemblyInfo.cs @@ -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(".NET NP library")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Icedream")] +[assembly: AssemblyProduct(".NET NP library")] +[assembly: AssemblyCopyright("© 2014 Icedream")] +[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("0efdd0b5-fd69-48fd-b714-f4f0e032f417")] + +// 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("0.1.0.0")] +[assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/src/libnpsharp/Rpc/Packets/AuthenticateExternalStatusMessage.cs b/src/libnpsharp/Rpc/Packets/AuthenticateExternalStatusMessage.cs new file mode 100644 index 0000000..9a1f875 --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/AuthenticateExternalStatusMessage.cs @@ -0,0 +1,12 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1006)] + [ProtoContract] + class AuthenticateExternalStatusMessage : RpcServerMessage + { + [ProtoMember(1)] + public int Status { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/AuthenticateResultMessage.cs b/src/libnpsharp/Rpc/Packets/AuthenticateResultMessage.cs new file mode 100644 index 0000000..f1ab79a --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/AuthenticateResultMessage.cs @@ -0,0 +1,18 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1010)] + [ProtoContract] + class AuthenticateResultMessage : RpcServerMessage + { + [ProtoMember(1)] + public int Result { get; set; } + + [ProtoMember(2)] + public ulong NPID { get; set; } + + [ProtoMember(3)] + public string SessionToken { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/AuthenticateWithTokenMessage.cs b/src/libnpsharp/Rpc/Packets/AuthenticateWithTokenMessage.cs new file mode 100644 index 0000000..1fa64cb --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/AuthenticateWithTokenMessage.cs @@ -0,0 +1,12 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1003)] + [ProtoContract] + class AuthenticateWithTokenMessage : RpcClientMessage + { + [ProtoMember(1)] + public string Token { get; set; } + } +} diff --git a/src/libnpsharp/Rpc/Packets/CloseAppMessage.cs b/src/libnpsharp/Rpc/Packets/CloseAppMessage.cs new file mode 100644 index 0000000..abbc9c2 --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/CloseAppMessage.cs @@ -0,0 +1,12 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(2001)] + [ProtoContract] + class CloseAppMessage : RpcServerMessage + { + [ProtoMember(1)] + public string Reason { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/HelloMessage.cs b/src/libnpsharp/Rpc/Packets/HelloMessage.cs new file mode 100644 index 0000000..741f805 --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/HelloMessage.cs @@ -0,0 +1,22 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1000)] + [ProtoContract] + class HelloMessage : RpcServerMessage + { + // I seriously have no idea where in the code this is used but whatever + [ProtoMember(1)] + public int Number1 { get; set; } + + [ProtoMember(2)] + public int Number2 { get; set; } + + [ProtoMember(3)] + public string Name { get; set; } + + [ProtoMember(4)] + public string String2 { get; set; } + } +} diff --git a/src/libnpsharp/Rpc/Packets/MessagingSendDataMessage.cs b/src/libnpsharp/Rpc/Packets/MessagingSendDataMessage.cs new file mode 100644 index 0000000..73e6b6e --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/MessagingSendDataMessage.cs @@ -0,0 +1,15 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(2002)] + [ProtoContract] + class MessagingSendDataMessage : RpcClientMessage + { + [ProtoMember(1)] + public ulong NPID { get; set; } + + [ProtoMember(2)] + public byte[] Data { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/PacketAttribute.cs b/src/libnpsharp/Rpc/Packets/PacketAttribute.cs new file mode 100644 index 0000000..1dfe92b --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/PacketAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace NPSharp.Rpc.Packets +{ + class PacketAttribute : Attribute + { + public PacketAttribute(uint type) + { + Type = type; + } + + public uint Type { get; set; } + } +} diff --git a/src/libnpsharp/Rpc/Packets/RpcClientMessage.cs b/src/libnpsharp/Rpc/Packets/RpcClientMessage.cs new file mode 100644 index 0000000..fdc85d9 --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/RpcClientMessage.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; + +namespace NPSharp.Rpc.Packets +{ + public class RpcClientMessage : RpcMessage + { + public byte[] Serialize(uint id) + { + byte[] content; + using (var bufferStream = new MemoryStream()) + { + ProtoBuf.Serializer.Serialize(bufferStream, this); + bufferStream.Seek(0, SeekOrigin.Begin); + content = bufferStream.ToArray(); + } + + var buffer = new List(); + buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(Signature))); + buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(content.Length))); + buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(GetTypeId()))); + buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(id))); + buffer.AddRange(content); + + return buffer.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/RpcMessage.cs b/src/libnpsharp/Rpc/Packets/RpcMessage.cs new file mode 100644 index 0000000..020bf21 --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/RpcMessage.cs @@ -0,0 +1,15 @@ +using System.Linq; + +namespace NPSharp.Rpc.Packets +{ + public class RpcMessage + { + internal const uint Signature = 0xDEADC0DE; // I wonder if aiw3 changed this since kernal noted it in his source code. + + public uint GetTypeId() + { + var packet = (PacketAttribute) GetType().GetCustomAttributes(typeof (PacketAttribute), false).Single(); + return packet.Type; + } + } +} diff --git a/src/libnpsharp/Rpc/Packets/RpcServerMessage.cs b/src/libnpsharp/Rpc/Packets/RpcServerMessage.cs new file mode 100644 index 0000000..e4adc8b --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/RpcServerMessage.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reflection; + +namespace NPSharp.Rpc.Packets +{ + public class RpcServerMessage : RpcMessage + { + public uint MessageId { get; private set; } + + public static RpcServerMessage Deserialize(NetworkStream ns) + { + var header = new byte[16]; + var l = ns.Read(header, 0, header.Length); + if (l == 0) + return null; + if (l < 16) + throw new ProtocolViolationException("Received incomplete header"); + + var signature = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 0)); + var length = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 4)); + var type = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 8)); + var buffer = new byte[length]; + ns.Read(buffer, 0, buffer.Length); + + if (signature != 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("Received packet of unknown 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 + // TODO: log4net + return null; +#endif + } + packet = (RpcServerMessage)ProtoBuf.Serializer.NonGeneric.Deserialize( + types.Single(), + ms + ); + } + + packet.MessageId = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 12)); + + return packet; + } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/StorageGetPublisherFileMessage.cs b/src/libnpsharp/Rpc/Packets/StorageGetPublisherFileMessage.cs new file mode 100644 index 0000000..6f14f77 --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/StorageGetPublisherFileMessage.cs @@ -0,0 +1,12 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [ProtoContract] + [Packet(1101)] + class StorageGetPublisherFileMessage : RpcClientMessage + { + [ProtoMember(1)] + public string FileName { get; set; } + } +} diff --git a/src/libnpsharp/Rpc/Packets/StorageGetUserFileMessage.cs b/src/libnpsharp/Rpc/Packets/StorageGetUserFileMessage.cs new file mode 100644 index 0000000..acda367 --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/StorageGetUserFileMessage.cs @@ -0,0 +1,15 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1102)] + [ProtoContract] + class StorageGetUserFileMessage : RpcClientMessage + { + [ProtoMember(1)] + public string FileName { get; set; } + + [ProtoMember(2)] + public ulong NPID { get; set; } // SERIOUSLY WHY IS THIS EVEN HERE + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/StoragePublisherFileMessage.cs b/src/libnpsharp/Rpc/Packets/StoragePublisherFileMessage.cs new file mode 100644 index 0000000..c4c5c4c --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/StoragePublisherFileMessage.cs @@ -0,0 +1,18 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1111)] + [ProtoContract] + class StoragePublisherFileMessage : RpcServerMessage + { + [ProtoMember(1)] + public int Result { get; set; } + + [ProtoMember(2)] + public string FileName { get; set; } + + [ProtoMember(3)] + public byte[] FileData { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/StorageSendRandomStringMessage.cs b/src/libnpsharp/Rpc/Packets/StorageSendRandomStringMessage.cs new file mode 100644 index 0000000..4ecd748 --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/StorageSendRandomStringMessage.cs @@ -0,0 +1,12 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1104)] + [ProtoContract] + class StorageSendRandomStringMessage : RpcClientMessage + { + [ProtoMember(1)] + public string RandomString { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/StorageUserFileMessage.cs b/src/libnpsharp/Rpc/Packets/StorageUserFileMessage.cs new file mode 100644 index 0000000..cbb991b --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/StorageUserFileMessage.cs @@ -0,0 +1,21 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1112)] + [ProtoContract] + class StorageUserFileMessage : RpcServerMessage + { + [ProtoMember(1)] + public int Result { get; set; } + + [ProtoMember(2)] + public string FileName { get; set; } + + [ProtoMember(3)] + public ulong NPID { get; set; } + + [ProtoMember(4)] + public byte[] FileData { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/StorageWriteUserFileMessage.cs b/src/libnpsharp/Rpc/Packets/StorageWriteUserFileMessage.cs new file mode 100644 index 0000000..f09472c --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/StorageWriteUserFileMessage.cs @@ -0,0 +1,18 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1103)] + [ProtoContract] + class StorageWriteUserFileMessage : RpcClientMessage + { + [ProtoMember(1)] + public string FileName { get; set; } + + [ProtoMember(2)] + public ulong NPID { get; set; } + + [ProtoMember(3)] + public byte[] FileData { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/Packets/StorageWriteUserFileResultMessage.cs b/src/libnpsharp/Rpc/Packets/StorageWriteUserFileResultMessage.cs new file mode 100644 index 0000000..5bf17ca --- /dev/null +++ b/src/libnpsharp/Rpc/Packets/StorageWriteUserFileResultMessage.cs @@ -0,0 +1,18 @@ +using ProtoBuf; + +namespace NPSharp.Rpc.Packets +{ + [Packet(1113)] + [ProtoContract] + class StorageWriteUserFileResultMessage : RpcServerMessage + { + [ProtoMember(1)] + public int Result { get; set; } + + [ProtoMember(2)] + public string FileName { get; set; } + + [ProtoMember(3)] + public ulong NPID { get; set; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Rpc/RpcClientStream.cs b/src/libnpsharp/Rpc/RpcClientStream.cs new file mode 100644 index 0000000..918834e --- /dev/null +++ b/src/libnpsharp/Rpc/RpcClientStream.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using NPSharp.Rpc.Packets; + +namespace NPSharp.Rpc +{ + /// + /// Represents a low-level client stream which can communicate with an NP server using RPC packets. + /// + public class RpcClientStream + { + private NetworkStream _ns; + private uint _id; + + private readonly string _host; + private readonly ushort _port; + + private readonly Dictionary> _callbacks = new Dictionary>(); + + /// + /// Initializes an RPC connection stream with a specified host and port. + /// + /// The host to connect to. + /// The port to use. Default: 3025. + public RpcClientStream(string host, ushort port = 3025) + { + _host = host; + _port = port; + } + + /// + /// Opens the RPC stream to the NP server. + /// + /// True if the connection succeeded, otherwise false. + public bool Open() + { + // 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(); + return true; + } + + /// + /// Closes the connection with the NP server. + /// + /// + public void Close(int timeout = 2000) + { + // Connection already closed? + if (_ns == null) + throw new InvalidOperationException("Connection already closed"); + + try + { + _ns.Close(timeout); + _ns.Dispose(); + } + finally + { + _ns = null; + } + } + + /// + /// Attaches a callback to the next message being sent out. This allows handling response packets. + /// + /// The method to call when we receive a response to the next message + public void AttachCallback(Action callback) + { + 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, callback); + } + + // TODO: Exposure of message ID needed or no? + /// + /// Sends out an RPC message. + /// + /// The RPC message to send out. + /// The new ID of the message. + 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); + + return _id++; + } + + /// + /// Waits for the next RPC message from the server and reads it. + /// + /// The received server message. + public RpcServerMessage Read() + { + if (_ns == null) + throw new InvalidOperationException("You need to open the stream first."); + + var message = RpcServerMessage.Deserialize(_ns); + + if (message == null) + return null; + + if (!_callbacks.ContainsKey(message.MessageId)) + return message; + + _callbacks[message.MessageId].Invoke(message); + _callbacks.Remove(message.MessageId); + + return message; + } + } +} diff --git a/src/libnpsharp/libnpsharp.csproj b/src/libnpsharp/libnpsharp.csproj new file mode 100644 index 0000000..5478e05 --- /dev/null +++ b/src/libnpsharp/libnpsharp.csproj @@ -0,0 +1,82 @@ + + + + + Debug + AnyCPU + {1A5AC63A-250E-4BC8-B81A-822AC31F5E37} + Library + Properties + NPSharp + libnpsharp + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + ..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libnpsharp/packages.config b/src/libnpsharp/packages.config new file mode 100644 index 0000000..0cd8e89 --- /dev/null +++ b/src/libnpsharp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file