diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config index 67f8ea0..3343d68 100644 --- a/.nuget/NuGet.Config +++ b/.nuget/NuGet.Config @@ -1,4 +1,5 @@  + diff --git a/libnpsharp.sln b/libnpsharp.sln index 628a6b0..ddeb623 100644 --- a/libnpsharp.sln +++ b/libnpsharp.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{587B7B EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "npfile", "src\npfile\npfile.csproj", "{19EBF339-E076-4962-A671-5B44A978687D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "npserv", "npserv\npserv.csproj", "{1FF77692-D07C-4131-95AE-21AD2A74CA11}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,6 +36,10 @@ Global {19EBF339-E076-4962-A671-5B44A978687D}.Debug|Any CPU.Build.0 = Debug|Any CPU {19EBF339-E076-4962-A671-5B44A978687D}.Release|Any CPU.ActiveCfg = Release|Any CPU {19EBF339-E076-4962-A671-5B44A978687D}.Release|Any CPU.Build.0 = Release|Any CPU + {1FF77692-D07C-4131-95AE-21AD2A74CA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FF77692-D07C-4131-95AE-21AD2A74CA11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FF77692-D07C-4131-95AE-21AD2A74CA11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FF77692-D07C-4131-95AE-21AD2A74CA11}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/libnpsharp.sln.DotSettings b/libnpsharp.sln.DotSettings index 21542fa..39361ad 100644 --- a/libnpsharp.sln.DotSettings +++ b/libnpsharp.sln.DotSettings @@ -3,4 +3,5 @@ ID NP NPID - RPC \ No newline at end of file + RPC + UTF \ No newline at end of file diff --git a/npserv/App.config b/npserv/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/npserv/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/npserv/Program.cs b/npserv/Program.cs new file mode 100644 index 0000000..ec39285 --- /dev/null +++ b/npserv/Program.cs @@ -0,0 +1,9 @@ +namespace NPSharp.CommandLine.Server +{ + class Program + { + static void Main(string[] args) + { + } + } +} diff --git a/npserv/Properties/AssemblyInfo.cs b/npserv/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cb7195c --- /dev/null +++ b/npserv/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("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")] diff --git a/npserv/npserv.csproj b/npserv/npserv.csproj new file mode 100644 index 0000000..702efeb --- /dev/null +++ b/npserv/npserv.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {1FF77692-D07C-4131-95AE-21AD2A74CA11} + Exe + Properties + NPSharp.CommandLine.Server + npserv + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libnpsharp/Authentication/AuthenticationHelper.cs b/src/libnpsharp/Authentication/AuthenticationHelper.cs index 58b2427..e5df6c9 100644 --- a/src/libnpsharp/Authentication/AuthenticationHelper.cs +++ b/src/libnpsharp/Authentication/AuthenticationHelper.cs @@ -1,115 +1,125 @@ using System; -using System.Net; using System.IO; +using System.Net; using System.Text; using System.Text.RegularExpressions; namespace NPSharp.Authentication { - public class AuthenticationHelper - { - private string _path; - private ushort _port; - private string _host; + /// + /// Represents a client which can communicate with an authentication endpoint in order to retrieve session + /// information, including tokens for authentication with NP servers. + /// + public class AuthenticationHelper + { + private readonly string _host; + private readonly string _path; + private readonly ushort _port; - /// - /// Gets the username. - /// - /// The username. - public string Username { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// Hostname of the authentication endpoint. + /// Port of the authentication endpoint. + /// Path of the authentication endpoint. + public AuthenticationHelper(string host, ushort port = 12003, string path = "/authenticate") + { + _host = host; + _port = port; + _path = path; + } - /// - /// Gets the user's e-mail address. - /// - /// The user's e-mail address. - public string UserEMail { get; private set; } + /// + /// Gets the username. + /// + /// The username. + public string Username { get; private set; } - /// - /// Gets the session token. - /// - /// The session token. - public string SessionToken { get; private set; } + /// + /// Gets the user's e-mail address. + /// + /// The user's e-mail address. + public string UserEMail { get; private set; } - /// - /// Gets the user identifier. - /// - /// The user identifier. - public uint UserId { get; private set; } + /// + /// Gets the session token. + /// + /// The session token. + public string SessionToken { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// Hostname of the authentication endpoint. - /// Port of the authentication endpoint. - /// Path of the authentication endpoint. - public AuthenticationHelper (string host, ushort port = 12003, string path = "/authenticate") - { - _host = host; - _port = port; - _path = path; - } + /// + /// Gets the user identifier. + /// + /// The user identifier. + public uint UserId { get; private set; } - /// - /// Authenticate the specified username and password. - /// - /// The username to use for authentication. - /// The password to use for authentication. - public void Authenticate (string username, string password) - { - var post = string.Format ("{0}&&{1}", username, password); + /// + /// Authenticate the specified username and password. + /// + /// The username to use for authentication. + /// The password to use for authentication. + public void Authenticate(string username, string password) + { + string post = string.Format("{0}&&{1}", username, password); - var uri = new UriBuilder { - Scheme = "http", - Port = _port, - Host = _host, - Path = _path - }.Uri; + Uri uri = new UriBuilder + { + Scheme = "http", + Port = _port, + Host = _host, + Path = _path + }.Uri; - var req = (HttpWebRequest)WebRequest.Create(uri); + var req = (HttpWebRequest) WebRequest.Create(uri); req.Method = "POST"; req.ContentType = "application/x-www-form-urlencoded"; req.AllowAutoRedirect = true; - using (var reqStream = req.GetRequestStream()) { - var buffer = Encoding.UTF8.GetBytes (post); - reqStream.Write (buffer, 0, post.Length); - reqStream.Flush (); - } + using (Stream reqStream = req.GetRequestStream()) + { + byte[] buffer = Encoding.UTF8.GetBytes(post); + reqStream.Write(buffer, 0, post.Length); + reqStream.Flush(); + } - // Response will be in this syntax: - // (ok|fail)#text#userid#username#email#sessiontoken - var rx = new Regex("^(?ok|fail)#(?.+)#(?[0-9]+)#(?.+)#(?.+)#(?[^#]+)[#]*$"); - var resp = (HttpWebResponse)req.GetResponse (); - using (var respStream = resp.GetResponseStream()) { + // Response will be in this syntax: + // (ok|fail)#text#userid#username#email#sessiontoken + var rx = + new Regex( + "^(?ok|fail)#(?.+)#(?[0-9]+)#(?.+)#(?.+)#(?[^#]+)[#]*$"); + var resp = (HttpWebResponse) req.GetResponse(); + using (Stream respStream = resp.GetResponseStream()) + { if (respStream == null) throw new Exception(@"No answer from server"); - using (var respReader = new StreamReader(respStream)) { - while (!respReader.EndOfStream) { - var line = respReader.ReadLine (); + using (var respReader = new StreamReader(respStream)) + { + while (!respReader.EndOfStream) + { + string line = respReader.ReadLine(); // No answer? - if (string.IsNullOrEmpty(line)) - continue; + if (string.IsNullOrEmpty(line)) + continue; - // DW response line found? - if (!rx.IsMatch (line)) - continue; + // DW response line found? + if (!rx.IsMatch(line)) + continue; - // This is a DW response line, analyze - var rxm = rx.Match (line); + // This is a DW response line, analyze + Match rxm = rx.Match(line); - // Login succeeded? - if (rxm.Groups ["status"].Value != "ok") - throw new Exception (rxm.Groups ["text"].Value); - - // Store all data - Username = rxm.Groups ["username"].Value; - UserEMail = rxm.Groups ["usermail"].Value; - SessionToken = rxm.Groups ["sessiontoken"].Value; - UserId = uint.Parse (rxm.Groups ["userid"].Value); - } - } - } - } - } -} + // Login succeeded? + if (rxm.Groups["status"].Value != "ok") + throw new Exception(rxm.Groups["text"].Value); + // Store all data + Username = rxm.Groups["username"].Value; + UserEMail = rxm.Groups["usermail"].Value; + SessionToken = rxm.Groups["sessiontoken"].Value; + UserId = uint.Parse(rxm.Groups["userid"].Value); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/libnpsharp/ClientEventArgs.cs b/src/libnpsharp/ClientEventArgs.cs new file mode 100644 index 0000000..fde6690 --- /dev/null +++ b/src/libnpsharp/ClientEventArgs.cs @@ -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; } + } +} diff --git a/src/libnpsharp/NPClient.cs b/src/libnpsharp/NPClient.cs index 53e9d91..461c2f9 100644 --- a/src/libnpsharp/NPClient.cs +++ b/src/libnpsharp/NPClient.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.IO; using System.Net; using System.Threading; @@ -11,40 +10,42 @@ using NPSharp.RPC.Messages; namespace NPSharp { /// - /// Represents a high-level network platform client. + /// Represents a high-level network platform client. /// public class NPClient { - private readonly RPCClientStream _rpc; - private CancellationTokenSource _cancellationTokenSource; + private readonly string _host; + private readonly ILog _log; + private readonly ushort _port; private CancellationToken _cancellationToken; - private Task _procTask; - private readonly ILog _log; + private CancellationTokenSource _cancellationTokenSource; + private RPCClientStream _rpc; /// - /// Initializes the NP client with a specified host and port. + /// 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); - _log = LogManager.GetLogger ("NPClient"); + _host = host; + _port = port; + _log = LogManager.GetLogger("NPClient"); } /// - /// The assigned NP user ID. Will be set on successful authentication. + /// 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. + /// 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. + /// Connects the client to the NP server. /// /// True if the connection succeeded, otherwise false. public bool Connect() @@ -54,26 +55,39 @@ namespace NPSharp _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; - if (!_rpc.Open()) - return false; - - _procTask = Task.Factory.StartNew(() => + try { - _log.Debug("Now receiving RPC messages"); + _rpc = RPCClientStream.Open(_host, _port); + } + catch (Exception err) + { +#if DEBUG + _log.ErrorFormat(@"Could not initialize RPC: {0}", err); +#else + _log.ErrorFormat(@"Could not initialize RPC: {0}", err.Message); +#endif + return false; + } + + Task.Factory.StartNew(() => + { + _log.Debug("Now receiving RPC messages"); try { while (true) { - _rpc.Read(); + if (_rpc.Read() == null) + break; + _log.Debug("Disconnected."); } } catch (ProtocolViolationException error) { _log.ErrorFormat("Protocol violation: {0}. Disconnect imminent.", error.Message); - Disconnect(); + Disconnect(); } - _log.Debug("Now not receiving RPC messages anymore"); + _log.Debug("Now not receiving RPC messages anymore"); }, _cancellationToken); _log.Debug("Connect() done"); @@ -81,13 +95,14 @@ namespace NPSharp } /// - /// Disconnects the client from the NP server. + /// Disconnects the client from the NP server. /// public void Disconnect() { _log.Debug("Disconnect() start"); - _cancellationTokenSource.Cancel(true); // TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read) + _cancellationTokenSource.Cancel(true); + // TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read) //_procTask.Wait(_cancellationToken); _rpc.Close(); @@ -98,7 +113,8 @@ namespace NPSharp // TODO: Try to use an exception for failed action instead /// - /// 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. /// /// The token to use for authentication /// True if the login succeeded, otherwise false. @@ -106,7 +122,7 @@ namespace NPSharp { var tcs = new TaskCompletionSource(); - _rpc.AttachCallback(packet => + _rpc.AttachHandlerForNextMessage(packet => { var result = packet as AuthenticateResultMessage; if (result == null) @@ -117,7 +133,7 @@ namespace NPSharp LoginId = result.NPID; SessionToken = result.SessionToken; tcs.SetResult(true); - }, 10); + }); _rpc.Send(new AuthenticateWithTokenMessage {Token = token}); return await tcs.Task; @@ -125,7 +141,7 @@ namespace NPSharp // TODO: Try to use an exception for failed action instead /// - /// Uploads a user file. + /// Uploads a user file. /// /// The file name to save the contents to on the server /// The raw byte contents @@ -134,20 +150,20 @@ namespace NPSharp { var tcs = new TaskCompletionSource(); - _rpc.AttachCallback(packet => + _rpc.AttachHandlerForNextMessage(packet => { var result = (StorageWriteUserFileResultMessage) packet; if (result.Result != 0) tcs.SetResult(false); tcs.SetResult(true); - }, 10); + }); _rpc.Send(new StorageWriteUserFileMessage {FileData = contents, FileName = filename, NPID = LoginId}); return await tcs.Task; } - + /// - /// Downloads a user file and returns its contents. + /// Downloads a user file and returns its contents. /// /// The file to download /// File contents as byte array @@ -155,7 +171,7 @@ namespace NPSharp { var tcs = new TaskCompletionSource(); - _rpc.AttachCallback(packet => + _rpc.AttachHandlerForNextMessage(packet => { var result = (StorageUserFileMessage) packet; if (result.Result != 0) @@ -164,7 +180,7 @@ namespace NPSharp return; } tcs.SetResult(result.FileData); - }, 10); + }); _rpc.Send(new StorageGetUserFileMessage {FileName = filename, NPID = LoginId}); return await tcs.Task; @@ -172,20 +188,20 @@ namespace NPSharp /// - /// Downloads a user file onto the harddisk. + /// 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); + byte[] contents = await GetUserFile(filename); File.WriteAllBytes(targetpath, contents); } /// - /// Downloads a publisher file and returns its contents. + /// Downloads a publisher file and returns its contents. /// /// The file to download /// File contents as byte array @@ -193,7 +209,7 @@ namespace NPSharp { var tcs = new TaskCompletionSource(); - _rpc.AttachCallback(packet => + _rpc.AttachHandlerForNextMessage(packet => { var result = (StoragePublisherFileMessage) packet; if (result.Result != 0) @@ -202,27 +218,27 @@ namespace NPSharp return; } tcs.SetResult(result.FileData); - }, 10); + }); _rpc.Send(new StorageGetPublisherFileMessage {FileName = filename}); return await tcs.Task; } /// - /// Downloads a publisher file onto the harddisk. + /// 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); + byte[] contents = await GetPublisherFile(filename); File.WriteAllBytes(targetpath, contents); } public void SendRandomString(string data) { - _rpc.Send(new StorageSendRandomStringMessage() { RandomString=data }); + _rpc.Send(new StorageSendRandomStringMessage {RandomString = data}); } } } \ No newline at end of file diff --git a/src/libnpsharp/NPFileException.cs b/src/libnpsharp/NPFileException.cs index 6255477..cfbf03d 100644 --- a/src/libnpsharp/NPFileException.cs +++ b/src/libnpsharp/NPFileException.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace NPSharp { - class NpFileException : Exception + internal class NpFileException : Exception { internal NpFileException(int error) : base(error == 1 ? @"File not found on NP server" : @"Internal error on NP server") @@ -17,4 +14,4 @@ namespace NPSharp { } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/NPServer.cs b/src/libnpsharp/NPServer.cs new file mode 100644 index 0000000..00002b8 --- /dev/null +++ b/src/libnpsharp/NPServer.cs @@ -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 _clients; + private readonly ILog _log; + + public NPServer() + { + _log = LogManager.GetLogger("NPServer"); + _clients = new List(); + } + + private void _handleClient(NPServerClient client) + { + #region RPC authentication message handlers + client.RPC.AttachHandlerForMessageType(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(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(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(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(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(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(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 + { + /// + /// Constructs an authentication result instance. + /// + /// Set this to null if authentication should fail, otherwise use an instance of a steam ID which is unique to the user. + public AuthenticationResult(CSteamID npid = null) + { + UserID = npid; + } + + public bool Result { get { return UserID != null; } } + + public CSteamID UserID { get; private set; } + } + + public enum TicketValidationResult + { + Valid = 0, + Invalid = 1 + } + + public IFriendsHandler FriendsHandler { get; set; } + + public interface IFriendsHandler + { + IEnumerable GetFriends(NPServerClient client); + + /* + void SetFriendStatus(NPServerClient client, PresenceState presenceState, + Dictionary presenceData, ulong serverID) + { + + } + */ + } + + public class NPServerClient + { + internal NPServerClient(NPServer np, RPCServerStream rpcclient) + { + NP = np; + RPC = rpcclient; + } + + internal readonly RPCServerStream RPC; + + internal readonly NPServer NP; + + public CSteamID UserID { get; internal set; } + + public IEnumerable Friends + { + get { return NP.FriendsHandler.GetFriends(this).ToArray(); } + } + + public IEnumerable FriendConnections + { + get { return NP.Clients.Where(c => Friends.Any(f => f.NPID == c.NPID)); } + } + + internal NPServerClient DedicatedServer; + + private readonly Dictionary _presence = new Dictionary(); + + public FriendsPresence[] PresenceData { get + { + return _presence.Select(i => new FriendsPresence {Key = i.Key, Value = i.Value}).ToArray(); + } } + + public bool IsServer { get; set; } + public bool IsDirty { get; set; } + public int GroupID { get; set; } + + internal void SetPresence(string key, string value) + { + if (!_presence.ContainsKey(key)) + _presence.Add(key, value); + else + _presence[key] = value; + } + } + + public enum PresenceState + { + Offline = 0, + Online = 1, + Playing = 2 + } + + 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); + } + } +} diff --git a/src/libnpsharp/Properties/AssemblyInfo.cs b/src/libnpsharp/Properties/AssemblyInfo.cs index 7813e5b..cf6e283 100644 --- a/src/libnpsharp/Properties/AssemblyInfo.cs +++ b/src/libnpsharp/Properties/AssemblyInfo.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; // Allgemeine Informationen über eine Assembly werden über die folgenden // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, // die mit einer Assembly verknüpft sind. + [assembly: AssemblyTitle("NPSharp Library")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] @@ -16,9 +17,11 @@ using System.Runtime.InteropServices; // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. + [assembly: ComVisible(false)] // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird + [assembly: Guid("0efdd0b5-fd69-48fd-b714-f4f0e032f417")] // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: @@ -31,5 +34,6 @@ using System.Runtime.InteropServices; // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern // übernehmen, indem Sie "*" eingeben: // [assembly: AssemblyVersion("1.0.*")] + [assembly: AssemblyVersion("0.1.*")] -[assembly: AssemblyFileVersion("0.1")] +[assembly: AssemblyFileVersion("0.1")] \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerMessage.cs new file mode 100644 index 0000000..075107c --- /dev/null +++ b/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerMessage.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerResultMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerResultMessage.cs new file mode 100644 index 0000000..6f1877d --- /dev/null +++ b/src/libnpsharp/RPC/Messages/AuthenticateRegisterServerResultMessage.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/AuthenticateUserGroupMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateUserGroupMessage.cs new file mode 100644 index 0000000..a639441 --- /dev/null +++ b/src/libnpsharp/RPC/Messages/AuthenticateUserGroupMessage.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketMessage.cs new file mode 100644 index 0000000..c6bc931 --- /dev/null +++ b/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketMessage.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketResultMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketResultMessage.cs new file mode 100644 index 0000000..30fa5f3 --- /dev/null +++ b/src/libnpsharp/RPC/Messages/AuthenticateValidateTicketResultMessage.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/AuthenticateWithDetailsMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateWithDetailsMessage.cs new file mode 100644 index 0000000..27ea124 --- /dev/null +++ b/src/libnpsharp/RPC/Messages/AuthenticateWithDetailsMessage.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/AuthenticateWithKeyMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateWithKeyMessage.cs new file mode 100644 index 0000000..54d8b95 --- /dev/null +++ b/src/libnpsharp/RPC/Messages/AuthenticateWithKeyMessage.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/AuthenticateWithTokenMessage.cs b/src/libnpsharp/RPC/Messages/AuthenticateWithTokenMessage.cs index 58c5e9f..a2ad8df 100644 --- a/src/libnpsharp/RPC/Messages/AuthenticateWithTokenMessage.cs +++ b/src/libnpsharp/RPC/Messages/AuthenticateWithTokenMessage.cs @@ -9,4 +9,4 @@ namespace NPSharp.RPC.Messages [ProtoMember(1)] public string Token { get; set; } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/FriendDetails.cs b/src/libnpsharp/RPC/Messages/FriendDetails.cs index b427869..b27b13b 100644 --- a/src/libnpsharp/RPC/Messages/FriendDetails.cs +++ b/src/libnpsharp/RPC/Messages/FriendDetails.cs @@ -6,7 +6,9 @@ namespace NPSharp.RPC.Messages [ProtoContract] public sealed class FriendDetails { - internal FriendDetails() { } + internal FriendDetails() + { + } [ProtoMember(1)] public UInt64 NPID { get; set; } diff --git a/src/libnpsharp/RPC/Messages/FriendsGetProfileDataMessage.cs b/src/libnpsharp/RPC/Messages/FriendsGetProfileDataMessage.cs index 032236c..e758c79 100644 --- a/src/libnpsharp/RPC/Messages/FriendsGetProfileDataMessage.cs +++ b/src/libnpsharp/RPC/Messages/FriendsGetProfileDataMessage.cs @@ -8,7 +8,7 @@ namespace NPSharp.RPC.Messages public sealed class FriendsGetProfileDataMessage : RPCClientMessage { [ProtoMember(1)] - public UInt64[] IDs { get; set; } + public UInt64[] FriendIDs { get; set; } [ProtoMember(2)] public string ProfileType { get; set; } diff --git a/src/libnpsharp/RPC/Messages/FriendsGetUserAvatarResultMessage.cs b/src/libnpsharp/RPC/Messages/FriendsGetUserAvatarResultMessage.cs index 721f10b..44e43f3 100644 --- a/src/libnpsharp/RPC/Messages/FriendsGetUserAvatarResultMessage.cs +++ b/src/libnpsharp/RPC/Messages/FriendsGetUserAvatarResultMessage.cs @@ -6,7 +6,9 @@ namespace NPSharp.RPC.Messages [Packet(1215)] public sealed class FriendsGetUserAvatarResultMessage : RPCServerMessage { - internal FriendsGetUserAvatarResultMessage() { } + internal FriendsGetUserAvatarResultMessage() + { + } [ProtoMember(1)] public int Result { get; internal set; } diff --git a/src/libnpsharp/RPC/Messages/FriendsPresenceMessage.cs b/src/libnpsharp/RPC/Messages/FriendsPresenceMessage.cs index e37975e..3eeb8ab 100644 --- a/src/libnpsharp/RPC/Messages/FriendsPresenceMessage.cs +++ b/src/libnpsharp/RPC/Messages/FriendsPresenceMessage.cs @@ -5,7 +5,7 @@ namespace NPSharp.RPC.Messages { [ProtoContract] [Packet(1212)] - public sealed class FriendsPresenceMessage + public sealed class FriendsPresenceMessage : RPCServerMessage { internal FriendsPresenceMessage() { diff --git a/src/libnpsharp/RPC/Messages/FriendsRosterMessage.cs b/src/libnpsharp/RPC/Messages/FriendsRosterMessage.cs index 4aaeb8c..2a39a00 100644 --- a/src/libnpsharp/RPC/Messages/FriendsRosterMessage.cs +++ b/src/libnpsharp/RPC/Messages/FriendsRosterMessage.cs @@ -6,7 +6,9 @@ namespace NPSharp.RPC.Messages [Packet(1211)] public sealed class FriendsRosterMessage : RPCServerMessage { - internal FriendsRosterMessage() { } + internal FriendsRosterMessage() + { + } [ProtoMember(1)] public FriendDetails[] Friends { get; set; } diff --git a/src/libnpsharp/RPC/Messages/FriendsSetSteamIDMessage.cs b/src/libnpsharp/RPC/Messages/FriendsSetSteamIDMessage.cs index 7456a27..80bd4d2 100644 --- a/src/libnpsharp/RPC/Messages/FriendsSetSteamIDMessage.cs +++ b/src/libnpsharp/RPC/Messages/FriendsSetSteamIDMessage.cs @@ -10,4 +10,4 @@ namespace NPSharp.RPC.Messages [ProtoMember(1)] public UInt64 SteamID { get; set; } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/HelloMessage.cs b/src/libnpsharp/RPC/Messages/HelloMessage.cs index 03ab464..2e447df 100644 --- a/src/libnpsharp/RPC/Messages/HelloMessage.cs +++ b/src/libnpsharp/RPC/Messages/HelloMessage.cs @@ -19,4 +19,4 @@ namespace NPSharp.RPC.Messages [ProtoMember(4)] public string String2 { get; set; } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/PacketAttribute.cs b/src/libnpsharp/RPC/Messages/PacketAttribute.cs index fc272e2..78df66a 100644 --- a/src/libnpsharp/RPC/Messages/PacketAttribute.cs +++ b/src/libnpsharp/RPC/Messages/PacketAttribute.cs @@ -11,4 +11,4 @@ namespace NPSharp.RPC.Messages public uint Type { get; set; } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/ProfileDataResult.cs b/src/libnpsharp/RPC/Messages/ProfileDataResult.cs index 9d3ba7b..61e82ba 100644 --- a/src/libnpsharp/RPC/Messages/ProfileDataResult.cs +++ b/src/libnpsharp/RPC/Messages/ProfileDataResult.cs @@ -6,7 +6,9 @@ namespace NPSharp.RPC.Messages [ProtoContract] public sealed class ProfileDataResult { - internal ProfileDataResult() { } + internal ProfileDataResult() + { + } [ProtoMember(1)] public UInt64 NPID { get; set; } diff --git a/src/libnpsharp/RPC/Messages/RPCClientMessage.cs b/src/libnpsharp/RPC/Messages/RPCClientMessage.cs index ab09095..7fcaa83 100644 --- a/src/libnpsharp/RPC/Messages/RPCClientMessage.cs +++ b/src/libnpsharp/RPC/Messages/RPCClientMessage.cs @@ -1,83 +1,6 @@ -using System; -using System.IO; -using log4net; - namespace NPSharp.RPC.Messages { public abstract class RPCClientMessage : RPCMessage { - - private static readonly ILog Log; - - static RPCClientMessage() - { - Log = LogManager.GetLogger("RPCClientMessage"); - } - - public byte[] Serialize(uint id) - { -#if DEBUG - Log.DebugFormat("RPCClientMessage[ID={0},Type={1},TypeName={2}] {{", id, GetTypeId(), GetType().Name); - foreach (var prop in GetType().GetProperties()) - { - Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(this)); - } -#endif - - byte[] content; - using (var bufferStream = new MemoryStream()) - { - ProtoBuf.Serializer.Serialize(bufferStream, this); - bufferStream.Seek(0, SeekOrigin.Begin); - content = bufferStream.ToArray(); - } - - Log.DebugFormat("}} => Serialized to {0} bytes", content.Length); - - byte[] buffArray; - using (var ms = new MemoryStream()) - { - using (var bw = new BinaryWriter(ms)) - { - bw.Write(Signature); - bw.Write((uint)content.Length); - bw.Write(GetTypeId()); - bw.Write(id); - bw.Write(content); - bw.Flush(); - - ms.Seek(0, SeekOrigin.Begin); - buffArray = ms.ToArray(); - } - } - -#if DEBUG - Console.Write("\t"); - - Console.ForegroundColor = ConsoleColor.Gray; - Console.Write(BitConverter.ToString(buffArray, 0, sizeof(uint)).Replace("-", "")); - - Console.ResetColor(); - Console.ForegroundColor = ConsoleColor.DarkRed; - Console.Write(BitConverter.ToString(buffArray, 1 * sizeof(uint), sizeof(uint)).Replace("-", "")); - - Console.ResetColor(); - Console.ForegroundColor = ConsoleColor.Green; - Console.Write(BitConverter.ToString(buffArray, 2 * sizeof(uint), sizeof(uint)).Replace("-", "")); - - Console.ResetColor(); - Console.ForegroundColor = ConsoleColor.Yellow; - Console.Write(BitConverter.ToString(buffArray, 3 * sizeof(uint), sizeof(uint)).Replace("-", "")); - - Console.ResetColor(); - Console.ForegroundColor = ConsoleColor.Magenta; - Console.Write(BitConverter.ToString(buffArray, 4 * sizeof(uint)).Replace("-", "")); - - Console.ResetColor(); - Console.WriteLine(); -#endif - - return buffArray; - } } } \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/RPCMessage.cs b/src/libnpsharp/RPC/Messages/RPCMessage.cs index 3d1f781..bc0d170 100644 --- a/src/libnpsharp/RPC/Messages/RPCMessage.cs +++ b/src/libnpsharp/RPC/Messages/RPCMessage.cs @@ -1,15 +1,162 @@ -using System.Linq; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using log4net; +using ProtoBuf; namespace NPSharp.RPC.Messages { public abstract class RPCMessage { - internal const uint Signature = 0xDEADC0DE; // I wonder if aiw3 changed this since kernal noted it in his source code. + internal const uint Signature = 0xDEADC0DE; + // I wonder if aiw3 changed this since kernal noted it in his source code. + + private static readonly ILog Log; + + static RPCMessage() + { + Log = LogManager.GetLogger("RPC"); + } + + internal uint MessageId { get; set; } public uint GetTypeId() { var packet = (PacketAttribute) GetType().GetCustomAttributes(typeof (PacketAttribute), false).Single(); return packet.Type; } + + public static T Deserialize(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; + } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/RPCServerMessage.cs b/src/libnpsharp/RPC/Messages/RPCServerMessage.cs index 0f60e0e..a94bfa2 100644 --- a/src/libnpsharp/RPC/Messages/RPCServerMessage.cs +++ b/src/libnpsharp/RPC/Messages/RPCServerMessage.cs @@ -1,105 +1,6 @@ -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Reflection; -using log4net; - namespace NPSharp.RPC.Messages { public abstract class RPCServerMessage : RPCMessage { - - private static readonly ILog Log; - - static RPCServerMessage() - { - Log = LogManager.GetLogger("RPCServerMessage"); - } - - // Internal constructor to make classes unconstructible from outside - internal RPCServerMessage() { } - - public uint MessageId { get; private set; } - - public static RPCServerMessage Deserialize(NetworkStream ns) - { - var header = new byte[4 * sizeof(uint)]; - Log.Debug("Reading..."); - var l = ns.Read(header, 0, header.Length); - if (l == 0) - { - Log.Debug("Received 0 bytes"); - return null; - } - if (l < 16) - { - Log.ErrorFormat("Received incomplete header ({0} bytes of 16 wanted bytes)", l); - throw new ProtocolViolationException("Received incomplete header"); - } - - uint signature, length, type, pid; - using (var ms = new MemoryStream(header)) - { - using (var br = new BinaryReader(ms)) - { - signature = br.ReadUInt32(); - length = br.ReadUInt32(); - type = br.ReadUInt32(); - pid = br.ReadUInt32(); - } - } - - var buffer = new byte[length]; - ns.Read(buffer, 0, buffer.Length); - - if (signature != Signature) - { - Log.Error("Received invalid signature"); - throw new ProtocolViolationException("Received packet with invalid signature"); - } - - RPCServerMessage packet; - - using (var ms = new MemoryStream(buffer)) - { - var types = Assembly.GetExecutingAssembly().GetTypes().Where( - t => - t.IsSubclassOf(typeof (RPCServerMessage)) - && - ((PacketAttribute) t.GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type == type - ).ToArray(); - if (!types.Any()) - { - throw new ProtocolViolationException(string.Format("Received packet of unknown type ({0})", type)); - } - if (types.Count() > 1) - { -#if DEBUG - Debug.Fail(string.Format("Bug in program code: Found more than 1 type for packet ID {0}", type)); -#else - return null; -#endif - } - packet = (RPCServerMessage)ProtoBuf.Serializer.NonGeneric.Deserialize( - types.Single(), - ms - ); - } - - packet.MessageId = pid; - -#if DEBUG - Log.DebugFormat("RPCServerMessage[ID={0},Type={1},TypeName={2}] {{", pid, packet.GetTypeId(), packet.GetType().Name); - foreach (var prop in packet.GetType().GetProperties().Where(p => !(p.DeclaringType == typeof (RPCServerMessage)))) - { - Log.DebugFormat("\t{0} = {1}", prop.Name, prop.GetValue(packet)); - } - Log.DebugFormat("}} => Read from {0} bytes", header.Length + buffer.Length); -#endif - - return packet; - } } } \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/StorageGetPublisherFileMessage.cs b/src/libnpsharp/RPC/Messages/StorageGetPublisherFileMessage.cs index f4e3b86..28a7a66 100644 --- a/src/libnpsharp/RPC/Messages/StorageGetPublisherFileMessage.cs +++ b/src/libnpsharp/RPC/Messages/StorageGetPublisherFileMessage.cs @@ -9,4 +9,4 @@ namespace NPSharp.RPC.Messages [ProtoMember(1)] public string FileName { get; set; } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/Messages/StorageGetUserFileMessage.cs b/src/libnpsharp/RPC/Messages/StorageGetUserFileMessage.cs index 4b0ffa6..b212683 100644 --- a/src/libnpsharp/RPC/Messages/StorageGetUserFileMessage.cs +++ b/src/libnpsharp/RPC/Messages/StorageGetUserFileMessage.cs @@ -8,7 +8,7 @@ namespace NPSharp.RPC.Messages { [ProtoMember(1)] public string FileName { get; set; } - + [ProtoMember(2)] public ulong NPID { get; set; } // SERIOUSLY WHY IS THIS EVEN HERE } diff --git a/src/libnpsharp/RPC/Messages/StorageWriteUserFileResultMessage.cs b/src/libnpsharp/RPC/Messages/StorageWriteUserFileResultMessage.cs index fb70565..b141e13 100644 --- a/src/libnpsharp/RPC/Messages/StorageWriteUserFileResultMessage.cs +++ b/src/libnpsharp/RPC/Messages/StorageWriteUserFileResultMessage.cs @@ -11,7 +11,7 @@ namespace NPSharp.RPC.Messages [ProtoMember(2)] public string FileName { get; set; } - + [ProtoMember(3)] public ulong NPID { get; set; } } diff --git a/src/libnpsharp/RPC/RPCClientStream.cs b/src/libnpsharp/RPC/RPCClientStream.cs index b1d270f..25125fd 100644 --- a/src/libnpsharp/RPC/RPCClientStream.cs +++ b/src/libnpsharp/RPC/RPCClientStream.cs @@ -1,154 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using log4net; +using System.Net.Sockets; using NPSharp.RPC.Messages; namespace NPSharp.RPC { /// - /// 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. /// - public class RPCClientStream + public class RPCClientStream : RPCStream { - private NetworkStream _ns; - private uint _id; - private readonly ILog _log; - - 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) + public RPCClientStream(Socket sock) : base(sock) { - _host = host; - _port = port; - _log = LogManager.GetLogger("RPC"); } - /// - /// Opens the RPC stream to the NP server. - /// - /// True if the connection succeeded, otherwise false. - public bool Open() + public static RPCClientStream Open(string host, ushort port = 3025) { - _log.Debug("Open() start"); - - // Connection already established? - if (_ns != null) - throw new InvalidOperationException("Connection already opened"); - - var tcp = new TcpClient(); - try - { - tcp.Connect(_host, _port); - } - catch - { - return false; - } - _ns = tcp.GetStream(); - - _log.Debug("Open() end"); - return true; - } - - /// - /// 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 - { - _callbacks.Clear(); - _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 - /// Time in seconds from now in which this callback will expire for the next packet - public void AttachCallback(Action 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.Now + TimeSpan.FromSeconds(timeout), 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); - _ns.Flush(); - - _log.DebugFormat("Sent packet ID {1} (type {0})", message.GetType().Name, _id); - - 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) - { - _log.Debug("Recv NULL message"); - return null; - } - - if (!_callbacks.ContainsKey(message.MessageId)) - return message; - - _cleanupCallbacks(); - if (_callbacks.ContainsKey(message.MessageId)) - _callbacks[message.MessageId].Item2.Invoke(message); - - return message; - } - - private void _cleanupCallbacks() - { - var cbr = (from item in _callbacks where item.Value.Item1 < DateTime.Now select item.Key).ToArray(); - foreach (var cb in cbr) - _callbacks.Remove(cb); + var sock = new Socket( + AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + sock.Connect(host, port); + return new RPCClientStream(sock); } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/RPCServerStream.cs b/src/libnpsharp/RPC/RPCServerStream.cs new file mode 100644 index 0000000..32f04f6 --- /dev/null +++ b/src/libnpsharp/RPC/RPCServerStream.cs @@ -0,0 +1,15 @@ +using System.Net.Sockets; +using NPSharp.RPC.Messages; + +namespace NPSharp.RPC +{ + /// + /// Represents a low-level stream which communicates with an NP client using RPC messages. + /// + public class RPCServerStream : RPCStream + { + public RPCServerStream(Socket sock) : base(sock) + { + } + } +} \ No newline at end of file diff --git a/src/libnpsharp/RPC/RPCStream.cs b/src/libnpsharp/RPC/RPCStream.cs new file mode 100644 index 0000000..6a6f803 --- /dev/null +++ b/src/libnpsharp/RPC/RPCStream.cs @@ -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 +{ + /// + /// Represents a low-level client stream which can communicate using RPC packets. + /// + public abstract class RPCStream + where TSend : RPCMessage + where TRecv : RPCMessage + { + /// + /// Registered callbacks for all received messages. + /// + protected readonly List> GeneralCallbacks = + new List>(); + + /// + /// Registered callbacks for specific received message IDs. + /// + protected readonly List>> IDCallbacks = + new List>>(); + + /// + /// Registered callbacks for specific received message type IDs. + /// + protected readonly List>> TypeCallbacks = + new List>>(); + + /// + /// Logger instance. + /// + private readonly ILog _log; + + /// + /// ID of the next message. + /// + protected uint MessageID; + + /// + /// Base stream. + /// + private Socket _sock; + + /// + /// Initializes an RPC connection stream from an already established network connection. + /// + /// Client's network stream + protected RPCStream(Socket sock) + { + _log = LogManager.GetLogger("RPC"); + _sock = sock; + } + + /// + /// Sets the next message's ID. + /// + protected void IterateMessageID() + { + MessageID++; + } + + /// + /// Closes the connection. + /// + /// + 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; + } + } + + /// + /// Attaches a callback to the connection which handles a specific incoming RPC message type. + /// + /// The message type to handle, must be a subtype of RPCMessage + /// + public void AttachHandlerForMessageType(Action callback) where T : TRecv + { + TypeCallbacks.Add( + new KeyValuePair>( + ((PacketAttribute) typeof (T).GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type, + (Action)callback)); + } + + /// + /// Attaches a callback to the connection which handles a response to the next message we send. + /// + /// + public void AttachHandlerForNextMessage(Action callback) + { + IDCallbacks.Add(new KeyValuePair>(MessageID, callback)); + } + + /// + /// Attaches a callback to the connection for all incoming RPC messages. + /// + /// + public void AttachHandler(Action callback) + { + GeneralCallbacks.Add(callback); + } + + /// + /// Sends out an RPC message to the remote endpoint. + /// + /// The RPC message to send out. + /// The new ID of the message. + 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(); + } + + /// + /// Waits for the next RPC message from the remote end and reads it. + /// + /// The received server message. + public TRecv Read() + { + if (_sock == null) + throw new InvalidOperationException("You need to open the stream first."); + + var message = RPCMessage.Deserialize(_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; + } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Steam/CSteamID.cs b/src/libnpsharp/Steam/CSteamID.cs new file mode 100644 index 0000000..b8336c4 --- /dev/null +++ b/src/libnpsharp/Steam/CSteamID.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Steam/EAccountType.cs b/src/libnpsharp/Steam/EAccountType.cs new file mode 100644 index 0000000..5a41d1e --- /dev/null +++ b/src/libnpsharp/Steam/EAccountType.cs @@ -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, + }; +} \ No newline at end of file diff --git a/src/libnpsharp/Steam/EUniverse.cs b/src/libnpsharp/Steam/EUniverse.cs new file mode 100644 index 0000000..33c5073 --- /dev/null +++ b/src/libnpsharp/Steam/EUniverse.cs @@ -0,0 +1,12 @@ +namespace NPSharp.Steam +{ + public enum EUniverse + { + Invalid = 0, + Public = 1, + Beta = 2, + Internal = 3, + Dev = 4, + Max = 5, + }; +} \ No newline at end of file diff --git a/src/libnpsharp/Steam/InteropHelp.cs b/src/libnpsharp/Steam/InteropHelp.cs new file mode 100644 index 0000000..9671f5e --- /dev/null +++ b/src/libnpsharp/Steam/InteropHelp.cs @@ -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); + + /// + /// Decodes IntPtr as if it were a UTF-8 string + /// + 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); + } + + /// + /// Encodes string as an IntPtr + /// + 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); + } + } + } + } +} \ No newline at end of file diff --git a/src/libnpsharp/Steam/README.txt b/src/libnpsharp/Steam/README.txt new file mode 100644 index 0000000..84c24b6 --- /dev/null +++ b/src/libnpsharp/Steam/README.txt @@ -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. \ No newline at end of file diff --git a/src/libnpsharp/Steam/SteamID_t.cs b/src/libnpsharp/Steam/SteamID_t.cs new file mode 100644 index 0000000..85b3704 --- /dev/null +++ b/src/libnpsharp/Steam/SteamID_t.cs @@ -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) + } +} \ No newline at end of file diff --git a/src/libnpsharp/Ticket.cs b/src/libnpsharp/Ticket.cs new file mode 100644 index 0000000..ddf61d3 --- /dev/null +++ b/src/libnpsharp/Ticket.cs @@ -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; } + } +} diff --git a/src/libnpsharp/libnpsharp.csproj b/src/libnpsharp/libnpsharp.csproj index effa5d8..9ba3807 100644 --- a/src/libnpsharp/libnpsharp.csproj +++ b/src/libnpsharp/libnpsharp.csproj @@ -54,8 +54,17 @@ + + + + + + + + + @@ -87,8 +96,18 @@ + + + + + + + + + + + -