move to github

This commit is contained in:
Magnus von Wachenfeldt 2015-12-04 10:23:49 +01:00
commit 2a9d8ce416
252 changed files with 45041 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# User-specific files
*.suo
*.user
# Build results
Debug/
Release/
*.ncb
*.aps
*.obj
.builds
TestResult.xml
output/
*.vshost.exe
*.Publish.xml
# ReSharper is a .NET coding add-in
_ReSharper*
*.ReSharper
# Others
**/[Bb]in
**/[Oo]bj
sql
TestResults
*.Cache
ClientBin
stylecop.*
~$*
*.dbmdl
*.log
*.log.20*
AssemblyCache.config
GravityForce/Content/obj/GravityForceContent.contentproj.XactOutput.FileList.txt

Binary file not shown.

Binary file not shown.

BIN
Library/NAudio.dll Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Library/NVorbis.dll Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@

* The NetBuffer object is gone; instead there are NetOutgoingMessage and NetIncomingMessage objects
* No need to allocate a read buffer before calling ReadMessage

View File

@ -0,0 +1,113 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<style type="text/css">
body
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
margin-left: 20px;
background-color: #dddddd;
}
td
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
}
.page
{
width: 700px;
}
.cf
{
font-family: Courier New;
font-size: 10pt;
color: black;
background: white;
padding: 16px;
border: 1px solid black;
}
.cl
{
margin: 0px;
}
.cb1
{
color: green;
}
.cb2
{
color: #2b91af;
}
.cb3
{
color: blue;
}
.cb4
{
color: #a31515;
}
</style>
<title>Peer/server discovery</title>
</head>
<body>
<table>
<tr>
<td class="page">
<h1>Peer/server discovery</h1>
<p>
Peer discovery is the process of clients detecting what servers are available. Discovery requests can be made in two ways;
locally as a broadcast, which will send a signal to all peers on your subnet. Secondly you can contact an ip address directly
and query it if a server is running.
</p>
<p>Responding to discovery requests are done in the same way regardless of how the request is made.</p>
<p>Here's how to do on the client side; ie. the side which makes a request:</p>
<div class="cf">
<pre class="cl"><span class="cb1">// Enable DiscoveryResponse messages</span></pre>
<pre class="cl">config.EnableMessageType(<span class="cb2">NetIncomingMessageType</span>.DiscoveryResponse);</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl"><span class="cb1">// Emit a discovery signal</span></pre>
<pre class="cl">Client.DiscoverLocalPeers(14242);</pre>
</div>
<p>This will send a discovery signal to your subnet; Here's how to receive the signal on the server side, and send a response back to the client:</p>
<div class="cf">
<pre class="cl"><span class="cb1">// Enable DiscoveryRequest messages</span></pre>
<pre class="cl">config.EnableMessageType(<span class="cb2">NetIncomingMessageType</span>.DiscoveryRequest);</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl"><span class="cb1">// Standard message reading loop</span></pre>
<pre class="cl"><span class="cb3">while</span> ((inc = Server.ReadMessage()) != <span class="cb1">null</span>)</pre>
<pre class="cl">{</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; <span class="cb3">switch</span> (inc.MessageType)</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; {</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.DiscoveryRequest:</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb1">// Create a response and write some example data to it</span></pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb2">NetOutgoingMessage</span> response = Server.CreateMessage();</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; response.Write(<span class="cb4">&quot;My server name&quot;</span>);</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb1">// Send the response to the sender of the request</span></pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Server.SendDiscoveryResponse(response, inc.SenderEndpoint);</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">break</span>;</pre>
</div>
<p>When the response then reaches the client, you can read the data you wrote on the server:</p>
<div class="cf">
<pre class="cl"><span class="cb1">// Standard message reading loop</span></pre>
<pre class="cl"><span class="cb3">while</span> ((inc = Client.ReadMessage()) != <span class="cb1">null</span>)</pre>
<pre class="cl">{</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; <span class="cb3">switch</span> (inc.MessageType)</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; {</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.DiscoveryResponse:</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb2">Console</span>.WriteLine(<span class="cb4">&quot;Found server at &quot;</span> + inc.SenderEndpoint + <span class="cb4">&quot; name: &quot;</span> + inc.ReadString());</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">break</span>;</pre>
</div>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,22 @@

Improvements over last version of library:
* New delivery type: Reliable sequenced (Lost packets are resent but late arrivals are dropped)
* Disconnects and shutdown requests are now queued properly, so calling shutdown will still send any queued messages before shutting down
* All messages are pooled/recycled for zero garbage
* Reduced CPU usage and lower latencies (in the <1 ms range, but still) due to better socket polling
* All public members of NetPeer/NetConnection are completely thread safe
* Larger number of delivery channels
* More exact roundtrip measurement
* Method serialize entire objects via reflection
* Unique identifier now exists for all peers/connections
* More flexible peer discovery; filters possible and arbitrary data can be sent with response
* Much better protection against malformed messages crashing the app
API enhancements:
* NetPeerConfiguration immutable properties now locked once NetPeer is initialized
* Messages cannot be send twice by accident
* Impossible to confuse sending and receiving buffers since they're different classes
* No more confusion if user should create a buffer or preallocate and reuse

View File

@ -0,0 +1,17 @@

PER MESSAGE:
7 bits - NetMessageType
1 bit - Is a message fragment?
[8 bits NetMessageLibraryType, if NetMessageType == Library]
[16 bits sequence number, if NetMessageType >= UserSequenced]
8/16 bits - Payload length in bits (variable size ushort)
[16 bits fragments group id, if fragmented]
[16 bits fragments total count, if fragmented]
[16 bits fragment number, if fragmented]
[x - Payload] if length > 0

View File

@ -0,0 +1,115 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<style type="text/css">
body
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
margin-left: 20px;
background-color: #dddddd;
}
td
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
}
.cf
{
font-family: Courier New;
font-size: 10pt;
color: black;
background: white;
padding: 16px;
border: 1px solid black;
}
.cl
{
margin: 0px;
}
.cb1
{
color: green;
}
.cb2
{
color: #2b91af;
}
.cb3
{
color: blue;
}
.cb4
{
color: #a31515;
}
</style>
<title>Lidgren tutorial</title>
</head>
<body>
<table>
<tr>
<td width="700">
<h1>Simulating bad network conditions</h1>
<p>
On the internet, your packets are likely to run in to all kinds of trouble. They will be delayed and lost and they might even arrive multiple times at the destination. Lidgren has a few option to simulate how your application or game will react when this happens.<br />
They are all configured using the NetPeerConfiguration class - these properties exists:</p>
<p>
<div class="cf">
<table>
<tr>
<td valign="top">
<b>SimulatedLoss</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This is a float which simulates lost packets. A value of 0 will disable this feature, a value of 0.5f will make half of your sent packets disappear, chosen randomly. Note that packets may contain several messages - this is the amount of packets lost.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>SimulatedDuplicatesChance</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This is a float which determines the chance that a packet will be duplicated at the destination. 0 means no packets will be duplicated, 0.5f means that on average, every other packet will be duplicated.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>SimulatedMinimumLatency</b><br />
<b>SimulatedRandomLatency</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
These two properties control simulating delay of packets in seconds (not milliseconds, use 0.05 for 50 ms of lag). They work on top of the actual network delay and the total delay will be:<br />
Actual one way latency + SimulatedMinimumLatency + [Randomly per packet 0 to SimulatedRandomLatency seconds]
</td>
</tr>
</table>
</div>
<p>It's recommended to assume symmetric condtions and configure server and client with the same simulation settings.</p>
<p>Simulating bad network conditions only works in DEBUG builds.</p>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,17 @@

Completed features:
* Message coalescing
* Peer, connection statistics
* Lag, loss and duplication simulation for testing
* Connection approval
* Throttling
* Clock synchronization to detect jitter per packet (NetTime.RemoteNow)
* Peer discovery
* Message fragmentation
Missing features:
* Receipts 25% done, need design
* More realistic lag/loss (lumpy)
* Detect estimated packet loss
* More advanced ack packet

View File

@ -0,0 +1,206 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<style type="text/css">
body
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
margin-left: 20px;
background-color: #dddddd;
}
td
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
}
.cf
{
font-family: Courier New;
font-size: 10pt;
color: black;
background: white;
padding: 16px;
border: 1px solid black;
}
.cl
{
margin: 0px;
}
.cb1
{
color: green;
}
.cb2
{
color: #2b91af;
}
.cb3
{
color: blue;
}
.cb4
{
color: #a31515;
}
</style>
<title>Lidgren basics tutorial</title>
</head>
<body>
<table>
<tr>
<td width="700">
<h1>
Lidgren basics</h1>
<p>
Lidgren network library is all about messages. There are two types of messages:</p>
<li>Library messages telling you things like a peer has connected or diagnostics messages (warnings, errors) when unexpected things happen.</li>
<li>Data messages which is data sent from a remote (connected or unconnected) peer.</li>
<p>
The base class for establishing connections, receiving and sending message are the NetPeer class. Using it you can make a peer-to-peer network, but if you are creating a server/client topology there are special classes called NetServer and NetClient. They inherit NetPeer but sets some defaults and includes some helper methods/properties.</p>
<p>
Here's how to set up a NetServer:</p>
<div class="cf">
<pre class="cl"><span class="cb2">NetPeerConfiguration</span> config = <span class="cb3">new</span> <span class="cb2">NetPeerConfiguration</span>(<span class="cb4">&quot;MyExampleName&quot;</span>);</pre>
<pre class="cl">config.Port = 14242;</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl"><span class="cb2">NetServer</span> server = <span class="cb3">new</span> <span class="cb2">NetServer</span>(config);</pre>
<pre class="cl">server.Start();</pre>
</div>
<p>
The code above first creates a configuration. It has lots of properties you can change, but the default values should be pretty good for most applications. The string you provide in the constructor (MyExampleName) is an identifier to distinquish it from other applications using the lidgren library. Just make sure you use the same string in both server and client - or you will be unable to communicate between them.</p>
<p>
Secondly we've set the local port the server should listen to. This is the port number we tell the client(s) what port number to connect to. The local port can be set for a client too, but it's not needed and not recommended.</p>
<p>
Thirdly we create our server object and fourth we Start() it. Starting the server will create a new network thread and bind to a socket and start listening for connections.</p>
<p>
Early on we spoke about messages; now is the time to start receiving and sending some. Here's a code snippet for receiving messages:</p>
<div class="cf">
<pre class="cl"><span class="cb2">NetIncomingMessage</span> msg;</pre>
<pre class="cl"><span class="cb3">while</span> ((msg = server.ReadMessage()) != <span class="cb3">null</span>)</pre>
<pre class="cl">{</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; <span class="cb3">switch</span> (msg.MessageType)</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; {</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.VerboseDebugMessage:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.DebugMessage:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.WarningMessage:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.ErrorMessage:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb2">Console</span>.WriteLine(msg.ReadString());</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">break</span>;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">default</span>:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb2">Console</span>.WriteLine(<span class="cb4">&quot;Unhandled type: &quot;</span> + msg.MessageType);</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">break</span>;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; }</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; server.Recycle(msg);</pre>
<pre class="cl">}</pre>
</div>
<p>
So, lets dissect the above code. First we declare a NetIncomingMessage, which is the type of incoming messages. Then we read a message and handles it, looping back as long as there are messages to fetch. For each message we find, we switch on sometime called MessageType - it's a description what the message contains. In this code example we only catch messages of type VerboseDebugMessage, DebugMessage, WarningMessage and ErrorMessage. All those four types are emitted by the library to inform about various events. They all contains a single string, so we use the method ReadString() to extract a copy of that string and print it in the console.</p>
<p>
Reading data will increment the internal message pointer so you can read subsequent data using the Read*() methods.</p>
<p>
For all other message type we just print that it's currently unhandled.</p>
<p>
Finally, we recycle the message after we're done with it - this will enable the library to reuse the object and create less garbage.</p>
<p>
Sending messages are even easier:</p>
<div class="cf">
<pre class="cl"><span class="cb2">NetOutgoingMessage</span> sendMsg = server.CreateMessage();</pre>
<pre class="cl">sendMsg.Write(<span class="cb4">&quot;Hello&quot;</span>);</pre>
<pre class="cl">sendMsg.Write(42);</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl">server.SendMessage(sendMsg, recipient, <span class="cb2">NetDeliveryMethod</span>.ReliableOrdered);</pre>
</div>
<p>
The above code first creates a new message, or uses a recycled message, which is why it's not possible to just create a message using new(). It then writes a string ("Hello") and an integer (System.Int32, 4 bytes in size) to the message.</p>
<p>
Then the message is sent using the SendMessage() method. The first argument is the message to send, the second argument is the recipient connection - which we'll not go into detail about just yet - and the third argument are HOW to deliver the message, or rather how to behave if network conditions are bad and a packet gets lost, duplicated or reordered.</p>
<p>
There are five delivery methods available:</p>
<div class="cf">
<table>
<tr>
<td valign="top">
<b>Unreliable</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This is just UDP. Messages can be lost, received more than once and messages sent after other messages may be received before them.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>UnreliableSequenced</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
Using this delivery method messages can still be lost; but you're protected against duplicated messages and if a message arrives late; that is, if a message sent after this one has already been received - it will be dropped. This means you will never receive "older" data than what you already have received.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>ReliableUnordered</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This delivery method ensures that every message sent will be received eventually. It does not however guarantee what order they will be received; late messages may be delivered before older ones.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>ReliableSequenced</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This delivery method is similar to UnreliableSequenced; except that is guarantees that SOME messages will be received - if you only send one message - it will be received. If you sent two messages quickly, and they get reordered in transit, only the newest message will be received - but at least ONE of them will be received guaranteed.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top"><b>ReliableOrdered</b></td>
<td>&nbsp;</td>
<td valign="top">
This delivery method guarantees that messages will always be received in the exact order they were sent.
</td>
</tr>
</table>
</div>
<p>
Here's how to read and decode the message above:</p>
<div class="cf">
<pre class="cl"><span class="cb2">NetIncomingMessage</span> incMsg = server.ReadMessage();</pre>
<pre class="cl"><span class="cb3">string</span> str = incMsg.ReadString();</pre>
<pre class="cl"><span class="cb3">int</span> a = incMsg.ReadInt32();</pre>
</div>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace Lidgren.Network
{
public class NetAESEncryption : NetCryptoProviderBase
{
public NetAESEncryption(NetPeer peer)
: base(peer, new AesCryptoServiceProvider())
{
}
public NetAESEncryption(NetPeer peer, string key)
: base(peer, new AesCryptoServiceProvider())
{
SetKey(key);
}
public NetAESEncryption(NetPeer peer, byte[] data, int offset, int count)
: base(peer, new AesCryptoServiceProvider())
{
SetKey(data, offset, count);
}
}
}

View File

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
namespace Lidgren.Network
{
/// <summary>
/// Base for a non-threadsafe encryption class
/// </summary>
public abstract class NetBlockEncryptionBase : NetEncryption
{
// temporary space for one block to avoid reallocating every time
private byte[] m_tmp;
/// <summary>
/// Block size in bytes for this cipher
/// </summary>
public abstract int BlockSize { get; }
/// <summary>
/// NetBlockEncryptionBase constructor
/// </summary>
public NetBlockEncryptionBase(NetPeer peer)
: base(peer)
{
m_tmp = new byte[BlockSize];
}
/// <summary>
/// Encrypt am outgoing message with this algorithm; no writing can be done to the message after encryption, or message will be corrupted
/// </summary>
public override bool Encrypt(NetOutgoingMessage msg)
{
int payloadBitLength = msg.LengthBits;
int numBytes = msg.LengthBytes;
int blockSize = BlockSize;
int numBlocks = (int)Math.Ceiling((double)numBytes / (double)blockSize);
int dstSize = numBlocks * blockSize;
msg.EnsureBufferSize(dstSize * 8 + (4 * 8)); // add 4 bytes for payload length at end
msg.LengthBits = dstSize * 8; // length will automatically adjust +4 bytes when payload length is written
for(int i=0;i<numBlocks;i++)
{
EncryptBlock(msg.m_data, (i * blockSize), m_tmp);
Buffer.BlockCopy(m_tmp, 0, msg.m_data, (i * blockSize), m_tmp.Length);
}
// add true payload length last
msg.Write((UInt32)payloadBitLength);
return true;
}
/// <summary>
/// Decrypt an incoming message encrypted with corresponding Encrypt
/// </summary>
/// <param name="msg">message to decrypt</param>
/// <returns>true if successful; false if failed</returns>
public override bool Decrypt(NetIncomingMessage msg)
{
int numEncryptedBytes = msg.LengthBytes - 4; // last 4 bytes is true bit length
int blockSize = BlockSize;
int numBlocks = numEncryptedBytes / blockSize;
if (numBlocks * blockSize != numEncryptedBytes)
return false;
for (int i = 0; i < numBlocks; i++)
{
DecryptBlock(msg.m_data, (i * blockSize), m_tmp);
Buffer.BlockCopy(m_tmp, 0, msg.m_data, (i * blockSize), m_tmp.Length);
}
// read 32 bits of true payload length
uint realSize = NetBitWriter.ReadUInt32(msg.m_data, 32, (numEncryptedBytes * 8));
msg.m_bitLength = (int)realSize;
return true;
}
/// <summary>
/// Encrypt a block of bytes
/// </summary>
protected abstract void EncryptBlock(byte[] source, int sourceOffset, byte[] destination);
/// <summary>
/// Decrypt a block of bytes
/// </summary>
protected abstract void DecryptBlock(byte[] source, int sourceOffset, byte[] destination);
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace Lidgren.Network
{
public abstract class NetCryptoProviderBase : NetEncryption
{
protected SymmetricAlgorithm m_algorithm;
public NetCryptoProviderBase(NetPeer peer, SymmetricAlgorithm algo)
: base(peer)
{
m_algorithm = algo;
m_algorithm.GenerateKey();
m_algorithm.GenerateIV();
}
public override void SetKey(byte[] data, int offset, int count)
{
int len = m_algorithm.Key.Length;
var key = new byte[len];
for (int i = 0; i < len; i++)
key[i] = data[offset + (i % count)];
m_algorithm.Key = key;
len = m_algorithm.IV.Length;
key = new byte[len];
for (int i = 0; i < len; i++)
key[len - 1 - i] = data[offset + (i % count)];
m_algorithm.IV = key;
}
public override bool Encrypt(NetOutgoingMessage msg)
{
int unEncLenBits = msg.LengthBits;
var ms = new MemoryStream();
var cs = new CryptoStream(ms, m_algorithm.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(msg.m_data, 0, msg.LengthBytes);
cs.Close();
// get results
var arr = ms.ToArray();
ms.Close();
msg.EnsureBufferSize((arr.Length + 4) * 8);
msg.LengthBits = 0; // reset write pointer
msg.Write((uint)unEncLenBits);
msg.Write(arr);
msg.LengthBits = (arr.Length + 4) * 8;
return true;
}
public override bool Decrypt(NetIncomingMessage msg)
{
int unEncLenBits = (int)msg.ReadUInt32();
var ms = new MemoryStream(msg.m_data, 4, msg.LengthBytes - 4);
var cs = new CryptoStream(ms, m_algorithm.CreateDecryptor(), CryptoStreamMode.Read);
var result = m_peer.GetStorage(unEncLenBits);
cs.Read(result, 0, NetUtility.BytesToHoldBits(unEncLenBits));
cs.Close();
// TODO: recycle existing msg
msg.m_data = result;
msg.m_bitLength = unEncLenBits;
msg.m_readPosition = 0;
return true;
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace Lidgren.Network
{
public class NetDESEncryption : NetCryptoProviderBase
{
public NetDESEncryption(NetPeer peer)
: base(peer, new DESCryptoServiceProvider())
{
}
public NetDESEncryption(NetPeer peer, string key)
: base(peer, new DESCryptoServiceProvider())
{
SetKey(key);
}
public NetDESEncryption(NetPeer peer, byte[] data, int offset, int count)
: base(peer, new DESCryptoServiceProvider())
{
SetKey(data, offset, count);
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace Lidgren.Network
{
/// <summary>
/// Interface for an encryption algorithm
/// </summary>
public abstract class NetEncryption
{
/// <summary>
/// NetPeer
/// </summary>
protected NetPeer m_peer;
/// <summary>
/// Constructor
/// </summary>
public NetEncryption(NetPeer peer)
{
if (peer == null)
throw new NetException("Peer must not be null");
m_peer = peer;
}
public void SetKey(string str)
{
var bytes = System.Text.Encoding.ASCII.GetBytes(str);
SetKey(bytes, 0, bytes.Length);
}
public abstract void SetKey(byte[] data, int offset, int count);
/// <summary>
/// Encrypt an outgoing message in place
/// </summary>
public abstract bool Encrypt(NetOutgoingMessage msg);
/// <summary>
/// Decrypt an incoming message in place
/// </summary>
public abstract bool Decrypt(NetIncomingMessage msg);
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace Lidgren.Network
{
public class NetRC2Encryption : NetCryptoProviderBase
{
public NetRC2Encryption(NetPeer peer)
: base(peer, new RC2CryptoServiceProvider())
{
}
public NetRC2Encryption(NetPeer peer, string key)
: base(peer, new RC2CryptoServiceProvider())
{
SetKey(key);
}
public NetRC2Encryption(NetPeer peer, byte[] data, int offset, int count)
: base(peer, new RC2CryptoServiceProvider())
{
SetKey(data, offset, count);
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace Lidgren.Network
{
public class NetTripleDESEncryption : NetCryptoProviderBase
{
public NetTripleDESEncryption(NetPeer peer)
: base(peer, new TripleDESCryptoServiceProvider())
{
}
public NetTripleDESEncryption(NetPeer peer, string key)
: base(peer, new TripleDESCryptoServiceProvider())
{
SetKey(key);
}
public NetTripleDESEncryption(NetPeer peer, byte[] data, int offset, int count)
: base(peer, new TripleDESCryptoServiceProvider())
{
SetKey(data, offset, count);
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
/// <summary>
/// Example class; not very good encryption
/// </summary>
public class NetXorEncryption : NetEncryption
{
private byte[] m_key;
/// <summary>
/// NetXorEncryption constructor
/// </summary>
public NetXorEncryption(NetPeer peer, byte[] key)
: base(peer)
{
m_key = key;
}
public override void SetKey(byte[] data, int offset, int count)
{
m_key = new byte[count];
Array.Copy(data, offset, m_key, 0, count);
}
/// <summary>
/// NetXorEncryption constructor
/// </summary>
public NetXorEncryption(NetPeer peer, string key)
: base(peer)
{
m_key = Encoding.UTF8.GetBytes(key);
}
/// <summary>
/// Encrypt an outgoing message
/// </summary>
public override bool Encrypt(NetOutgoingMessage msg)
{
int numBytes = msg.LengthBytes;
for (int i = 0; i < numBytes; i++)
{
int offset = i % m_key.Length;
msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]);
}
return true;
}
/// <summary>
/// Decrypt an incoming message
/// </summary>
public override bool Decrypt(NetIncomingMessage msg)
{
int numBytes = msg.LengthBytes;
for (int i = 0; i < numBytes; i++)
{
int offset = i % m_key.Length;
msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]);
}
return true;
}
}
}

View File

@ -0,0 +1,154 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Security.Cryptography;
using System.Text;
using System.Security;
namespace Lidgren.Network
{
/// <summary>
/// Methods to encrypt and decrypt data using the XTEA algorithm
/// </summary>
public sealed class NetXtea : NetBlockEncryptionBase
{
private const int c_blockSize = 8;
private const int c_keySize = 16;
private const int c_delta = unchecked((int)0x9E3779B9);
private readonly int m_numRounds;
private readonly uint[] m_sum0;
private readonly uint[] m_sum1;
/// <summary>
/// Gets the block size for this cipher
/// </summary>
public override int BlockSize { get { return c_blockSize; } }
/// <summary>
/// 16 byte key
/// </summary>
public NetXtea(NetPeer peer, byte[] key, int rounds)
: base(peer)
{
if (key.Length < c_keySize)
throw new NetException("Key too short!");
m_numRounds = rounds;
m_sum0 = new uint[m_numRounds];
m_sum1 = new uint[m_numRounds];
uint[] tmp = new uint[8];
int num2;
int index = num2 = 0;
while (index < 4)
{
tmp[index] = BitConverter.ToUInt32(key, num2);
index++;
num2 += 4;
}
for (index = num2 = 0; index < 32; index++)
{
m_sum0[index] = ((uint)num2) + tmp[num2 & 3];
num2 += -1640531527;
m_sum1[index] = ((uint)num2) + tmp[(num2 >> 11) & 3];
}
}
/// <summary>
/// 16 byte key
/// </summary>
public NetXtea(NetPeer peer, byte[] key)
: this(peer, key, 32)
{
}
/// <summary>
/// String to hash for key
/// </summary>
public NetXtea(NetPeer peer, string key)
: this(peer, NetUtility.CreateSHA1Hash(key), 32)
{
}
public override void SetKey(byte[] data, int offset, int length)
{
var key = NetUtility.CreateSHA1Hash(data, offset, length);
NetException.Assert(key.Length == 16);
SetKey(key, 0, 16);
}
/// <summary>
/// Encrypts a block of bytes
/// </summary>
protected override void EncryptBlock(byte[] source, int sourceOffset, byte[] destination)
{
uint v0 = BytesToUInt(source, sourceOffset);
uint v1 = BytesToUInt(source, sourceOffset + 4);
for (int i = 0; i != m_numRounds; i++)
{
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i];
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i];
}
UIntToBytes(v0, destination, 0);
UIntToBytes(v1, destination, 0 + 4);
return;
}
/// <summary>
/// Decrypts a block of bytes
/// </summary>
protected override void DecryptBlock(byte[] source, int sourceOffset, byte[] destination)
{
// Pack bytes into integers
uint v0 = BytesToUInt(source, sourceOffset);
uint v1 = BytesToUInt(source, sourceOffset + 4);
for (int i = m_numRounds - 1; i >= 0; i--)
{
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i];
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i];
}
UIntToBytes(v0, destination, 0);
UIntToBytes(v1, destination, 0 + 4);
return;
}
private static uint BytesToUInt(byte[] bytes, int offset)
{
uint retval = (uint)(bytes[offset] << 24);
retval |= (uint)(bytes[++offset] << 16);
retval |= (uint)(bytes[++offset] << 8);
return (retval | bytes[++offset]);
}
private static void UIntToBytes(uint value, byte[] destination, int destinationOffset)
{
destination[destinationOffset++] = (byte)(value >> 24);
destination[destinationOffset++] = (byte)(value >> 16);
destination[destinationOffset++] = (byte)(value >> 8);
destination[destinationOffset++] = (byte)value;
}
}
}

View File

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{49BA1C69-6104-41AC-A5D8-B54FA9F696E8}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Lidgren.Network</RootNamespace>
<AssemblyName>Lidgren.Network</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\Debug\Lidgren.Network.XML</DocumentationFile>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Encryption\NetAESEncryption.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Encryption\NetCryptoProviderBase.cs" />
<Compile Include="Encryption\NetDESEncryption.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Encryption\NetEncryption.cs" />
<Compile Include="Encryption\NetBlockEncryptionBase.cs" />
<Compile Include="Encryption\NetRC2Encryption.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Encryption\NetTripleDESEncryption.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Encryption\NetXorEncryption.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Encryption\NetXteaEncryption.cs" />
<Compile Include="NamespaceDoc.cs" />
<Compile Include="NetBigInteger.cs" />
<Compile Include="NetBitVector.cs" />
<Compile Include="NetBitWriter.cs" />
<Compile Include="NetBuffer.cs" />
<Compile Include="NetBuffer.Peek.cs" />
<Compile Include="NetBuffer.Read.cs" />
<Compile Include="NetBuffer.Read.Reflection.cs" />
<Compile Include="NetBuffer.Write.cs" />
<Compile Include="NetBuffer.Write.Reflection.cs" />
<Compile Include="NetClient.cs" />
<Compile Include="NetConnection.cs" />
<Compile Include="NetConnection.Handshake.cs" />
<Compile Include="NetConnection.Latency.cs" />
<Compile Include="NetConnection.MTU.cs" />
<Compile Include="NetConnectionStatistics.cs" />
<Compile Include="NetConnectionStatus.cs" />
<Compile Include="NetConstants.cs" />
<Compile Include="NetDeliveryMethod.cs" />
<Compile Include="NetException.cs" />
<Compile Include="NetFragmentationHelper.cs" />
<Compile Include="NetIncomingMessage.cs" />
<Compile Include="NetIncomingMessageType.cs" />
<Compile Include="NetMessageType.cs" />
<Compile Include="NetNatIntroduction.cs" />
<Compile Include="NetOutgoingMessage.cs" />
<Compile Include="NetPeer.cs" />
<Compile Include="NetPeer.Discovery.cs" />
<Compile Include="NetPeer.Fragmentation.cs" />
<Compile Include="NetPeer.Internal.cs" />
<Compile Include="NetPeer.LatencySimulation.cs" />
<Compile Include="NetPeer.Logging.cs" />
<Compile Include="NetPeer.MessagePools.cs" />
<Compile Include="NetPeer.Send.cs" />
<Compile Include="NetPeerConfiguration.cs" />
<Compile Include="NetPeerStatistics.cs" />
<Compile Include="NetPeerStatus.cs" />
<Compile Include="NetQueue.cs" />
<Compile Include="NetRandom.cs" />
<Compile Include="NetRandom.Implementations.cs" />
<Compile Include="NetRandomSeed.cs" />
<Compile Include="NetReceiverChannelBase.cs" />
<Compile Include="NetReliableOrderedReceiver.cs" />
<Compile Include="NetReliableSenderChannel.cs" />
<Compile Include="NetReliableSequencedReceiver.cs" />
<Compile Include="NetReliableUnorderedReceiver.cs" />
<Compile Include="NetSenderChannelBase.cs" />
<Compile Include="NetSendResult.cs" />
<Compile Include="NetServer.cs" />
<Compile Include="NetSRP.cs" />
<Compile Include="NetStoredReliableMessage.cs" />
<Compile Include="NetTime.cs" />
<Compile Include="NetTuple.cs" />
<Compile Include="NetUnreliableSenderChannel.cs" />
<Compile Include="NetUnreliableSequencedReceiver.cs" />
<Compile Include="NetUnreliableUnorderedReceiver.cs" />
<Compile Include="NetUPnP.cs" />
<Compile Include="NetUtility.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
<Visible>False</Visible>
<ProductName>Windows Installer 3.1</ProductName>
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
/// <summary>
/// Lidgren Network Library
/// </summary>
internal class NamespaceDoc
{
// <include file='_Namespace.xml' path='Documentation/*' />
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,172 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Text;
namespace Lidgren.Network
{
/// <summary>
/// Fixed size vector of booleans
/// </summary>
public sealed class NetBitVector
{
private readonly int m_capacity;
private readonly int[] m_data;
private int m_numBitsSet;
/// <summary>
/// Gets the number of bits/booleans stored in this vector
/// </summary>
public int Capacity { get { return m_capacity; } }
/// <summary>
/// NetBitVector constructor
/// </summary>
public NetBitVector(int bitsCapacity)
{
m_capacity = bitsCapacity;
m_data = new int[(bitsCapacity + 31) / 32];
}
/// <summary>
/// Returns true if all bits/booleans are set to zero/false
/// </summary>
public bool IsEmpty()
{
return (m_numBitsSet == 0);
}
/// <summary>
/// Returns the number of bits/booleans set to one/true
/// </summary>
/// <returns></returns>
public int Count()
{
return m_numBitsSet;
}
/// <summary>
/// Shift all bits one step down, cycling the first bit to the top
/// </summary>
public void RotateDown()
{
int lenMinusOne = m_data.Length - 1;
int firstBit = m_data[0] & 1;
for (int i = 0; i < lenMinusOne; i++)
m_data[i] = ((m_data[i] >> 1) & ~(1 << 31)) | m_data[i + 1] << 31;
int lastIndex = m_capacity - 1 - (32 * lenMinusOne);
// special handling of last int
int cur = m_data[lenMinusOne];
cur = cur >> 1;
cur |= firstBit << lastIndex;
m_data[lenMinusOne] = cur;
}
/// <summary>
/// Gets the first (lowest) index set to true
/// </summary>
public int GetFirstSetIndex()
{
int idx = 0;
int data = m_data[0];
while (data == 0)
{
idx++;
data = m_data[idx];
}
int a = 0;
while (((data >> a) & 1) == 0)
a++;
return (idx * 32) + a;
}
/// <summary>
/// Gets the bit/bool at the specified index
/// </summary>
public bool Get(int bitIndex)
{
NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity);
return (m_data[bitIndex / 32] & (1 << (bitIndex % 32))) != 0;
}
/// <summary>
/// Sets or clears the bit/bool at the specified index
/// </summary>
public void Set(int bitIndex, bool value)
{
NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity);
int idx = bitIndex / 32;
if (value)
{
if ((m_data[idx] & (1 << (bitIndex % 32))) == 0)
m_numBitsSet++;
m_data[idx] |= (1 << (bitIndex % 32));
}
else
{
if ((m_data[idx] & (1 << (bitIndex % 32))) != 0)
m_numBitsSet--;
m_data[idx] &= (~(1 << (bitIndex % 32)));
}
}
/// <summary>
/// Gets the bit/bool at the specified index
/// </summary>
[System.Runtime.CompilerServices.IndexerName("Bit")]
public bool this[int index]
{
get { return Get(index); }
set { Set(index, value); }
}
/// <summary>
/// Sets all bits/booleans to zero/false
/// </summary>
public void Clear()
{
Array.Clear(m_data, 0, m_data.Length);
m_numBitsSet = 0;
NetException.Assert(this.IsEmpty());
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
StringBuilder bdr = new StringBuilder(m_capacity + 2);
bdr.Append('[');
for (int i = 0; i < m_capacity; i++)
bdr.Append(Get(m_capacity - i - 1) ? '1' : '0');
bdr.Append(']');
return bdr.ToString();
}
}
}

View File

@ -0,0 +1,513 @@
//#define UNSAFE
//#define BIGENDIAN
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Helper class for NetBuffer to write/read bits
/// </summary>
public static class NetBitWriter
{
/// <summary>
/// Read 1-8 bits from a buffer into a byte
/// </summary>
public static byte ReadByte(byte[] fromBuffer, int numberOfBits, int readBitOffset)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits < 9)), "Read() can only read between 1 and 8 bits");
int bytePtr = readBitOffset >> 3;
int startReadAtIndex = readBitOffset - (bytePtr * 8); // (readBitOffset % 8);
if (startReadAtIndex == 0 && numberOfBits == 8)
return fromBuffer[bytePtr];
// mask away unused bits lower than (right of) relevant bits in first byte
byte returnValue = (byte)(fromBuffer[bytePtr] >> startReadAtIndex);
int numberOfBitsInSecondByte = numberOfBits - (8 - startReadAtIndex);
if (numberOfBitsInSecondByte < 1)
{
// we don't need to read from the second byte, but we DO need
// to mask away unused bits higher than (left of) relevant bits
return (byte)(returnValue & (255 >> (8 - numberOfBits)));
}
byte second = fromBuffer[bytePtr + 1];
// mask away unused bits higher than (left of) relevant bits in second byte
second &= (byte)(255 >> (8 - numberOfBitsInSecondByte));
return (byte)(returnValue | (byte)(second << (numberOfBits - numberOfBitsInSecondByte)));
}
/// <summary>
/// Read several bytes from a buffer
/// </summary>
public static void ReadBytes(byte[] fromBuffer, int numberOfBytes, int readBitOffset, byte[] destination, int destinationByteOffset)
{
int readPtr = readBitOffset >> 3;
int startReadAtIndex = readBitOffset - (readPtr * 8); // (readBitOffset % 8);
if (startReadAtIndex == 0)
{
Buffer.BlockCopy(fromBuffer, readPtr, destination, destinationByteOffset, numberOfBytes);
return;
}
int secondPartLen = 8 - startReadAtIndex;
int secondMask = 255 >> secondPartLen;
for (int i = 0; i < numberOfBytes; i++)
{
// mask away unused bits lower than (right of) relevant bits in byte
int b = fromBuffer[readPtr] >> startReadAtIndex;
readPtr++;
// mask away unused bits higher than (left of) relevant bits in second byte
int second = fromBuffer[readPtr] & secondMask;
destination[destinationByteOffset++] = (byte)(b | (second << secondPartLen));
}
return;
}
/// <summary>
/// Write 0-8 bits of data to buffer
/// </summary>
public static void WriteByte(byte source, int numberOfBits, byte[] destination, int destBitOffset)
{
if (numberOfBits == 0)
return;
NetException.Assert(((numberOfBits >= 0) && (numberOfBits <= 8)), "Must write between 0 and 8 bits!");
// Mask out all the bits we dont want
source = (byte)(source & (0xFF >> (8 - numberOfBits)));
int p = destBitOffset >> 3;
int bitsUsed = destBitOffset & 0x7; // mod 8
int bitsFree = 8 - bitsUsed;
int bitsLeft = bitsFree - numberOfBits;
// Fast path, everything fits in the first byte
if (bitsLeft >= 0)
{
int mask = (0xFF >> bitsFree) | (0xFF << (8 - bitsLeft));
destination[p] = (byte)(
// Mask out lower and upper bits
(destination[p] & mask) |
// Insert new bits
(source << bitsUsed)
);
return;
}
destination[p] = (byte)(
// Mask out upper bits
(destination[p] & (0xFF >> bitsFree)) |
// Write the lower bits to the upper bits in the first byte
(source << bitsUsed)
);
p += 1;
destination[p] = (byte)(
// Mask out lower bits
(destination[p] & (0xFF << (numberOfBits - bitsFree))) |
// Write the upper bits to the lower bits of the second byte
(source >> bitsFree)
);
}
/// <summary>
/// Write several whole bytes
/// </summary>
public static void WriteBytes(byte[] source, int sourceByteOffset, int numberOfBytes, byte[] destination, int destBitOffset)
{
int dstBytePtr = destBitOffset >> 3;
int firstPartLen = (destBitOffset % 8);
if (firstPartLen == 0)
{
Buffer.BlockCopy(source, sourceByteOffset, destination, dstBytePtr, numberOfBytes);
return;
}
int lastPartLen = 8 - firstPartLen;
for (int i = 0; i < numberOfBytes; i++)
{
byte src = source[sourceByteOffset + i];
// write last part of this byte
destination[dstBytePtr] &= (byte)(255 >> lastPartLen); // clear before writing
destination[dstBytePtr] |= (byte)(src << firstPartLen); // write first half
dstBytePtr++;
// write first part of next byte
destination[dstBytePtr] &= (byte)(255 << firstPartLen); // clear before writing
destination[dstBytePtr] |= (byte)(src >> lastPartLen); // write second half
}
return;
}
/// <summary>
/// Reads an unsigned 16 bit integer
/// </summary>
[CLSCompliant(false)]
#if UNSAFE
public static unsafe ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset)
{
Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits");
if (numberOfBits == 16 && ((readBitOffset % 8) == 0))
{
fixed (byte* ptr = &(fromBuffer[readBitOffset / 8]))
{
return *(((ushort*)ptr));
}
}
#else
public static ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset)
{
Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits");
#endif
ushort returnValue;
if (numberOfBits <= 8)
{
returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset);
return returnValue;
}
returnValue = ReadByte(fromBuffer, 8, readBitOffset);
numberOfBits -= 8;
readBitOffset += 8;
if (numberOfBits <= 8)
{
returnValue |= (ushort)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8);
}
#if BIGENDIAN
// reorder bytes
uint retVal = returnValue;
retVal = ((retVal & 0x0000ff00) >> 8) | ((retVal & 0x000000ff) << 8);
return (ushort)retVal;
#else
return returnValue;
#endif
}
/// <summary>
/// Reads the specified number of bits into an UInt32
/// </summary>
[CLSCompliant(false)]
#if UNSAFE
public static unsafe uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits");
if (numberOfBits == 32 && ((readBitOffset % 8) == 0))
{
fixed (byte* ptr = &(fromBuffer[readBitOffset / 8]))
{
return *(((uint*)ptr));
}
}
#else
public static uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits");
#endif
uint returnValue;
if (numberOfBits <= 8)
{
returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset);
return returnValue;
}
returnValue = ReadByte(fromBuffer, 8, readBitOffset);
numberOfBits -= 8;
readBitOffset += 8;
if (numberOfBits <= 8)
{
returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8);
return returnValue;
}
returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 8);
numberOfBits -= 8;
readBitOffset += 8;
if (numberOfBits <= 8)
{
uint r = ReadByte(fromBuffer, numberOfBits, readBitOffset);
r <<= 16;
returnValue |= r;
return returnValue;
}
returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 16);
numberOfBits -= 8;
readBitOffset += 8;
returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 24);
#if BIGENDIAN
// reorder bytes
return
((returnValue & 0xff000000) >> 24) |
((returnValue & 0x00ff0000) >> 8) |
((returnValue & 0x0000ff00) << 8) |
((returnValue & 0x000000ff) << 24);
#else
return returnValue;
#endif
}
//[CLSCompliant(false)]
//public static ulong ReadUInt64(byte[] fromBuffer, int numberOfBits, int readBitOffset)
/// <summary>
/// Writes an unsigned 16 bit integer
/// </summary>
[CLSCompliant(false)]
public static void WriteUInt16(ushort source, int numberOfBits, byte[] destination, int destinationBitOffset)
{
if (numberOfBits == 0)
return;
NetException.Assert((numberOfBits >= 0 && numberOfBits <= 16), "numberOfBits must be between 0 and 16");
#if BIGENDIAN
// reorder bytes
uint intSource = source;
intSource = ((intSource & 0x0000ff00) >> 8) | ((intSource & 0x000000ff) << 8);
source = (ushort)intSource;
#endif
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset);
return;
}
NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset);
numberOfBits -= 8;
if (numberOfBits > 0)
NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset + 8);
}
/// <summary>
/// Writes the specified number of bits into a byte array
/// </summary>
[CLSCompliant(false)]
public static int WriteUInt32(uint source, int numberOfBits, byte[] destination, int destinationBitOffset)
{
#if BIGENDIAN
// reorder bytes
source = ((source & 0xff000000) >> 24) |
((source & 0x00ff0000) >> 8) |
((source & 0x0000ff00) << 8) |
((source & 0x000000ff) << 24);
#endif
int returnValue = destinationBitOffset + numberOfBits;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
/// <summary>
/// Writes the specified number of bits into a byte array
/// </summary>
[CLSCompliant(false)]
public static int WriteUInt64(ulong source, int numberOfBits, byte[] destination, int destinationBitOffset)
{
#if BIGENDIAN
source = ((source & 0xff00000000000000L) >> 56) |
((source & 0x00ff000000000000L) >> 40) |
((source & 0x0000ff0000000000L) >> 24) |
((source & 0x000000ff00000000L) >> 8) |
((source & 0x00000000ff000000L) << 8) |
((source & 0x0000000000ff0000L) << 24) |
((source & 0x000000000000ff00L) << 40) |
((source & 0x00000000000000ffL) << 56);
#endif
int returnValue = destinationBitOffset + numberOfBits;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 24), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 32), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 32), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 40), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 40), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 48), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 48), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 56), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 56), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
return returnValue;
}
//
// Variable size
//
/// <summary>
/// Write Base128 encoded variable sized unsigned integer
/// </summary>
/// <returns>number of bytes written</returns>
[CLSCompliant(false)]
public static int WriteVariableUInt32(byte[] intoBuffer, int offset, uint value)
{
int retval = 0;
uint num1 = (uint)value;
while (num1 >= 0x80)
{
intoBuffer[offset + retval] = (byte)(num1 | 0x80);
num1 = num1 >> 7;
retval++;
}
intoBuffer[offset + retval] = (byte)num1;
return retval + 1;
}
/// <summary>
/// Reads a UInt32 written using WriteUnsignedVarInt(); will increment offset!
/// </summary>
[CLSCompliant(false)]
public static uint ReadVariableUInt32(byte[] buffer, ref int offset)
{
int num1 = 0;
int num2 = 0;
while (true)
{
NetException.Assert(num2 != 0x23, "Bad 7-bit encoded integer");
byte num3 = buffer[offset++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
return (uint)num1;
}
}
}
}

View File

@ -0,0 +1,312 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Diagnostics;
using System.Net;
namespace Lidgren.Network
{
public partial class NetBuffer
{
/// <summary>
/// Gets the internal data buffer
/// </summary>
public byte[] PeekDataBuffer() { return m_data; }
//
// 1 bit
//
/// <summary>
/// Reads a 1-bit Boolean without advancing the read pointer
/// </summary>
public bool PeekBoolean()
{
NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition);
return (retval > 0 ? true : false);
}
//
// 8 bit
//
/// <summary>
/// Reads a Byte without advancing the read pointer
/// </summary>
public byte PeekByte()
{
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
return retval;
}
/// <summary>
/// Reads an SByte without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public sbyte PeekSByte()
{
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
return (sbyte)retval;
}
/// <summary>
/// Reads the specified number of bits into a Byte without advancing the read pointer
/// </summary>
public byte PeekByte(int numberOfBits)
{
byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition);
return retval;
}
/// <summary>
/// Reads the specified number of bytes without advancing the read pointer
/// </summary>
public byte[] PeekBytes(int numberOfBytes)
{
NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError);
byte[] retval = new byte[numberOfBytes];
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0);
return retval;
}
/// <summary>
/// Reads the specified number of bytes without advancing the read pointer
/// </summary>
public void PeekBytes(byte[] into, int offset, int numberOfBytes)
{
NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError);
NetException.Assert(offset + numberOfBytes <= into.Length);
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset);
return;
}
//
// 16 bit
//
/// <summary>
/// Reads an Int16 without advancing the read pointer
/// </summary>
public Int16 PeekInt16()
{
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition);
return (short)retval;
}
/// <summary>
/// Reads a UInt16 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt16 PeekUInt16()
{
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition);
return (ushort)retval;
}
//
// 32 bit
//
/// <summary>
/// Reads an Int32 without advancing the read pointer
/// </summary>
public Int32 PeekInt32()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
return (Int32)retval;
}
/// <summary>
/// Reads the specified number of bits into an Int32 without advancing the read pointer
/// </summary>
public Int32 PeekInt32(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadInt() can only read between 1 and 32 bits");
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
if (numberOfBits == 32)
return (int)retval;
int signBit = 1 << (numberOfBits - 1);
if ((retval & signBit) == 0)
return (int)retval; // positive
// negative
unchecked
{
uint mask = ((uint)-1) >> (33 - numberOfBits);
uint tmp = (retval & mask) + 1;
return -((int)tmp);
}
}
/// <summary>
/// Reads a UInt32 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt32 PeekUInt32()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
return retval;
}
/// <summary>
/// Reads the specified number of bits into a UInt32 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt32 PeekUInt32(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadUInt() can only read between 1 and 32 bits");
//NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size");
UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
return retval;
}
//
// 64 bit
//
/// <summary>
/// Reads a UInt64 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt64 PeekUInt64()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition + 32);
ulong retval = low + (high << 32);
return retval;
}
/// <summary>
/// Reads an Int64 without advancing the read pointer
/// </summary>
public Int64 PeekInt64()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
unchecked
{
ulong retval = PeekUInt64();
long longRetval = (long)retval;
return longRetval;
}
}
/// <summary>
/// Reads the specified number of bits into an UInt64 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt64 PeekUInt64(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 64), "ReadUInt() can only read between 1 and 64 bits");
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
ulong retval;
if (numberOfBits <= 32)
{
retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
}
else
{
retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
retval |= NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition) << 32;
}
return retval;
}
/// <summary>
/// Reads the specified number of bits into an Int64 without advancing the read pointer
/// </summary>
public Int64 PeekInt64(int numberOfBits)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits < 65)), "ReadInt64(bits) can only read between 1 and 64 bits");
return (long)PeekUInt64(numberOfBits);
}
//
// Floating point
//
/// <summary>
/// Reads a 32-bit Single without advancing the read pointer
/// </summary>
public float PeekFloat()
{
return PeekSingle();
}
/// <summary>
/// Reads a 32-bit Single without advancing the read pointer
/// </summary>
public float PeekSingle()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
if ((m_readPosition & 7) == 0) // read directly
{
float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3);
return retval;
}
byte[] bytes = PeekBytes(4);
return BitConverter.ToSingle(bytes, 0);
}
/// <summary>
/// Reads a 64-bit Double without advancing the read pointer
/// </summary>
public double PeekDouble()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
if ((m_readPosition & 7) == 0) // read directly
{
// read directly
double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3);
return retval;
}
byte[] bytes = PeekBytes(8);
return BitConverter.ToDouble(bytes, 0);
}
/// <summary>
/// Reads a string without advancing the read pointer
/// </summary>
public string PeekString()
{
int wasReadPosition = m_readPosition;
string retval = ReadString();
m_readPosition = wasReadPosition;
return retval;
}
}
}

View File

@ -0,0 +1,107 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Reflection;
namespace Lidgren.Network
{
public partial class NetBuffer
{
/// <summary>
/// Reads all public and private declared instance fields of the object in alphabetical order using reflection
/// </summary>
public void ReadAllFields(object target)
{
ReadAllFields(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}
/// <summary>
/// Reads all fields with the specified binding of the object in alphabetical order using reflection
/// </summary>
public void ReadAllFields(object target, BindingFlags flags)
{
if (target == null)
throw new ArgumentNullException("target");
Type tp = target.GetType();
FieldInfo[] fields = tp.GetFields(flags);
NetUtility.SortMembersList(fields);
foreach (FieldInfo fi in fields)
{
object value;
// find read method
MethodInfo readMethod;
if (s_readMethods.TryGetValue(fi.FieldType, out readMethod))
{
// read value
value = readMethod.Invoke(this, null);
// set the value
fi.SetValue(target, value);
}
}
}
/// <summary>
/// Reads all public and private declared instance fields of the object in alphabetical order using reflection
/// </summary>
public void ReadAllProperties(object target)
{
ReadAllProperties(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}
/// <summary>
/// Reads all fields with the specified binding of the object in alphabetical order using reflection
/// </summary>
public void ReadAllProperties(object target, BindingFlags flags)
{
if (target == null)
throw new ArgumentNullException("target");
Type tp = target.GetType();
PropertyInfo[] fields = tp.GetProperties(flags);
NetUtility.SortMembersList(fields);
foreach (PropertyInfo fi in fields)
{
object value;
// find read method
MethodInfo readMethod;
if (s_readMethods.TryGetValue(fi.PropertyType, out readMethod))
{
// read value
value = readMethod.Invoke(this, null);
// set the value
#if UNITY_WEBPLAYER || UNITY_4_5
var setMethod = fi.GetSetMethod();
#else
var setMethod = fi.SetMethod;
#endif
if (setMethod != null)
setMethod.Invoke(target, new object[] { value });
}
}
}
}
}

View File

@ -0,0 +1,675 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Net;
namespace Lidgren.Network
{
/// <summary>
/// Base class for NetIncomingMessage and NetOutgoingMessage
/// </summary>
public partial class NetBuffer
{
private const string c_readOverflowError = "Trying to read past the buffer size - likely caused by mismatching Write/Reads, different size or order.";
/// <summary>
/// Reads a boolean value (stored as a single bit) written using Write(bool)
/// </summary>
public bool ReadBoolean()
{
NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition);
m_readPosition += 1;
return (retval > 0 ? true : false);
}
/// <summary>
/// Reads a byte
/// </summary>
public byte ReadByte()
{
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
m_readPosition += 8;
return retval;
}
/// <summary>
/// Reads a byte and returns true or false for success
/// </summary>
public bool ReadByte(out byte result)
{
if (m_bitLength - m_readPosition < 8)
{
result = 0;
return false;
}
result = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
m_readPosition += 8;
return true;
}
/// <summary>
/// Reads a signed byte
/// </summary>
[CLSCompliant(false)]
public sbyte ReadSByte()
{
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
m_readPosition += 8;
return (sbyte)retval;
}
/// <summary>
/// Reads 1 to 8 bits into a byte
/// </summary>
public byte ReadByte(int numberOfBits)
{
NetException.Assert(numberOfBits > 0 && numberOfBits <= 8, "ReadByte(bits) can only read between 1 and 8 bits");
byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition);
m_readPosition += numberOfBits;
return retval;
}
/// <summary>
/// Reads the specified number of bytes
/// </summary>
public byte[] ReadBytes(int numberOfBytes)
{
NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError);
byte[] retval = new byte[numberOfBytes];
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0);
m_readPosition += (8 * numberOfBytes);
return retval;
}
/// <summary>
/// Reads the specified number of bytes and returns true for success
/// </summary>
public bool ReadBytes(int numberOfBytes, out byte[] result)
{
if (m_bitLength - m_readPosition + 7 < (numberOfBytes * 8))
{
result = null;
return false;
}
result = new byte[numberOfBytes];
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, result, 0);
m_readPosition += (8 * numberOfBytes);
return true;
}
/// <summary>
/// Reads the specified number of bytes into a preallocated array
/// </summary>
/// <param name="into">The destination array</param>
/// <param name="offset">The offset where to start writing in the destination array</param>
/// <param name="numberOfBytes">The number of bytes to read</param>
public void ReadBytes(byte[] into, int offset, int numberOfBytes)
{
NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError);
NetException.Assert(offset + numberOfBytes <= into.Length);
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset);
m_readPosition += (8 * numberOfBytes);
return;
}
/// <summary>
/// Reads the specified number of bits into a preallocated array
/// </summary>
/// <param name="into">The destination array</param>
/// <param name="offset">The offset where to start writing in the destination array</param>
/// <param name="numberOfBits">The number of bits to read</param>
public void ReadBits(byte[] into, int offset, int numberOfBits)
{
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
NetException.Assert(offset + NetUtility.BytesToHoldBits(numberOfBits) <= into.Length);
int numberOfWholeBytes = numberOfBits / 8;
int extraBits = numberOfBits - (numberOfWholeBytes * 8);
NetBitWriter.ReadBytes(m_data, numberOfWholeBytes, m_readPosition, into, offset);
m_readPosition += (8 * numberOfWholeBytes);
if (extraBits > 0)
into[offset + numberOfWholeBytes] = ReadByte(extraBits);
return;
}
/// <summary>
/// Reads a 16 bit signed integer written using Write(Int16)
/// </summary>
public Int16 ReadInt16()
{
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition);
m_readPosition += 16;
return (short)retval;
}
/// <summary>
/// Reads a 16 bit unsigned integer written using Write(UInt16)
/// </summary>
[CLSCompliant(false)]
public UInt16 ReadUInt16()
{
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition);
m_readPosition += 16;
return (ushort)retval;
}
/// <summary>
/// Reads a 32 bit signed integer written using Write(Int32)
/// </summary>
public Int32 ReadInt32()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
m_readPosition += 32;
return (Int32)retval;
}
/// <summary>
/// Reads a 32 bit signed integer written using Write(Int32)
/// </summary>
[CLSCompliant(false)]
public bool ReadInt32(out Int32 result)
{
if (m_bitLength - m_readPosition < 32)
{
result = 0;
return false;
}
result = (Int32)NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
m_readPosition += 32;
return true;
}
/// <summary>
/// Reads a signed integer stored in 1 to 32 bits, written using Write(Int32, Int32)
/// </summary>
public Int32 ReadInt32(int numberOfBits)
{
NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadInt32(bits) can only read between 1 and 32 bits");
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
m_readPosition += numberOfBits;
if (numberOfBits == 32)
return (int)retval;
int signBit = 1 << (numberOfBits - 1);
if ((retval & signBit) == 0)
return (int)retval; // positive
// negative
unchecked
{
uint mask = ((uint)-1) >> (33 - numberOfBits);
uint tmp = (retval & mask) + 1;
return -((int)tmp);
}
}
/// <summary>
/// Reads an 32 bit unsigned integer written using Write(UInt32)
/// </summary>
[CLSCompliant(false)]
public UInt32 ReadUInt32()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
m_readPosition += 32;
return retval;
}
/// <summary>
/// Reads an 32 bit unsigned integer written using Write(UInt32) and returns true for success
/// </summary>
[CLSCompliant(false)]
public bool ReadUInt32(out UInt32 result)
{
if (m_bitLength - m_readPosition < 32)
{
result = 0;
return false;
}
result = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
m_readPosition += 32;
return true;
}
/// <summary>
/// Reads an unsigned integer stored in 1 to 32 bits, written using Write(UInt32, Int32)
/// </summary>
[CLSCompliant(false)]
public UInt32 ReadUInt32(int numberOfBits)
{
NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadUInt32(bits) can only read between 1 and 32 bits");
//NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size");
UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
m_readPosition += numberOfBits;
return retval;
}
/// <summary>
/// Reads a 64 bit unsigned integer written using Write(UInt64)
/// </summary>
[CLSCompliant(false)]
public UInt64 ReadUInt64()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
m_readPosition += 32;
ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
ulong retval = low + (high << 32);
m_readPosition += 32;
return retval;
}
/// <summary>
/// Reads a 64 bit signed integer written using Write(Int64)
/// </summary>
public Int64 ReadInt64()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
unchecked
{
ulong retval = ReadUInt64();
long longRetval = (long)retval;
return longRetval;
}
}
/// <summary>
/// Reads an unsigned integer stored in 1 to 64 bits, written using Write(UInt64, Int32)
/// </summary>
[CLSCompliant(false)]
public UInt64 ReadUInt64(int numberOfBits)
{
NetException.Assert(numberOfBits > 0 && numberOfBits <= 64, "ReadUInt64(bits) can only read between 1 and 64 bits");
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
ulong retval;
if (numberOfBits <= 32)
{
retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
}
else
{
retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
retval |= NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition) << 32;
}
m_readPosition += numberOfBits;
return retval;
}
/// <summary>
/// Reads a signed integer stored in 1 to 64 bits, written using Write(Int64, Int32)
/// </summary>
public Int64 ReadInt64(int numberOfBits)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 64)), "ReadInt64(bits) can only read between 1 and 64 bits");
return (long)ReadUInt64(numberOfBits);
}
/// <summary>
/// Reads a 32 bit floating point value written using Write(Single)
/// </summary>
public float ReadFloat()
{
return ReadSingle();
}
/// <summary>
/// Reads a 32 bit floating point value written using Write(Single)
/// </summary>
public float ReadSingle()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
if ((m_readPosition & 7) == 0) // read directly
{
float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3);
m_readPosition += 32;
return retval;
}
byte[] bytes = ReadBytes(4);
return BitConverter.ToSingle(bytes, 0);
}
/// <summary>
/// Reads a 32 bit floating point value written using Write(Single)
/// </summary>
public bool ReadSingle(out float result)
{
if (m_bitLength - m_readPosition < 32)
{
result = 0.0f;
return false;
}
if ((m_readPosition & 7) == 0) // read directly
{
result = BitConverter.ToSingle(m_data, m_readPosition >> 3);
m_readPosition += 32;
return true;
}
byte[] bytes = ReadBytes(4);
result = BitConverter.ToSingle(bytes, 0);
return true;
}
/// <summary>
/// Reads a 64 bit floating point value written using Write(Double)
/// </summary>
public double ReadDouble()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
if ((m_readPosition & 7) == 0) // read directly
{
// read directly
double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3);
m_readPosition += 64;
return retval;
}
byte[] bytes = ReadBytes(8);
return BitConverter.ToDouble(bytes, 0);
}
//
// Variable bit count
//
/// <summary>
/// Reads a variable sized UInt32 written using WriteVariableUInt32()
/// </summary>
[CLSCompliant(false)]
public uint ReadVariableUInt32()
{
int num1 = 0;
int num2 = 0;
while (m_bitLength - m_readPosition >= 8)
{
byte num3 = this.ReadByte();
num1 |= (num3 & 0x7f) << num2;
num2 += 7;
if ((num3 & 0x80) == 0)
return (uint)num1;
}
// ouch; failed to find enough bytes; malformed variable length number?
return (uint)num1;
}
/// <summary>
/// Reads a variable sized UInt32 written using WriteVariableUInt32() and returns true for success
/// </summary>
[CLSCompliant(false)]
public bool ReadVariableUInt32(out uint result)
{
int num1 = 0;
int num2 = 0;
while (m_bitLength - m_readPosition >= 8)
{
byte num3;
if (ReadByte(out num3) == false)
{
result = 0;
return false;
}
num1 |= (num3 & 0x7f) << num2;
num2 += 7;
if ((num3 & 0x80) == 0)
{
result = (uint)num1;
return true;
}
}
result = (uint)num1;
return false;
}
/// <summary>
/// Reads a variable sized Int32 written using WriteVariableInt32()
/// </summary>
public int ReadVariableInt32()
{
uint n = ReadVariableUInt32();
return (int)(n >> 1) ^ -(int)(n & 1); // decode zigzag
}
/// <summary>
/// Reads a variable sized Int64 written using WriteVariableInt64()
/// </summary>
public Int64 ReadVariableInt64()
{
UInt64 n = ReadVariableUInt64();
return (Int64)(n >> 1) ^ -(long)(n & 1); // decode zigzag
}
/// <summary>
/// Reads a variable sized UInt32 written using WriteVariableInt64()
/// </summary>
[CLSCompliant(false)]
public UInt64 ReadVariableUInt64()
{
UInt64 num1 = 0;
int num2 = 0;
while (m_bitLength - m_readPosition >= 8)
{
//if (num2 == 0x23)
// throw new FormatException("Bad 7-bit encoded integer");
byte num3 = this.ReadByte();
num1 |= ((UInt64)num3 & 0x7f) << num2;
num2 += 7;
if ((num3 & 0x80) == 0)
return num1;
}
// ouch; failed to find enough bytes; malformed variable length number?
return num1;
}
/// <summary>
/// Reads a 32 bit floating point value written using WriteSignedSingle()
/// </summary>
/// <param name="numberOfBits">The number of bits used when writing the value</param>
/// <returns>A floating point value larger or equal to -1 and smaller or equal to 1</returns>
public float ReadSignedSingle(int numberOfBits)
{
uint encodedVal = ReadUInt32(numberOfBits);
int maxVal = (1 << numberOfBits) - 1;
return ((float)(encodedVal + 1) / (float)(maxVal + 1) - 0.5f) * 2.0f;
}
/// <summary>
/// Reads a 32 bit floating point value written using WriteUnitSingle()
/// </summary>
/// <param name="numberOfBits">The number of bits used when writing the value</param>
/// <returns>A floating point value larger or equal to 0 and smaller or equal to 1</returns>
public float ReadUnitSingle(int numberOfBits)
{
uint encodedVal = ReadUInt32(numberOfBits);
int maxVal = (1 << numberOfBits) - 1;
return (float)(encodedVal + 1) / (float)(maxVal + 1);
}
/// <summary>
/// Reads a 32 bit floating point value written using WriteRangedSingle()
/// </summary>
/// <param name="min">The minimum value used when writing the value</param>
/// <param name="max">The maximum value used when writing the value</param>
/// <param name="numberOfBits">The number of bits used when writing the value</param>
/// <returns>A floating point value larger or equal to MIN and smaller or equal to MAX</returns>
public float ReadRangedSingle(float min, float max, int numberOfBits)
{
float range = max - min;
int maxVal = (1 << numberOfBits) - 1;
float encodedVal = (float)ReadUInt32(numberOfBits);
float unit = encodedVal / (float)maxVal;
return min + (unit * range);
}
/// <summary>
/// Reads a 32 bit integer value written using WriteRangedInteger()
/// </summary>
/// <param name="min">The minimum value used when writing the value</param>
/// <param name="max">The maximum value used when writing the value</param>
/// <returns>A signed integer value larger or equal to MIN and smaller or equal to MAX</returns>
public int ReadRangedInteger(int min, int max)
{
uint range = (uint)(max - min);
int numBits = NetUtility.BitsToHoldUInt(range);
uint rvalue = ReadUInt32(numBits);
return (int)(min + rvalue);
}
/// <summary>
/// Reads a string written using Write(string)
/// </summary>
public string ReadString()
{
int byteLen = (int)ReadVariableUInt32();
if (byteLen <= 0)
return String.Empty;
if ((ulong)(m_bitLength - m_readPosition) < ((ulong)byteLen * 8))
{
// not enough data
#if DEBUG
throw new NetException(c_readOverflowError);
#else
m_readPosition = m_bitLength;
return null; // unfortunate; but we need to protect against DDOS
#endif
}
if ((m_readPosition & 7) == 0)
{
// read directly
string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen);
m_readPosition += (8 * byteLen);
return retval;
}
byte[] bytes = ReadBytes(byteLen);
return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
/// <summary>
/// Reads a string written using Write(string) and returns true for success
/// </summary>
public bool ReadString(out string result)
{
uint byteLen;
if (ReadVariableUInt32(out byteLen) == false)
{
result = String.Empty;
return false;
}
if (byteLen <= 0)
{
result = String.Empty;
return true;
}
if (m_bitLength - m_readPosition < (byteLen * 8))
{
result = String.Empty;
return false;
}
if ((m_readPosition & 7) == 0)
{
// read directly
result = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, (int)byteLen);
m_readPosition += (8 * (int)byteLen);
return true;
}
byte[] bytes;
if (ReadBytes((int)byteLen, out bytes) == false)
{
result = String.Empty;
return false;
}
result = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
return true;
}
/// <summary>
/// Reads a value, in local time comparable to NetTime.Now, written using WriteTime() for the connection supplied
/// </summary>
public double ReadTime(NetConnection connection, bool highPrecision)
{
double remoteTime = (highPrecision ? ReadDouble() : (double)ReadSingle());
if (connection == null)
throw new NetException("Cannot call ReadTime() on message without a connected sender (ie. unconnected messages)");
// lets bypass NetConnection.GetLocalTime for speed
return remoteTime - connection.m_remoteTimeOffset;
}
/// <summary>
/// Reads a stored IPv4 endpoint description
/// </summary>
public IPEndPoint ReadIPEndPoint()
{
byte len = ReadByte();
byte[] addressBytes = ReadBytes(len);
int port = (int)ReadUInt16();
IPAddress address = new IPAddress(addressBytes);
return new IPEndPoint(address, port);
}
/// <summary>
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
/// </summary>
public void SkipPadBits()
{
m_readPosition = ((m_readPosition + 7) >> 3) * 8;
}
/// <summary>
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
/// </summary>
public void ReadPadBits()
{
m_readPosition = ((m_readPosition + 7) >> 3) * 8;
}
/// <summary>
/// Pads data with the specified number of bits.
/// </summary>
public void SkipPadBits(int numberOfBits)
{
m_readPosition += numberOfBits;
}
}
}

View File

@ -0,0 +1,98 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Reflection;
namespace Lidgren.Network
{
public partial class NetBuffer
{
/// <summary>
/// Writes all public and private declared instance fields of the object in alphabetical order using reflection
/// </summary>
public void WriteAllFields(object ob)
{
WriteAllFields(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}
/// <summary>
/// Writes all fields with specified binding in alphabetical order using reflection
/// </summary>
public void WriteAllFields(object ob, BindingFlags flags)
{
if (ob == null)
return;
Type tp = ob.GetType();
FieldInfo[] fields = tp.GetFields(flags);
NetUtility.SortMembersList(fields);
foreach (FieldInfo fi in fields)
{
object value = fi.GetValue(ob);
// find the appropriate Write method
MethodInfo writeMethod;
if (s_writeMethods.TryGetValue(fi.FieldType, out writeMethod))
writeMethod.Invoke(this, new object[] { value });
else
throw new NetException("Failed to find write method for type " + fi.FieldType);
}
}
/// <summary>
/// Writes all public and private declared instance properties of the object in alphabetical order using reflection
/// </summary>
public void WriteAllProperties(object ob)
{
WriteAllProperties(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}
/// <summary>
/// Writes all properties with specified binding in alphabetical order using reflection
/// </summary>
public void WriteAllProperties(object ob, BindingFlags flags)
{
if (ob == null)
return;
Type tp = ob.GetType();
PropertyInfo[] fields = tp.GetProperties(flags);
NetUtility.SortMembersList(fields);
foreach (PropertyInfo fi in fields)
{
#if UNITY_WEBPLAYER || UNITY_4_5
MethodInfo getMethod = fi.GetGetMethod();
#else
MethodInfo getMethod = fi.GetMethod;
#endif
if (getMethod != null)
{
object value = getMethod.Invoke(ob, null);
// find the appropriate Write method
MethodInfo writeMethod;
if (s_writeMethods.TryGetValue(fi.PropertyType, out writeMethod))
writeMethod.Invoke(this, new object[] { value });
}
}
}
}
}

View File

@ -0,0 +1,676 @@
//#define UNSAFE
//#define BIGENDIAN
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text;
using System.Runtime.InteropServices;
namespace Lidgren.Network
{
/// <summary>
/// Utility struct for writing Singles
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct SingleUIntUnion
{
/// <summary>
/// Value as a 32 bit float
/// </summary>
[FieldOffset(0)]
public float SingleValue;
/// <summary>
/// Value as an unsigned 32 bit integer
/// </summary>
[FieldOffset(0)]
[CLSCompliant(false)]
public uint UIntValue;
}
public partial class NetBuffer
{
/// <summary>
/// Ensures the buffer can hold this number of bits
/// </summary>
public void EnsureBufferSize(int numberOfBits)
{
int byteLen = ((numberOfBits + 7) >> 3);
if (m_data == null)
{
m_data = new byte[byteLen + c_overAllocateAmount];
return;
}
if (m_data.Length < byteLen)
Array.Resize<byte>(ref m_data, byteLen + c_overAllocateAmount);
return;
}
/// <summary>
/// Ensures the buffer can hold this number of bits
/// </summary>
internal void InternalEnsureBufferSize(int numberOfBits)
{
int byteLen = ((numberOfBits + 7) >> 3);
if (m_data == null)
{
m_data = new byte[byteLen];
return;
}
if (m_data.Length < byteLen)
Array.Resize<byte>(ref m_data, byteLen);
return;
}
/// <summary>
/// Writes a boolean value using 1 bit
/// </summary>
public void Write(bool value)
{
EnsureBufferSize(m_bitLength + 1);
NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength);
m_bitLength += 1;
}
/// <summary>
/// Write a byte
/// </summary>
public void Write(byte source)
{
EnsureBufferSize(m_bitLength + 8);
NetBitWriter.WriteByte(source, 8, m_data, m_bitLength);
m_bitLength += 8;
}
/// <summary>
/// Writes a signed byte
/// </summary>
[CLSCompliant(false)]
public void Write(sbyte source)
{
EnsureBufferSize(m_bitLength + 8);
NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength);
m_bitLength += 8;
}
/// <summary>
/// Writes 1 to 8 bits of a byte
/// </summary>
public void Write(byte source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits");
EnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
/// <summary>
/// Writes all bytes in an array
/// </summary>
public void Write(byte[] source)
{
if (source == null)
throw new ArgumentNullException("source");
int bits = source.Length * 8;
EnsureBufferSize(m_bitLength + bits);
NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength);
m_bitLength += bits;
}
/// <summary>
/// Writes the specified number of bytes from an array
/// </summary>
public void Write(byte[] source, int offsetInBytes, int numberOfBytes)
{
if (source == null)
throw new ArgumentNullException("source");
int bits = numberOfBytes * 8;
EnsureBufferSize(m_bitLength + bits);
NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength);
m_bitLength += bits;
}
/// <summary>
/// Writes an unsigned 16 bit integer
/// </summary>
/// <param name="source"></param>
[CLSCompliant(false)]
public void Write(UInt16 source)
{
EnsureBufferSize(m_bitLength + 16);
NetBitWriter.WriteUInt16(source, 16, m_data, m_bitLength);
m_bitLength += 16;
}
/// <summary>
/// Writes a 16 bit unsigned integer at a given offset in the buffer
/// </summary>
[CLSCompliant(false)]
public void WriteAt(Int32 offset, UInt16 source)
{
int newBitLength = Math.Max(m_bitLength, offset + 16);
EnsureBufferSize(newBitLength);
NetBitWriter.WriteUInt16(source, 16, m_data, offset);
m_bitLength = newBitLength;
}
/// <summary>
/// Writes an unsigned integer using 1 to 16 bits
/// </summary>
[CLSCompliant(false)]
public void Write(UInt16 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits");
EnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt16(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
/// <summary>
/// Writes a signed 16 bit integer
/// </summary>
public void Write(Int16 source)
{
EnsureBufferSize(m_bitLength + 16);
NetBitWriter.WriteUInt16((ushort)source, 16, m_data, m_bitLength);
m_bitLength += 16;
}
/// <summary>
/// Writes a 16 bit signed integer at a given offset in the buffer
/// </summary>
public void WriteAt(Int32 offset, Int16 source)
{
int newBitLength = Math.Max(m_bitLength, offset + 16);
EnsureBufferSize(newBitLength);
NetBitWriter.WriteUInt16((ushort)source, 16, m_data, offset);
m_bitLength = newBitLength;
}
#if UNSAFE
/// <summary>
/// Writes a 32 bit signed integer
/// </summary>
public unsafe void Write(Int32 source)
{
EnsureBufferSize(m_bitLength + 32);
// can write fast?
if (m_bitLength % 8 == 0)
{
fixed (byte* numRef = &Data[m_bitLength / 8])
{
*((int*)numRef) = source;
}
}
else
{
NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength);
}
m_bitLength += 32;
}
#else
/// <summary>
/// Writes a 32 bit signed integer
/// </summary>
public void Write(Int32 source)
{
EnsureBufferSize(m_bitLength + 32);
NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength);
m_bitLength += 32;
}
#endif
/// <summary>
/// Writes a 32 bit signed integer at a given offset in the buffer
/// </summary>
public void WriteAt(Int32 offset, Int32 source)
{
int newBitLength = Math.Max(m_bitLength, offset + 32);
EnsureBufferSize(newBitLength);
NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, offset);
m_bitLength = newBitLength;
}
#if UNSAFE
/// <summary>
/// Writes a 32 bit unsigned integer
/// </summary>
public unsafe void Write(UInt32 source)
{
EnsureBufferSize(m_bitLength + 32);
// can write fast?
if (m_bitLength % 8 == 0)
{
fixed (byte* numRef = &Data[m_bitLength / 8])
{
*((uint*)numRef) = source;
}
}
else
{
NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength);
}
m_bitLength += 32;
}
#else
/// <summary>
/// Writes a 32 bit unsigned integer
/// </summary>
[CLSCompliant(false)]
public void Write(UInt32 source)
{
EnsureBufferSize(m_bitLength + 32);
NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength);
m_bitLength += 32;
}
#endif
/// <summary>
/// Writes a 32 bit unsigned integer at a given offset in the buffer
/// </summary>
[CLSCompliant(false)]
public void WriteAt(Int32 offset, UInt32 source)
{
int newBitLength = Math.Max(m_bitLength, offset + 32);
EnsureBufferSize(newBitLength);
NetBitWriter.WriteUInt32(source, 32, m_data, offset);
m_bitLength = newBitLength;
}
/// <summary>
/// Writes a 32 bit signed integer
/// </summary>
[CLSCompliant(false)]
public void Write(UInt32 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits");
EnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
/// <summary>
/// Writes a signed integer using 1 to 32 bits
/// </summary>
public void Write(Int32 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits");
EnsureBufferSize(m_bitLength + numberOfBits);
if (numberOfBits != 32)
{
// make first bit sign
int signBit = 1 << (numberOfBits - 1);
if (source < 0)
source = (-source - 1) | signBit;
else
source &= (~signBit);
}
NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
/// <summary>
/// Writes a 64 bit unsigned integer
/// </summary>
[CLSCompliant(false)]
public void Write(UInt64 source)
{
EnsureBufferSize(m_bitLength + 64);
NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength);
m_bitLength += 64;
}
/// <summary>
/// Writes a 64 bit unsigned integer at a given offset in the buffer
/// </summary>
[CLSCompliant(false)]
public void WriteAt(Int32 offset, UInt64 source)
{
int newBitLength = Math.Max(m_bitLength, offset + 64);
EnsureBufferSize(newBitLength);
NetBitWriter.WriteUInt64(source, 64, m_data, offset);
m_bitLength = newBitLength;
}
/// <summary>
/// Writes an unsigned integer using 1 to 64 bits
/// </summary>
[CLSCompliant(false)]
public void Write(UInt64 source, int numberOfBits)
{
EnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
/// <summary>
/// Writes a 64 bit signed integer
/// </summary>
public void Write(Int64 source)
{
EnsureBufferSize(m_bitLength + 64);
ulong usource = (ulong)source;
NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength);
m_bitLength += 64;
}
/// <summary>
/// Writes a signed integer using 1 to 64 bits
/// </summary>
public void Write(Int64 source, int numberOfBits)
{
EnsureBufferSize(m_bitLength + numberOfBits);
ulong usource = (ulong)source;
NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
//
// Floating point
//
#if UNSAFE
/// <summary>
/// Writes a 32 bit floating point value
/// </summary>
public unsafe void Write(float source)
{
uint val = *((uint*)&source);
#if BIGENDIAN
val = NetUtility.SwapByteOrder(val);
#endif
Write(val);
}
#else
/// <summary>
/// Writes a 32 bit floating point value
/// </summary>
public void Write(float source)
{
// Use union to avoid BitConverter.GetBytes() which allocates memory on the heap
SingleUIntUnion su;
su.UIntValue = 0; // must initialize every member of the union to avoid warning
su.SingleValue = source;
#if BIGENDIAN
// swap byte order
su.UIntValue = NetUtility.SwapByteOrder(su.UIntValue);
#endif
Write(su.UIntValue);
}
#endif
#if UNSAFE
/// <summary>
/// Writes a 64 bit floating point value
/// </summary>
public unsafe void Write(double source)
{
ulong val = *((ulong*)&source);
#if BIGENDIAN
val = NetUtility.SwapByteOrder(val);
#endif
Write(val);
}
#else
/// <summary>
/// Writes a 64 bit floating point value
/// </summary>
public void Write(double source)
{
byte[] val = BitConverter.GetBytes(source);
#if BIGENDIAN
// 0 1 2 3 4 5 6 7
// swap byte order
byte tmp = val[7];
val[7] = val[0];
val[0] = tmp;
tmp = val[6];
val[6] = val[1];
val[1] = tmp;
tmp = val[5];
val[5] = val[2];
val[2] = tmp;
tmp = val[4];
val[4] = val[3];
val[3] = tmp;
#endif
Write(val);
}
#endif
//
// Variable bits
//
/// <summary>
/// Write Base128 encoded variable sized unsigned integer of up to 32 bits
/// </summary>
/// <returns>number of bytes written</returns>
[CLSCompliant(false)]
public int WriteVariableUInt32(uint value)
{
int retval = 1;
uint num1 = (uint)value;
while (num1 >= 0x80)
{
this.Write((byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
this.Write((byte)num1);
return retval;
}
/// <summary>
/// Write Base128 encoded variable sized signed integer of up to 32 bits
/// </summary>
/// <returns>number of bytes written</returns>
public int WriteVariableInt32(int value)
{
uint zigzag = (uint)(value << 1) ^ (uint)(value >> 31);
return WriteVariableUInt32(zigzag);
}
/// <summary>
/// Write Base128 encoded variable sized signed integer of up to 64 bits
/// </summary>
/// <returns>number of bytes written</returns>
public int WriteVariableInt64(Int64 value)
{
ulong zigzag = (ulong)(value << 1) ^ (ulong)(value >> 63);
return WriteVariableUInt64(zigzag);
}
/// <summary>
/// Write Base128 encoded variable sized unsigned integer of up to 64 bits
/// </summary>
/// <returns>number of bytes written</returns>
[CLSCompliant(false)]
public int WriteVariableUInt64(UInt64 value)
{
int retval = 1;
UInt64 num1 = (UInt64)value;
while (num1 >= 0x80)
{
this.Write((byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
this.Write((byte)num1);
return retval;
}
/// <summary>
/// Compress (lossy) a float in the range -1..1 using numberOfBits bits
/// </summary>
public void WriteSignedSingle(float value, int numberOfBits)
{
NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value);
float unit = (value + 1.0f) * 0.5f;
int maxVal = (1 << numberOfBits) - 1;
uint writeVal = (uint)(unit * (float)maxVal);
Write(writeVal, numberOfBits);
}
/// <summary>
/// Compress (lossy) a float in the range 0..1 using numberOfBits bits
/// </summary>
public void WriteUnitSingle(float value, int numberOfBits)
{
NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value);
int maxValue = (1 << numberOfBits) - 1;
uint writeVal = (uint)(value * (float)maxValue);
Write(writeVal, numberOfBits);
}
/// <summary>
/// Compress a float within a specified range using a certain number of bits
/// </summary>
public void WriteRangedSingle(float value, float min, float max, int numberOfBits)
{
NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value);
float range = max - min;
float unit = ((value - min) / range);
int maxVal = (1 << numberOfBits) - 1;
Write((UInt32)((float)maxVal * unit), numberOfBits);
}
/// <summary>
/// Writes an integer with the least amount of bits need for the specified range
/// Returns number of bits written
/// </summary>
public int WriteRangedInteger(int min, int max, int value)
{
NetException.Assert(value >= min && value <= max, "Value not within min/max range!");
uint range = (uint)(max - min);
int numBits = NetUtility.BitsToHoldUInt(range);
uint rvalue = (uint)(value - min);
Write(rvalue, numBits);
return numBits;
}
/// <summary>
/// Write a string
/// </summary>
public void Write(string source)
{
if (string.IsNullOrEmpty(source))
{
WriteVariableUInt32(0);
return;
}
byte[] bytes = Encoding.UTF8.GetBytes(source);
EnsureBufferSize(m_bitLength + 8 + (bytes.Length * 8));
WriteVariableUInt32((uint)bytes.Length);
Write(bytes);
}
/// <summary>
/// Writes an endpoint description
/// </summary>
public void Write(IPEndPoint endPoint)
{
byte[] bytes = endPoint.Address.GetAddressBytes();
Write((byte)bytes.Length);
Write(bytes);
Write((ushort)endPoint.Port);
}
/// <summary>
/// Writes the current local time to a message; readable (and convertable to local time) by the remote host using ReadTime()
/// </summary>
public void WriteTime(bool highPrecision)
{
double localTime = NetTime.Now;
if (highPrecision)
Write(localTime);
else
Write((float)localTime);
}
/// <summary>
/// Writes a local timestamp to a message; readable (and convertable to local time) by the remote host using ReadTime()
/// </summary>
public void WriteTime(double localTime, bool highPrecision)
{
if (highPrecision)
Write(localTime);
else
Write((float)localTime);
}
/// <summary>
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
/// </summary>
public void WritePadBits()
{
m_bitLength = ((m_bitLength + 7) >> 3) * 8;
EnsureBufferSize(m_bitLength);
}
/// <summary>
/// Pads data with the specified number of bits.
/// </summary>
public void WritePadBits(int numberOfBits)
{
m_bitLength += numberOfBits;
EnsureBufferSize(m_bitLength);
}
/// <summary>
/// Append all the bits of message to this message
/// </summary>
public void Write(NetBuffer buffer)
{
EnsureBufferSize(m_bitLength + (buffer.LengthBytes * 8));
Write(buffer.m_data, 0, buffer.LengthBytes);
// did we write excessive bits?
int bitsInLastByte = (buffer.m_bitLength % 8);
if (bitsInLastByte != 0)
{
int excessBits = 8 - bitsInLastByte;
m_bitLength -= excessBits;
}
}
}
}

View File

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Lidgren.Network
{
public partial class NetBuffer
{
/// <summary>
/// Number of bytes to overallocate for each message to avoid resizing
/// </summary>
protected const int c_overAllocateAmount = 4;
private static readonly Dictionary<Type, MethodInfo> s_readMethods;
private static readonly Dictionary<Type, MethodInfo> s_writeMethods;
internal byte[] m_data;
internal int m_bitLength;
internal int m_readPosition;
/// <summary>
/// Gets or sets the internal data buffer
/// </summary>
public byte[] Data
{
get { return m_data; }
set { m_data = value; }
}
/// <summary>
/// Gets or sets the length of the used portion of the buffer in bytes
/// </summary>
public int LengthBytes
{
get { return ((m_bitLength + 7) >> 3); }
set
{
m_bitLength = value * 8;
InternalEnsureBufferSize(m_bitLength);
}
}
/// <summary>
/// Gets or sets the length of the used portion of the buffer in bits
/// </summary>
public int LengthBits
{
get { return m_bitLength; }
set
{
m_bitLength = value;
InternalEnsureBufferSize(m_bitLength);
}
}
/// <summary>
/// Gets or sets the read position in the buffer, in bits (not bytes)
/// </summary>
public long Position
{
get { return (long)m_readPosition; }
set { m_readPosition = (int)value; }
}
/// <summary>
/// Gets the position in the buffer in bytes; note that the bits of the first returned byte may already have been read - check the Position property to make sure.
/// </summary>
public int PositionInBytes
{
get { return (int)(m_readPosition / 8); }
}
static NetBuffer()
{
s_readMethods = new Dictionary<Type, MethodInfo>();
MethodInfo[] methods = typeof(NetIncomingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public);
foreach (MethodInfo mi in methods)
{
if (mi.GetParameters().Length == 0 && mi.Name.StartsWith("Read", StringComparison.InvariantCulture) && mi.Name.Substring(4) == mi.ReturnType.Name)
{
s_readMethods[mi.ReturnType] = mi;
}
}
s_writeMethods = new Dictionary<Type, MethodInfo>();
methods = typeof(NetOutgoingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public);
foreach (MethodInfo mi in methods)
{
if (mi.Name.Equals("Write", StringComparison.InvariantCulture))
{
ParameterInfo[] pis = mi.GetParameters();
if (pis.Length == 1)
s_writeMethods[pis[0].ParameterType] = mi;
}
}
}
}
}

View File

@ -0,0 +1,171 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Net;
namespace Lidgren.Network
{
/// <summary>
/// Specialized version of NetPeer used for a "client" connection. It does not accept any incoming connections and maintains a ServerConnection property
/// </summary>
public class NetClient : NetPeer
{
/// <summary>
/// Gets the connection to the server, if any
/// </summary>
public NetConnection ServerConnection
{
get
{
NetConnection retval = null;
if (m_connections.Count > 0)
{
try
{
retval = m_connections[0];
}
catch
{
// preempted!
return null;
}
}
return retval;
}
}
/// <summary>
/// Gets the connection status of the server connection (or NetConnectionStatus.Disconnected if no connection)
/// </summary>
public NetConnectionStatus ConnectionStatus
{
get
{
var conn = ServerConnection;
if (conn == null)
return NetConnectionStatus.Disconnected;
return conn.Status;
}
}
/// <summary>
/// NetClient constructor
/// </summary>
/// <param name="config"></param>
public NetClient(NetPeerConfiguration config)
: base(config)
{
config.AcceptIncomingConnections = false;
}
/// <summary>
/// Connect to a remote server
/// </summary>
/// <param name="remoteEndPoint">The remote endpoint to connect to</param>
/// <param name="hailMessage">The hail message to pass</param>
/// <returns>server connection, or null if already connected</returns>
public override NetConnection Connect(IPEndPoint remoteEndPoint, NetOutgoingMessage hailMessage)
{
lock (m_connections)
{
if (m_connections.Count > 0)
{
LogWarning("Connect attempt failed; Already connected");
return null;
}
}
lock (m_handshakes)
{
if (m_handshakes.Count > 0)
{
LogWarning("Connect attempt failed; Handshake already in progress");
return null;
}
}
return base.Connect(remoteEndPoint, hailMessage);
}
/// <summary>
/// Disconnect from server
/// </summary>
/// <param name="byeMessage">reason for disconnect</param>
public void Disconnect(string byeMessage)
{
NetConnection serverConnection = ServerConnection;
if (serverConnection == null)
{
lock (m_handshakes)
{
if (m_handshakes.Count > 0)
{
LogVerbose("Aborting connection attempt");
foreach(var hs in m_handshakes)
hs.Value.Disconnect(byeMessage);
return;
}
}
LogWarning("Disconnect requested when not connected!");
return;
}
serverConnection.Disconnect(byeMessage);
}
/// <summary>
/// Sends message to server
/// </summary>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method)
{
NetConnection serverConnection = ServerConnection;
if (serverConnection == null)
{
LogWarning("Cannot send message, no server connection!");
return NetSendResult.FailedNotConnected;
}
return serverConnection.SendMessage(msg, method, 0);
}
/// <summary>
/// Sends message to server
/// </summary>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
{
NetConnection serverConnection = ServerConnection;
if (serverConnection == null)
{
LogWarning("Cannot send message, no server connection!");
return NetSendResult.FailedNotConnected;
}
return serverConnection.SendMessage(msg, method, sequenceChannel);
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetClient " + ServerConnection + "]";
}
}
}

View File

@ -0,0 +1,493 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
public partial class NetConnection
{
internal bool m_connectRequested;
internal bool m_disconnectRequested;
internal bool m_disconnectReqSendBye;
internal string m_disconnectMessage;
internal bool m_connectionInitiator;
internal NetIncomingMessage m_remoteHailMessage;
internal float m_lastHandshakeSendTime;
internal int m_handshakeAttempts;
/// <summary>
/// The message that the remote part specified via Connect() or Approve() - can be null.
/// </summary>
public NetIncomingMessage RemoteHailMessage { get { return m_remoteHailMessage; } }
// heartbeat called when connection still is in m_handshakes of NetPeer
internal void UnconnectedHeartbeat(float now)
{
m_peer.VerifyNetworkThread();
if (m_disconnectRequested)
ExecuteDisconnect(m_disconnectMessage, true);
if (m_connectRequested)
{
switch (m_status)
{
case NetConnectionStatus.Connected:
case NetConnectionStatus.RespondedConnect:
// reconnect
ExecuteDisconnect("Reconnecting", true);
break;
case NetConnectionStatus.InitiatedConnect:
// send another connect attempt
SendConnect(now);
break;
case NetConnectionStatus.Disconnected:
m_peer.ThrowOrLog("This connection is Disconnected; spent. A new one should have been created");
break;
case NetConnectionStatus.Disconnecting:
// let disconnect finish first
break;
case NetConnectionStatus.None:
default:
SendConnect(now);
break;
}
return;
}
if (now - m_lastHandshakeSendTime > m_peerConfiguration.m_resendHandshakeInterval)
{
if (m_handshakeAttempts >= m_peerConfiguration.m_maximumHandshakeAttempts)
{
// failed to connect
ExecuteDisconnect("Failed to establish connection - no response from remote host", true);
return;
}
// resend handshake
switch (m_status)
{
case NetConnectionStatus.InitiatedConnect:
SendConnect(now);
break;
case NetConnectionStatus.RespondedConnect:
SendConnectResponse(now, true);
break;
case NetConnectionStatus.RespondedAwaitingApproval:
// awaiting approval
m_lastHandshakeSendTime = now; // postpone handshake resend
break;
case NetConnectionStatus.None:
case NetConnectionStatus.ReceivedInitiation:
default:
m_peer.LogWarning("Time to resend handshake, but status is " + m_status);
break;
}
}
}
internal void ExecuteDisconnect(string reason, bool sendByeMessage)
{
m_peer.VerifyNetworkThread();
// clear send queues
for (int i = 0; i < m_sendChannels.Length; i++)
{
NetSenderChannelBase channel = m_sendChannels[i];
if (channel != null)
channel.Reset();
}
if (sendByeMessage)
SendDisconnect(reason, true);
if (m_status == NetConnectionStatus.ReceivedInitiation)
{
// nothing much has happened yet; no need to send disconnected status message
m_status = NetConnectionStatus.Disconnected;
}
else
{
SetStatus(NetConnectionStatus.Disconnected, reason);
}
// in case we're still in handshake
lock (m_peer.m_handshakes)
m_peer.m_handshakes.Remove(m_remoteEndPoint);
m_disconnectRequested = false;
m_connectRequested = false;
m_handshakeAttempts = 0;
}
internal void SendConnect(float now)
{
m_peer.VerifyNetworkThread();
int preAllocate = 13 + m_peerConfiguration.AppIdentifier.Length;
preAllocate += (m_localHailMessage == null ? 0 : m_localHailMessage.LengthBytes);
NetOutgoingMessage om = m_peer.CreateMessage(preAllocate);
om.m_messageType = NetMessageType.Connect;
om.Write(m_peerConfiguration.AppIdentifier);
om.Write(m_peer.m_uniqueIdentifier);
om.Write(now);
WriteLocalHail(om);
m_peer.SendLibrary(om, m_remoteEndPoint);
m_connectRequested = false;
m_lastHandshakeSendTime = now;
m_handshakeAttempts++;
if (m_handshakeAttempts > 1)
m_peer.LogDebug("Resending Connect...");
SetStatus(NetConnectionStatus.InitiatedConnect, "Locally requested connect");
}
internal void SendConnectResponse(float now, bool onLibraryThread)
{
if (onLibraryThread)
m_peer.VerifyNetworkThread();
NetOutgoingMessage om = m_peer.CreateMessage(m_peerConfiguration.AppIdentifier.Length + 13 + (m_localHailMessage == null ? 0 : m_localHailMessage.LengthBytes));
om.m_messageType = NetMessageType.ConnectResponse;
om.Write(m_peerConfiguration.AppIdentifier);
om.Write(m_peer.m_uniqueIdentifier);
om.Write(now);
WriteLocalHail(om);
if (onLibraryThread)
m_peer.SendLibrary(om, m_remoteEndPoint);
else
m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple<System.Net.IPEndPoint, NetOutgoingMessage>(m_remoteEndPoint, om));
m_lastHandshakeSendTime = now;
m_handshakeAttempts++;
if (m_handshakeAttempts > 1)
m_peer.LogDebug("Resending ConnectResponse...");
SetStatus(NetConnectionStatus.RespondedConnect, "Remotely requested connect");
}
internal void SendDisconnect(string reason, bool onLibraryThread)
{
if (onLibraryThread)
m_peer.VerifyNetworkThread();
NetOutgoingMessage om = m_peer.CreateMessage(reason);
om.m_messageType = NetMessageType.Disconnect;
if (onLibraryThread)
m_peer.SendLibrary(om, m_remoteEndPoint);
else
m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple<System.Net.IPEndPoint, NetOutgoingMessage>(m_remoteEndPoint, om));
}
private void WriteLocalHail(NetOutgoingMessage om)
{
if (m_localHailMessage != null)
{
byte[] hi = m_localHailMessage.Data;
if (hi != null && hi.Length >= m_localHailMessage.LengthBytes)
{
if (om.LengthBytes + m_localHailMessage.LengthBytes > m_peerConfiguration.m_maximumTransmissionUnit - 10)
m_peer.ThrowOrLog("Hail message too large; can maximally be " + (m_peerConfiguration.m_maximumTransmissionUnit - 10 - om.LengthBytes));
om.Write(m_localHailMessage.Data, 0, m_localHailMessage.LengthBytes);
}
}
}
internal void SendConnectionEstablished()
{
NetOutgoingMessage om = m_peer.CreateMessage(4);
om.m_messageType = NetMessageType.ConnectionEstablished;
om.Write((float)NetTime.Now);
m_peer.SendLibrary(om, m_remoteEndPoint);
m_handshakeAttempts = 0;
InitializePing();
if (m_status != NetConnectionStatus.Connected)
SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier));
}
/// <summary>
/// Approves this connection; sending a connection response to the remote host
/// </summary>
public void Approve()
{
if (m_status != NetConnectionStatus.RespondedAwaitingApproval)
{
m_peer.LogWarning("Approve() called in wrong status; expected RespondedAwaitingApproval; got " + m_status);
return;
}
m_localHailMessage = null;
m_handshakeAttempts = 0;
SendConnectResponse((float)NetTime.Now, false);
}
/// <summary>
/// Approves this connection; sending a connection response to the remote host
/// </summary>
/// <param name="localHail">The local hail message that will be set as RemoteHailMessage on the remote host</param>
public void Approve(NetOutgoingMessage localHail)
{
if (m_status != NetConnectionStatus.RespondedAwaitingApproval)
{
m_peer.LogWarning("Approve() called in wrong status; expected RespondedAwaitingApproval; got " + m_status);
return;
}
m_localHailMessage = localHail;
m_handshakeAttempts = 0;
SendConnectResponse((float)NetTime.Now, false);
}
/// <summary>
/// Denies this connection; disconnecting it
/// </summary>
public void Deny()
{
Deny(string.Empty);
}
/// <summary>
/// Denies this connection; disconnecting it
/// </summary>
/// <param name="reason">The stated reason for the disconnect, readable as a string in the StatusChanged message on the remote host</param>
public void Deny(string reason)
{
// send disconnect; remove from handshakes
SendDisconnect(reason, false);
// remove from handshakes
lock (m_peer.m_handshakes)
m_peer.m_handshakes.Remove(m_remoteEndPoint);
}
internal void ReceivedHandshake(double now, NetMessageType tp, int ptr, int payloadLength)
{
m_peer.VerifyNetworkThread();
byte[] hail;
switch (tp)
{
case NetMessageType.Connect:
if (m_status == NetConnectionStatus.ReceivedInitiation)
{
// Whee! Server full has already been checked
bool ok = ValidateHandshakeData(ptr, payloadLength, out hail);
if (ok)
{
if (hail != null)
{
m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail);
m_remoteHailMessage.LengthBits = (hail.Length * 8);
}
else
{
m_remoteHailMessage = null;
}
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval))
{
// ok, let's not add connection just yet
NetIncomingMessage appMsg = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, (m_remoteHailMessage == null ? 0 : m_remoteHailMessage.LengthBytes));
appMsg.m_receiveTime = now;
appMsg.m_senderConnection = this;
appMsg.m_senderEndPoint = this.m_remoteEndPoint;
if (m_remoteHailMessage != null)
appMsg.Write(m_remoteHailMessage.m_data, 0, m_remoteHailMessage.LengthBytes);
SetStatus(NetConnectionStatus.RespondedAwaitingApproval, "Awaiting approval");
m_peer.ReleaseMessage(appMsg);
return;
}
SendConnectResponse((float)now, true);
}
return;
}
if (m_status == NetConnectionStatus.RespondedAwaitingApproval)
{
m_peer.LogWarning("Ignoring multiple Connect() most likely due to a delayed Approval");
return;
}
if (m_status == NetConnectionStatus.RespondedConnect)
{
// our ConnectResponse must have been lost
SendConnectResponse((float)now, true);
return;
}
m_peer.LogDebug("Unhandled Connect: " + tp + ", status is " + m_status + " length: " + payloadLength);
break;
case NetMessageType.ConnectResponse:
HandleConnectResponse(now, tp, ptr, payloadLength);
break;
case NetMessageType.ConnectionEstablished:
switch (m_status)
{
case NetConnectionStatus.Connected:
// ok...
break;
case NetConnectionStatus.Disconnected:
case NetConnectionStatus.Disconnecting:
case NetConnectionStatus.None:
// too bad, almost made it
break;
case NetConnectionStatus.ReceivedInitiation:
// uh, a little premature... ignore
break;
case NetConnectionStatus.InitiatedConnect:
// weird, should have been RespondedConnect...
break;
case NetConnectionStatus.RespondedConnect:
// awesome
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
InitializeRemoteTimeOffset(msg.ReadSingle());
m_peer.AcceptConnection(this);
InitializePing();
SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier));
return;
}
break;
case NetMessageType.Disconnect:
// ouch
string reason = "Ouch";
try
{
NetIncomingMessage inc = m_peer.SetupReadHelperMessage(ptr, payloadLength);
reason = inc.ReadString();
}
catch
{
}
ExecuteDisconnect(reason, false);
break;
case NetMessageType.Discovery:
m_peer.HandleIncomingDiscoveryRequest(now, m_remoteEndPoint, ptr, payloadLength);
return;
case NetMessageType.DiscoveryResponse:
m_peer.HandleIncomingDiscoveryResponse(now, m_remoteEndPoint, ptr, payloadLength);
return;
case NetMessageType.Ping:
// silently ignore
return;
default:
m_peer.LogDebug("Unhandled type during handshake: " + tp + " length: " + payloadLength);
break;
}
}
private void HandleConnectResponse(double now, NetMessageType tp, int ptr, int payloadLength)
{
byte[] hail;
switch (m_status)
{
case NetConnectionStatus.InitiatedConnect:
// awesome
bool ok = ValidateHandshakeData(ptr, payloadLength, out hail);
if (ok)
{
if (hail != null)
{
m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail);
m_remoteHailMessage.LengthBits = (hail.Length * 8);
}
else
{
m_remoteHailMessage = null;
}
m_peer.AcceptConnection(this);
SendConnectionEstablished();
return;
}
break;
case NetConnectionStatus.RespondedConnect:
// hello, wtf?
break;
case NetConnectionStatus.Disconnecting:
case NetConnectionStatus.Disconnected:
case NetConnectionStatus.ReceivedInitiation:
case NetConnectionStatus.None:
// wtf? anyway, bye!
break;
case NetConnectionStatus.Connected:
// my ConnectionEstablished must have been lost, send another one
SendConnectionEstablished();
return;
}
}
private bool ValidateHandshakeData(int ptr, int payloadLength, out byte[] hail)
{
hail = null;
// create temporary incoming message
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
try
{
string remoteAppIdentifier = msg.ReadString();
long remoteUniqueIdentifier = msg.ReadInt64();
InitializeRemoteTimeOffset(msg.ReadSingle());
int remainingBytes = payloadLength - (msg.PositionInBytes - ptr);
if (remainingBytes > 0)
hail = msg.ReadBytes(remainingBytes);
if (remoteAppIdentifier != m_peer.m_configuration.AppIdentifier)
{
ExecuteDisconnect("Wrong application identifier!", true);
return false;
}
m_remoteUniqueIdentifier = remoteUniqueIdentifier;
}
catch(Exception ex)
{
// whatever; we failed
ExecuteDisconnect("Handshake data validation failed", true);
m_peer.LogWarning("ReadRemoteHandshakeData failed: " + ex.Message);
return false;
}
return true;
}
/// <summary>
/// Disconnect from the remote peer
/// </summary>
/// <param name="byeMessage">the message to send with the disconnect message</param>
public void Disconnect(string byeMessage)
{
// user or library thread
if (m_status == NetConnectionStatus.None || m_status == NetConnectionStatus.Disconnected)
return;
m_peer.LogVerbose("Disconnect requested for " + this);
m_disconnectMessage = byeMessage;
if (m_status != NetConnectionStatus.Disconnected && m_status != NetConnectionStatus.None)
SetStatus(NetConnectionStatus.Disconnecting, byeMessage);
m_handshakeAttempts = 0;
m_disconnectRequested = true;
m_disconnectReqSendBye = true;
}
}
}

View File

@ -0,0 +1,149 @@
using System;
namespace Lidgren.Network
{
public partial class NetConnection
{
private float m_sentPingTime;
private int m_sentPingNumber;
private float m_averageRoundtripTime;
private float m_timeoutDeadline = float.MaxValue;
// local time value + m_remoteTimeOffset = remote time value
internal double m_remoteTimeOffset;
/// <summary>
/// Gets the current average roundtrip time in seconds
/// </summary>
public float AverageRoundtripTime { get { return m_averageRoundtripTime; } }
/// <summary>
/// Time offset between this peer and the remote peer
/// </summary>
public float RemoteTimeOffset { get { return (float)m_remoteTimeOffset; } }
// this might happen more than once
internal void InitializeRemoteTimeOffset(float remoteSendTime)
{
m_remoteTimeOffset = (remoteSendTime + (m_averageRoundtripTime / 2.0)) - NetTime.Now;
}
/// <summary>
/// Gets local time value comparable to NetTime.Now from a remote value
/// </summary>
public double GetLocalTime(double remoteTimestamp)
{
return remoteTimestamp - m_remoteTimeOffset;
}
/// <summary>
/// Gets the remote time value for a local time value produced by NetTime.Now
/// </summary>
public double GetRemoteTime(double localTimestamp)
{
return localTimestamp + m_remoteTimeOffset;
}
internal void InitializePing()
{
float now = (float)NetTime.Now;
// randomize ping sent time (0.25 - 1.0 x ping interval)
m_sentPingTime = now;
m_sentPingTime -= (m_peerConfiguration.PingInterval * 0.25f); // delay ping for a little while
m_sentPingTime -= (MWCRandom.Instance.NextSingle() * (m_peerConfiguration.PingInterval * 0.75f));
m_timeoutDeadline = now + (m_peerConfiguration.m_connectionTimeout * 2.0f); // initially allow a little more time
// make it better, quick :-)
SendPing();
}
internal void SendPing()
{
m_peer.VerifyNetworkThread();
m_sentPingNumber++;
m_sentPingTime = (float)NetTime.Now;
NetOutgoingMessage om = m_peer.CreateMessage(1);
om.Write((byte)m_sentPingNumber); // truncating to 0-255
om.m_messageType = NetMessageType.Ping;
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
bool connectionReset;
m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset);
m_statistics.PacketSent(len, 1);
m_peer.Recycle(om);
}
internal void SendPong(int pingNumber)
{
m_peer.VerifyNetworkThread();
NetOutgoingMessage om = m_peer.CreateMessage(5);
om.Write((byte)pingNumber);
om.Write((float)NetTime.Now); // we should update this value to reflect the exact point in time the packet is SENT
om.m_messageType = NetMessageType.Pong;
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
bool connectionReset;
m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset);
m_statistics.PacketSent(len, 1);
m_peer.Recycle(om);
}
internal void ReceivedPong(float now, int pongNumber, float remoteSendTime)
{
if ((byte)pongNumber != (byte)m_sentPingNumber)
{
m_peer.LogVerbose("Ping/Pong mismatch; dropped message?");
return;
}
m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout;
float rtt = now - m_sentPingTime;
NetException.Assert(rtt >= 0);
double diff = (remoteSendTime + (rtt / 2.0)) - now;
if (m_averageRoundtripTime < 0)
{
m_remoteTimeOffset = diff;
m_averageRoundtripTime = rtt;
m_peer.LogDebug("Initiated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + " Remote time is: " + (now + diff));
}
else
{
m_averageRoundtripTime = (m_averageRoundtripTime * 0.7f) + (float)(rtt * 0.3f);
m_remoteTimeOffset = ((m_remoteTimeOffset * (double)(m_sentPingNumber - 1)) + diff) / (double)m_sentPingNumber;
m_peer.LogVerbose("Updated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + ", remote time to " + (now + m_remoteTimeOffset) + " (ie. diff " + m_remoteTimeOffset + ")");
}
// update resend delay for all channels
float resendDelay = GetResendDelay();
foreach (var chan in m_sendChannels)
{
var rchan = chan as NetReliableSenderChannel;
if (rchan != null)
rchan.m_resendDelay = resendDelay;
}
// m_peer.LogVerbose("Timeout deadline pushed to " + m_timeoutDeadline);
// notify the application that average rtt changed
if (m_peer.m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionLatencyUpdated))
{
NetIncomingMessage update = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionLatencyUpdated, 4);
update.m_senderConnection = this;
update.m_senderEndPoint = this.m_remoteEndPoint;
update.Write(rtt);
m_peer.ReleaseMessage(update);
}
}
}
}

View File

@ -0,0 +1,182 @@
using System;
namespace Lidgren.Network
{
public partial class NetConnection
{
private enum ExpandMTUStatus
{
None,
InProgress,
Finished
}
private const int c_protocolMaxMTU = (int)((((float)ushort.MaxValue / 8.0f) - 1.0f));
private ExpandMTUStatus m_expandMTUStatus;
private int m_largestSuccessfulMTU;
private int m_smallestFailedMTU;
private int m_lastSentMTUAttemptSize;
private double m_lastSentMTUAttemptTime;
private int m_mtuAttemptFails;
internal int m_currentMTU;
/// <summary>
/// Gets the current MTU in bytes. If PeerConfiguration.AutoExpandMTU is false, this will be PeerConfiguration.MaximumTransmissionUnit.
/// </summary>
public int CurrentMTU { get { return m_currentMTU; } }
internal void InitExpandMTU(double now)
{
m_lastSentMTUAttemptTime = now + m_peerConfiguration.m_expandMTUFrequency + 1.5f + m_averageRoundtripTime; // wait a tiny bit before starting to expand mtu
m_largestSuccessfulMTU = 512;
m_smallestFailedMTU = -1;
m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit;
}
private void MTUExpansionHeartbeat(double now)
{
if (m_expandMTUStatus == ExpandMTUStatus.Finished)
return;
if (m_expandMTUStatus == ExpandMTUStatus.None)
{
if (m_peerConfiguration.m_autoExpandMTU == false)
{
FinalizeMTU(m_currentMTU);
return;
}
// begin expansion
ExpandMTU(now);
return;
}
if (now > m_lastSentMTUAttemptTime + m_peerConfiguration.ExpandMTUFrequency)
{
m_mtuAttemptFails++;
if (m_mtuAttemptFails == 3)
{
FinalizeMTU(m_currentMTU);
return;
}
// timed out; ie. failed
m_smallestFailedMTU = m_lastSentMTUAttemptSize;
ExpandMTU(now);
}
}
private void ExpandMTU(double now)
{
int tryMTU;
// we've nevered encountered failure
if (m_smallestFailedMTU == -1)
{
// we've never encountered failure; expand by 25% each time
tryMTU = (int)((float)m_currentMTU * 1.25f);
//m_peer.LogDebug("Trying MTU " + tryMTU);
}
else
{
// we HAVE encountered failure; so try in between
tryMTU = (int)(((float)m_smallestFailedMTU + (float)m_largestSuccessfulMTU) / 2.0f);
//m_peer.LogDebug("Trying MTU " + m_smallestFailedMTU + " <-> " + m_largestSuccessfulMTU + " = " + tryMTU);
}
if (tryMTU > c_protocolMaxMTU)
tryMTU = c_protocolMaxMTU;
if (tryMTU == m_largestSuccessfulMTU)
{
//m_peer.LogDebug("Found optimal MTU - exiting");
FinalizeMTU(m_largestSuccessfulMTU);
return;
}
SendExpandMTU(now, tryMTU);
}
private void SendExpandMTU(double now, int size)
{
NetOutgoingMessage om = m_peer.CreateMessage(size);
byte[] tmp = new byte[size];
om.Write(tmp);
om.m_messageType = NetMessageType.ExpandMTURequest;
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
bool ok = m_peer.SendMTUPacket(len, m_remoteEndPoint);
if (ok == false)
{
//m_peer.LogDebug("Send MTU failed for size " + size);
// failure
if (m_smallestFailedMTU == -1 || size < m_smallestFailedMTU)
{
m_smallestFailedMTU = size;
m_mtuAttemptFails++;
if (m_mtuAttemptFails >= m_peerConfiguration.ExpandMTUFailAttempts)
{
FinalizeMTU(m_largestSuccessfulMTU);
return;
}
}
ExpandMTU(now);
return;
}
m_lastSentMTUAttemptSize = size;
m_lastSentMTUAttemptTime = now;
m_statistics.PacketSent(len, 1);
m_peer.Recycle(om);
}
private void FinalizeMTU(int size)
{
if (m_expandMTUStatus == ExpandMTUStatus.Finished)
return;
m_expandMTUStatus = ExpandMTUStatus.Finished;
m_currentMTU = size;
if (m_currentMTU != m_peerConfiguration.m_maximumTransmissionUnit)
m_peer.LogDebug("Expanded Maximum Transmission Unit to: " + m_currentMTU + " bytes");
return;
}
private void SendMTUSuccess(int size)
{
NetOutgoingMessage om = m_peer.CreateMessage(4);
om.Write(size);
om.m_messageType = NetMessageType.ExpandMTUSuccess;
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
bool connectionReset;
m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset);
m_peer.Recycle(om);
//m_peer.LogDebug("Received MTU expand request for " + size + " bytes");
m_statistics.PacketSent(len, 1);
}
private void HandleExpandMTUSuccess(double now, int size)
{
if (size > m_largestSuccessfulMTU)
m_largestSuccessfulMTU = size;
if (size < m_currentMTU)
{
//m_peer.LogDebug("Received low MTU expand success (size " + size + "); current mtu is " + m_currentMTU);
return;
}
//m_peer.LogDebug("Expanding MTU to " + size);
m_currentMTU = size;
ExpandMTU(now);
}
}
}

View File

@ -0,0 +1,563 @@
using System;
using System.Net;
using System.Threading;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Represents a connection to a remote peer
/// </summary>
[DebuggerDisplay("RemoteUniqueIdentifier={RemoteUniqueIdentifier} RemoteEndPoint={remoteEndPoint}")]
public partial class NetConnection
{
private const int m_infrequentEventsSkipFrames = 8; // number of heartbeats to skip checking for infrequent events (ping, timeout etc)
private const int m_messageCoalesceFrames = 3; // number of heartbeats to wait for more incoming messages before sending packet
internal NetPeer m_peer;
internal NetPeerConfiguration m_peerConfiguration;
internal NetConnectionStatus m_status;
internal NetConnectionStatus m_visibleStatus;
internal IPEndPoint m_remoteEndPoint;
internal NetSenderChannelBase[] m_sendChannels;
internal NetReceiverChannelBase[] m_receiveChannels;
internal NetOutgoingMessage m_localHailMessage;
internal long m_remoteUniqueIdentifier;
internal NetQueue<NetTuple<NetMessageType, int>> m_queuedOutgoingAcks;
internal NetQueue<NetTuple<NetMessageType, int>> m_queuedIncomingAcks;
private int m_sendBufferWritePtr;
private int m_sendBufferNumMessages;
private object m_tag;
internal NetConnectionStatistics m_statistics;
/// <summary>
/// Gets or sets the application defined object containing data about the connection
/// </summary>
public object Tag
{
get { return m_tag; }
set { m_tag = value; }
}
/// <summary>
/// Gets the peer which holds this connection
/// </summary>
public NetPeer Peer { get { return m_peer; } }
/// <summary>
/// Gets the current status of the connection (synced to the last status message read)
/// </summary>
public NetConnectionStatus Status { get { return m_visibleStatus; } }
/// <summary>
/// Gets various statistics for this connection
/// </summary>
public NetConnectionStatistics Statistics { get { return m_statistics; } }
/// <summary>
/// Gets the remote endpoint for the connection
/// </summary>
public IPEndPoint RemoteEndPoint { get { return m_remoteEndPoint; } }
/// <summary>
/// Gets the unique identifier of the remote NetPeer for this connection
/// </summary>
public long RemoteUniqueIdentifier { get { return m_remoteUniqueIdentifier; } }
/// <summary>
/// Gets the local hail message that was sent as part of the handshake
/// </summary>
public NetOutgoingMessage LocalHailMessage { get { return m_localHailMessage; } }
// gets the time before automatically resending an unacked message
internal float GetResendDelay()
{
float avgRtt = m_averageRoundtripTime;
if (avgRtt <= 0)
avgRtt = 0.1f; // "default" resend is based on 100 ms roundtrip time
return 0.025f + (avgRtt * 2.1f); // 25 ms + double rtt
}
internal NetConnection(NetPeer peer, IPEndPoint remoteEndPoint)
{
m_peer = peer;
m_peerConfiguration = m_peer.Configuration;
m_status = NetConnectionStatus.None;
m_visibleStatus = NetConnectionStatus.None;
m_remoteEndPoint = remoteEndPoint;
m_sendChannels = new NetSenderChannelBase[NetConstants.NumTotalChannels];
m_receiveChannels = new NetReceiverChannelBase[NetConstants.NumTotalChannels];
m_queuedOutgoingAcks = new NetQueue<NetTuple<NetMessageType, int>>(4);
m_queuedIncomingAcks = new NetQueue<NetTuple<NetMessageType, int>>(4);
m_statistics = new NetConnectionStatistics(this);
m_averageRoundtripTime = -1.0f;
m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit;
}
/// <summary>
/// Change the internal endpoint to this new one. Used when, during handshake, a switch in port is detected (due to NAT)
/// </summary>
internal void MutateEndPoint(IPEndPoint endPoint)
{
m_remoteEndPoint = endPoint;
}
internal void SetStatus(NetConnectionStatus status, string reason)
{
// user or library thread
if (status == m_status)
return;
m_status = status;
if (reason == null)
reason = string.Empty;
if (m_status == NetConnectionStatus.Connected)
{
m_timeoutDeadline = (float)NetTime.Now + m_peerConfiguration.m_connectionTimeout;
m_peer.LogVerbose("Timeout deadline initialized to " + m_timeoutDeadline);
}
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.StatusChanged))
{
NetIncomingMessage info = m_peer.CreateIncomingMessage(NetIncomingMessageType.StatusChanged, 4 + reason.Length + (reason.Length > 126 ? 2 : 1));
info.m_senderConnection = this;
info.m_senderEndPoint = m_remoteEndPoint;
info.Write((byte)m_status);
info.Write(reason);
m_peer.ReleaseMessage(info);
}
else
{
// app dont want those messages, update visible status immediately
m_visibleStatus = m_status;
}
}
internal void Heartbeat(float now, uint frameCounter)
{
m_peer.VerifyNetworkThread();
NetException.Assert(m_status != NetConnectionStatus.InitiatedConnect && m_status != NetConnectionStatus.RespondedConnect);
if ((frameCounter % m_infrequentEventsSkipFrames) == 0)
{
if (now > m_timeoutDeadline)
{
//
// connection timed out
//
m_peer.LogVerbose("Connection timed out at " + now + " deadline was " + m_timeoutDeadline);
ExecuteDisconnect("Connection timed out", true);
return;
}
// send ping?
if (m_status == NetConnectionStatus.Connected)
{
if (now > m_sentPingTime + m_peer.m_configuration.m_pingInterval)
SendPing();
// handle expand mtu
MTUExpansionHeartbeat(now);
}
if (m_disconnectRequested)
{
ExecuteDisconnect(m_disconnectMessage, m_disconnectReqSendBye);
return;
}
}
bool connectionReset; // TODO: handle connection reset
//
// Note: at this point m_sendBufferWritePtr and m_sendBufferNumMessages may be non-null; resends may already be queued up
//
byte[] sendBuffer = m_peer.m_sendBuffer;
int mtu = m_currentMTU;
if ((frameCounter % m_messageCoalesceFrames) == 0) // coalesce a few frames
{
//
// send ack messages
//
while (m_queuedOutgoingAcks.Count > 0)
{
int acks = (mtu - (m_sendBufferWritePtr + 5)) / 3; // 3 bytes per actual ack
if (acks > m_queuedOutgoingAcks.Count)
acks = m_queuedOutgoingAcks.Count;
NetException.Assert(acks > 0);
m_sendBufferNumMessages++;
// write acks header
sendBuffer[m_sendBufferWritePtr++] = (byte)NetMessageType.Acknowledge;
sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number
sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number
int len = (acks * 3) * 8; // bits
sendBuffer[m_sendBufferWritePtr++] = (byte)len;
sendBuffer[m_sendBufferWritePtr++] = (byte)(len >> 8);
// write acks
for (int i = 0; i < acks; i++)
{
NetTuple<NetMessageType, int> tuple;
m_queuedOutgoingAcks.TryDequeue(out tuple);
//m_peer.LogVerbose("Sending ack for " + tuple.Item1 + "#" + tuple.Item2);
sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item1;
sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item2;
sendBuffer[m_sendBufferWritePtr++] = (byte)(tuple.Item2 >> 8);
}
if (m_queuedOutgoingAcks.Count > 0)
{
// send packet and go for another round of acks
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0);
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset);
m_statistics.PacketSent(m_sendBufferWritePtr, 1);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
//
// Parse incoming acks (may trigger resends)
//
NetTuple<NetMessageType, int> incAck;
while (m_queuedIncomingAcks.TryDequeue(out incAck))
{
//m_peer.LogVerbose("Received ack for " + acktp + "#" + seqNr);
NetSenderChannelBase chan = m_sendChannels[(int)incAck.Item1 - 1];
// If we haven't sent a message on this channel there is no reason to ack it
if (chan == null)
continue;
chan.ReceiveAcknowledge(now, incAck.Item2);
}
}
//
// send queued messages
//
if (m_peer.m_executeFlushSendQueue)
{
for (int i = m_sendChannels.Length - 1; i >= 0; i--) // Reverse order so reliable messages are sent first
{
var channel = m_sendChannels[i];
NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0);
if (channel != null)
channel.SendQueuedMessages(now);
NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0);
}
}
//
// Put on wire data has been written to send buffer but not yet sent
//
if (m_sendBufferWritePtr > 0)
{
m_peer.VerifyNetworkThread();
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0);
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset);
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
// Queue an item for immediate sending on the wire
// This method is called from the ISenderChannels
internal void QueueSendMessage(NetOutgoingMessage om, int seqNr)
{
m_peer.VerifyNetworkThread();
int sz = om.GetEncodedSize();
//if (sz > m_currentMTU)
// m_peer.LogWarning("Message larger than MTU! Fragmentation must have failed!");
bool connReset; // TODO: handle connection reset
// can fit this message together with previously written to buffer?
if (m_sendBufferWritePtr + sz > m_currentMTU)
{
if (m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0)
{
// previous message in buffer; send these first
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset);
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
// encode it into buffer regardless if it (now) fits within MTU or not
m_sendBufferWritePtr = om.Encode(m_peer.m_sendBuffer, m_sendBufferWritePtr, seqNr);
m_sendBufferNumMessages++;
if (m_sendBufferWritePtr > m_currentMTU)
{
// send immediately; we're already over MTU
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset);
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
/// <summary>
/// Send a message to this remote connection
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="method">How to deliver the message</param>
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
{
return m_peer.SendMessage(msg, this, method, sequenceChannel);
}
// called by SendMessage() and NetPeer.SendMessage; ie. may be user thread
internal NetSendResult EnqueueMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
{
if (m_status != NetConnectionStatus.Connected)
return NetSendResult.FailedNotConnected;
NetMessageType tp = (NetMessageType)((int)method + sequenceChannel);
msg.m_messageType = tp;
// TODO: do we need to make this more thread safe?
int channelSlot = (int)method - 1 + sequenceChannel;
NetSenderChannelBase chan = m_sendChannels[channelSlot];
if (chan == null)
chan = CreateSenderChannel(tp);
if ((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.UnreliableSequenced) && msg.GetEncodedSize() > m_currentMTU)
m_peer.ThrowOrLog("Reliable message too large! Fragmentation failure?");
var retval = chan.Enqueue(msg);
//if (retval == NetSendResult.Sent && m_peerConfiguration.m_autoFlushSendQueue == false)
// retval = NetSendResult.Queued; // queued since we're not autoflushing
return retval;
}
// may be on user thread
private NetSenderChannelBase CreateSenderChannel(NetMessageType tp)
{
NetSenderChannelBase chan;
lock (m_sendChannels)
{
NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp);
int sequenceChannel = (int)tp - (int)method;
int channelSlot = (int)method - 1 + sequenceChannel;
if (m_sendChannels[channelSlot] != null)
{
// we were pre-empted by another call to this method
chan = m_sendChannels[channelSlot];
}
else
{
switch (method)
{
case NetDeliveryMethod.Unreliable:
case NetDeliveryMethod.UnreliableSequenced:
chan = new NetUnreliableSenderChannel(this, NetUtility.GetWindowSize(method));
break;
case NetDeliveryMethod.ReliableOrdered:
chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method));
break;
case NetDeliveryMethod.ReliableSequenced:
case NetDeliveryMethod.ReliableUnordered:
default:
chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method));
break;
}
m_sendChannels[channelSlot] = chan;
}
}
return chan;
}
// received a library message while Connected
internal void ReceivedLibraryMessage(NetMessageType tp, int ptr, int payloadLength)
{
m_peer.VerifyNetworkThread();
float now = (float)NetTime.Now;
switch (tp)
{
case NetMessageType.Connect:
m_peer.LogDebug("Received handshake message (" + tp + ") despite connection being in place");
break;
case NetMessageType.ConnectResponse:
// handshake message must have been lost
HandleConnectResponse(now, tp, ptr, payloadLength);
break;
case NetMessageType.ConnectionEstablished:
// do nothing, all's well
break;
case NetMessageType.LibraryError:
m_peer.ThrowOrLog("LibraryError received by ReceivedLibraryMessage; this usually indicates a malformed message");
break;
case NetMessageType.Disconnect:
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
m_disconnectRequested = true;
m_disconnectMessage = msg.ReadString();
m_disconnectReqSendBye = false;
//ExecuteDisconnect(msg.ReadString(), false);
break;
case NetMessageType.Acknowledge:
for (int i = 0; i < payloadLength; i+=3)
{
NetMessageType acktp = (NetMessageType)m_peer.m_receiveBuffer[ptr++]; // netmessagetype
int seqNr = m_peer.m_receiveBuffer[ptr++];
seqNr |= (m_peer.m_receiveBuffer[ptr++] << 8);
// need to enqueue this and handle it in the netconnection heartbeat; so be able to send resends together with normal sends
m_queuedIncomingAcks.Enqueue(new NetTuple<NetMessageType, int>(acktp, seqNr));
}
break;
case NetMessageType.Ping:
int pingNr = m_peer.m_receiveBuffer[ptr++];
SendPong(pingNr);
break;
case NetMessageType.Pong:
NetIncomingMessage pmsg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
int pongNr = pmsg.ReadByte();
float remoteSendTime = pmsg.ReadSingle();
ReceivedPong(now, pongNr, remoteSendTime);
break;
case NetMessageType.ExpandMTURequest:
SendMTUSuccess(payloadLength);
break;
case NetMessageType.ExpandMTUSuccess:
if (m_peer.Configuration.AutoExpandMTU == false)
{
m_peer.LogDebug("Received ExpandMTURequest altho AutoExpandMTU is turned off!");
break;
}
NetIncomingMessage emsg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
int size = emsg.ReadInt32();
HandleExpandMTUSuccess(now, size);
break;
case NetMessageType.NatIntroduction:
// Unusual situation where server is actually already known, but got a nat introduction - oh well, lets handle it as usual
m_peer.HandleNatIntroduction(ptr);
break;
default:
m_peer.LogWarning("Connection received unhandled library message: " + tp);
break;
}
}
internal void ReceivedMessage(NetIncomingMessage msg)
{
m_peer.VerifyNetworkThread();
NetMessageType tp = msg.m_receivedMessageType;
int channelSlot = (int)tp - 1;
NetReceiverChannelBase chan = m_receiveChannels[channelSlot];
if (chan == null)
chan = CreateReceiverChannel(tp);
chan.ReceiveMessage(msg);
}
private NetReceiverChannelBase CreateReceiverChannel(NetMessageType tp)
{
m_peer.VerifyNetworkThread();
// create receiver channel
NetReceiverChannelBase chan;
NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp);
switch (method)
{
case NetDeliveryMethod.Unreliable:
chan = new NetUnreliableUnorderedReceiver(this);
break;
case NetDeliveryMethod.ReliableOrdered:
chan = new NetReliableOrderedReceiver(this, NetConstants.ReliableOrderedWindowSize);
break;
case NetDeliveryMethod.UnreliableSequenced:
chan = new NetUnreliableSequencedReceiver(this);
break;
case NetDeliveryMethod.ReliableUnordered:
chan = new NetReliableUnorderedReceiver(this, NetConstants.ReliableOrderedWindowSize);
break;
case NetDeliveryMethod.ReliableSequenced:
chan = new NetReliableSequencedReceiver(this, NetConstants.ReliableSequencedWindowSize);
break;
default:
throw new NetException("Unhandled NetDeliveryMethod!");
}
int channelSlot = (int)tp - 1;
NetException.Assert(m_receiveChannels[channelSlot] == null);
m_receiveChannels[channelSlot] = chan;
return chan;
}
internal void QueueAck(NetMessageType tp, int sequenceNumber)
{
m_queuedOutgoingAcks.Enqueue(new NetTuple<NetMessageType, int>(tp, sequenceNumber));
}
/// <summary>
/// Zero windowSize indicates that the channel is not yet instantiated (used)
/// Negative freeWindowSlots means this amount of messages are currently queued but delayed due to closed window
/// </summary>
public void GetSendQueueInfo(NetDeliveryMethod method, int sequenceChannel, out int windowSize, out int freeWindowSlots)
{
int channelSlot = (int)method - 1 + sequenceChannel;
var chan = m_sendChannels[channelSlot];
if (chan == null)
{
windowSize = NetUtility.GetWindowSize(method);
freeWindowSlots = windowSize;
return;
}
windowSize = chan.WindowSize;
freeWindowSlots = chan.GetAllowedSends() - chan.m_queuedSends.Count;
return;
}
public bool CanSendImmediately(NetDeliveryMethod method, int sequenceChannel)
{
int channelSlot = (int)method - 1 + sequenceChannel;
var chan = m_sendChannels[channelSlot];
if (chan == null)
return true;
return (chan.GetAllowedSends() - chan.m_queuedSends.Count) > 0;
}
internal void Shutdown(string reason)
{
ExecuteDisconnect(reason, true);
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetConnection to " + m_remoteEndPoint + "]";
}
}
}

View File

@ -0,0 +1,212 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Uncomment the line below to get statistics in RELEASE builds
//#define USE_RELEASE_STATISTICS
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace Lidgren.Network
{
internal enum MessageResendReason
{
Delay,
HoleInSequence
}
/// <summary>
/// Statistics for a NetConnection instance
/// </summary>
public sealed class NetConnectionStatistics
{
private readonly NetConnection m_connection;
internal int m_sentPackets;
internal int m_receivedPackets;
internal int m_sentMessages;
internal int m_receivedMessages;
internal int m_receivedFragments;
internal int m_sentBytes;
internal int m_receivedBytes;
internal int m_resentMessagesDueToDelay;
internal int m_resentMessagesDueToHole;
internal NetConnectionStatistics(NetConnection conn)
{
m_connection = conn;
Reset();
}
internal void Reset()
{
m_sentPackets = 0;
m_receivedPackets = 0;
m_sentMessages = 0;
m_receivedMessages = 0;
m_receivedFragments = 0;
m_sentBytes = 0;
m_receivedBytes = 0;
m_resentMessagesDueToDelay = 0;
m_resentMessagesDueToHole = 0;
}
/// <summary>
/// Gets the number of sent packets for this connection
/// </summary>
public int SentPackets { get { return m_sentPackets; } }
/// <summary>
/// Gets the number of received packets for this connection
/// </summary>
public int ReceivedPackets { get { return m_receivedPackets; } }
/// <summary>
/// Gets the number of sent bytes for this connection
/// </summary>
public int SentBytes { get { return m_sentBytes; } }
/// <summary>
/// Gets the number of received bytes for this connection
/// </summary>
public int ReceivedBytes { get { return m_receivedBytes; } }
/// <summary>
/// Gets the number of resent reliable messages for this connection
/// </summary>
public int ResentMessages { get { return m_resentMessagesDueToHole + m_resentMessagesDueToDelay; } }
// public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } }
#if USE_RELEASE_STATISTICS
internal void PacketSent(int numBytes, int numMessages)
{
NetException.Assert(numBytes > 0 && numMessages > 0);
m_sentPackets++;
m_sentBytes += numBytes;
m_sentMessages += numMessages;
}
#else
[Conditional("DEBUG")]
internal void PacketSent(int numBytes, int numMessages)
{
NetException.Assert(numBytes > 0 && numMessages > 0);
m_sentPackets++;
m_sentBytes += numBytes;
m_sentMessages += numMessages;
}
#endif
#if USE_RELEASE_STATISTICS
internal void PacketReceived(int numBytes, int numMessages)
{
NetException.Assert(numBytes > 0 && numMessages > 0);
m_receivedPackets++;
m_receivedBytes += numBytes;
m_receivedMessages += numMessages;
}
#else
[Conditional("DEBUG")]
internal void PacketReceived(int numBytes, int numMessages, int numFragments)
{
NetException.Assert(numBytes > 0 && numMessages > 0);
m_receivedPackets++;
m_receivedBytes += numBytes;
m_receivedMessages += numMessages;
m_receivedFragments += numFragments;
}
#endif
#if USE_RELEASE_STATISTICS
internal void MessageResent(MessageResendReason reason)
{
if (reason == MessageResendReason.Delay)
m_resentMessagesDueToDelay++;
else
m_resentMessagesDueToHole++;
}
#else
[Conditional("DEBUG")]
internal void MessageResent(MessageResendReason reason)
{
if (reason == MessageResendReason.Delay)
m_resentMessagesDueToDelay++;
else
m_resentMessagesDueToHole++;
}
#endif
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
StringBuilder bdr = new StringBuilder();
//bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime));
bdr.AppendLine("Current MTU: " + m_connection.m_currentMTU);
bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets");
bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets");
if (m_resentMessagesDueToDelay > 0)
bdr.AppendLine("Resent messages (delay): " + m_resentMessagesDueToDelay);
if (m_resentMessagesDueToDelay > 0)
bdr.AppendLine("Resent messages (holes): " + m_resentMessagesDueToHole);
int numUnsent = 0;
int numStored = 0;
foreach (NetSenderChannelBase sendChan in m_connection.m_sendChannels)
{
if (sendChan == null)
continue;
numUnsent += sendChan.m_queuedSends.Count;
var relSendChan = sendChan as NetReliableSenderChannel;
if (relSendChan != null)
{
for (int i = 0; i < relSendChan.m_storedMessages.Length; i++)
if (relSendChan.m_storedMessages[i].Message != null)
numStored++;
}
}
int numWithheld = 0;
foreach (NetReceiverChannelBase recChan in m_connection.m_receiveChannels)
{
var relRecChan = recChan as NetReliableOrderedReceiver;
if (relRecChan != null)
{
for (int i = 0; i < relRecChan.m_withheldMessages.Length; i++)
if (relRecChan.m_withheldMessages[i] != null)
numWithheld++;
}
}
bdr.AppendLine("Unsent messages: " + numUnsent);
bdr.AppendLine("Stored messages: " + numStored);
bdr.AppendLine("Withheld messages: " + numWithheld);
return bdr.ToString();
}
}
}

View File

@ -0,0 +1,68 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
namespace Lidgren.Network
{
/// <summary>
/// Status for a NetConnection instance
/// </summary>
public enum NetConnectionStatus
{
/// <summary>
/// No connection, or attempt, in place
/// </summary>
None,
/// <summary>
/// Connect has been sent; waiting for ConnectResponse
/// </summary>
InitiatedConnect,
/// <summary>
/// Connect was received, but ConnectResponse hasn't been sent yet
/// </summary>
ReceivedInitiation,
/// <summary>
/// Connect was received and ApprovalMessage released to the application; awaiting Approve() or Deny()
/// </summary>
RespondedAwaitingApproval, // We got Connect, released ApprovalMessage
/// <summary>
/// Connect was received and ConnectResponse has been sent; waiting for ConnectionEstablished
/// </summary>
RespondedConnect, // we got Connect, sent ConnectResponse
/// <summary>
/// Connected
/// </summary>
Connected, // we received ConnectResponse (if initiator) or ConnectionEstablished (if passive)
/// <summary>
/// In the process of disconnecting
/// </summary>
Disconnecting,
/// <summary>
/// Disconnected
/// </summary>
Disconnected
}
}

View File

@ -0,0 +1,57 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
namespace Lidgren.Network
{
/// <summary>
/// All the constants used when compiling the library
/// </summary>
internal static class NetConstants
{
internal const int NumTotalChannels = 99;
internal const int NetChannelsPerDeliveryMethod = 32;
internal const int NumSequenceNumbers = 1024;
internal const int HeaderByteSize = 5;
internal const int UnreliableWindowSize = 128;
internal const int ReliableOrderedWindowSize = 64;
internal const int ReliableSequencedWindowSize = 64;
internal const int DefaultWindowSize = 64;
internal const int MaxFragmentationGroups = ushort.MaxValue - 1;
internal const int UnfragmentedMessageHeaderSize = 5;
/// <summary>
/// Number of channels which needs a sequence number to work
/// </summary>
internal const int NumSequencedChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced1;
/// <summary>
/// Number of reliable channels
/// </summary>
internal const int NumReliableChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserReliableUnordered;
internal const string ConnResetMessage = "Connection was reset by remote host";
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
/// <summary>
/// How the library deals with resends and handling of late messages
/// </summary>
public enum NetDeliveryMethod : byte
{
//
// Actually a publicly visible subset of NetMessageType
//
/// <summary>
/// Indicates an error
/// </summary>
Unknown = 0,
/// <summary>
/// Unreliable, unordered delivery
/// </summary>
Unreliable = 1,
/// <summary>
/// Unreliable delivery, but automatically dropping late messages
/// </summary>
UnreliableSequenced = 2,
/// <summary>
/// Reliable delivery, but unordered
/// </summary>
ReliableUnordered = 34,
/// <summary>
/// Reliable delivery, except for late messages which are dropped
/// </summary>
ReliableSequenced = 35,
/// <summary>
/// Reliable, ordered delivery
/// </summary>
ReliableOrdered = 67,
}
}

View File

@ -0,0 +1,74 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
namespace Lidgren.Network
{
/// <summary>
/// Exception thrown in the Lidgren Network Library
/// </summary>
public sealed class NetException : Exception
{
/// <summary>
/// NetException constructor
/// </summary>
public NetException()
: base()
{
}
/// <summary>
/// NetException constructor
/// </summary>
public NetException(string message)
: base(message)
{
}
/// <summary>
/// NetException constructor
/// </summary>
public NetException(string message, Exception inner)
: base(message, inner)
{
}
/// <summary>
/// Throws an exception, in DEBUG only, if first parameter is false
/// </summary>
[Conditional("DEBUG")]
public static void Assert(bool isOk, string message)
{
if (!isOk)
throw new NetException(message);
}
/// <summary>
/// Throws an exception, in DEBUG only, if first parameter is false
/// </summary>
[Conditional("DEBUG")]
public static void Assert(bool isOk)
{
if (!isOk)
throw new NetException();
}
}
}

View File

@ -0,0 +1,175 @@
using System;
namespace Lidgren.Network
{
internal static class NetFragmentationHelper
{
internal static int WriteHeader(
byte[] destination,
int ptr,
int group,
int totalBits,
int chunkByteSize,
int chunkNumber)
{
uint num1 = (uint)group;
while (num1 >= 0x80)
{
destination[ptr++] = (byte)(num1 | 0x80);
num1 = num1 >> 7;
}
destination[ptr++] = (byte)num1;
// write variable length fragment total bits
uint num2 = (uint)totalBits;
while (num2 >= 0x80)
{
destination[ptr++] = (byte)(num2 | 0x80);
num2 = num2 >> 7;
}
destination[ptr++] = (byte)num2;
// write variable length fragment chunk size
uint num3 = (uint)chunkByteSize;
while (num3 >= 0x80)
{
destination[ptr++] = (byte)(num3 | 0x80);
num3 = num3 >> 7;
}
destination[ptr++] = (byte)num3;
// write variable length fragment chunk number
uint num4 = (uint)chunkNumber;
while (num4 >= 0x80)
{
destination[ptr++] = (byte)(num4 | 0x80);
num4 = num4 >> 7;
}
destination[ptr++] = (byte)num4;
return ptr;
}
internal static int ReadHeader(byte[] buffer, int ptr, out int group, out int totalBits, out int chunkByteSize, out int chunkNumber)
{
int num1 = 0;
int num2 = 0;
while (true)
{
byte num3 = buffer[ptr++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
group = num1;
break;
}
}
num1 = 0;
num2 = 0;
while (true)
{
byte num3 = buffer[ptr++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
totalBits = num1;
break;
}
}
num1 = 0;
num2 = 0;
while (true)
{
byte num3 = buffer[ptr++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
chunkByteSize = num1;
break;
}
}
num1 = 0;
num2 = 0;
while (true)
{
byte num3 = buffer[ptr++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
chunkNumber = num1;
break;
}
}
return ptr;
}
internal static int GetFragmentationHeaderSize(int groupId, int totalBytes, int chunkByteSize, int numChunks)
{
int len = 4;
// write variable length fragment group id
uint num1 = (uint)groupId;
while (num1 >= 0x80)
{
len++;
num1 = num1 >> 7;
}
// write variable length fragment total bits
uint num2 = (uint)(totalBytes * 8);
while (num2 >= 0x80)
{
len++;
num2 = num2 >> 7;
}
// write variable length fragment chunk byte size
uint num3 = (uint)chunkByteSize;
while (num3 >= 0x80)
{
len++;
num3 = num3 >> 7;
}
// write variable length fragment chunk number
uint num4 = (uint)numChunks;
while (num4 >= 0x80)
{
len++;
num4 = num4 >> 7;
}
return len;
}
internal static int GetBestChunkSize(int group, int totalBytes, int mtu)
{
int tryChunkSize = mtu - NetConstants.HeaderByteSize - 4; // naive approximation
int est = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, totalBytes / tryChunkSize);
tryChunkSize = mtu - NetConstants.HeaderByteSize - est; // slightly less naive approximation
int headerSize = 0;
do
{
tryChunkSize--; // keep reducing chunk size until it fits within MTU including header
int numChunks = totalBytes / tryChunkSize;
if (numChunks * tryChunkSize < totalBytes)
numChunks++;
headerSize = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, numChunks); // 4+ bytes
} while (tryChunkSize + headerSize + NetConstants.HeaderByteSize + 1 >= mtu);
return tryChunkSize;
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Lidgren.Network
{
public sealed class NetFragmentationInfo
{
public int TotalFragmentCount;
public bool[] Received;
public int TotalReceived;
public int FragmentSize;
}
}

View File

@ -0,0 +1,115 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Net;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Incoming message either sent from a remote peer or generated within the library
/// </summary>
[DebuggerDisplay("Type={MessageType} LengthBits={LengthBits}")]
public sealed class NetIncomingMessage : NetBuffer
{
internal NetIncomingMessageType m_incomingMessageType;
internal IPEndPoint m_senderEndPoint;
internal NetConnection m_senderConnection;
internal int m_sequenceNumber;
internal NetMessageType m_receivedMessageType;
internal bool m_isFragment;
internal double m_receiveTime;
/// <summary>
/// Gets the type of this incoming message
/// </summary>
public NetIncomingMessageType MessageType { get { return m_incomingMessageType; } }
/// <summary>
/// Gets the delivery method this message was sent with (if user data)
/// </summary>
public NetDeliveryMethod DeliveryMethod { get { return NetUtility.GetDeliveryMethod(m_receivedMessageType); } }
/// <summary>
/// Gets the sequence channel this message was sent with (if user data)
/// </summary>
public int SequenceChannel { get { return (int)m_receivedMessageType - (int)NetUtility.GetDeliveryMethod(m_receivedMessageType); } }
/// <summary>
/// IPEndPoint of sender, if any
/// </summary>
public IPEndPoint SenderEndPoint { get { return m_senderEndPoint; } }
/// <summary>
/// NetConnection of sender, if any
/// </summary>
public NetConnection SenderConnection { get { return m_senderConnection; } }
/// <summary>
/// What local time the message was received from the network
/// </summary>
public double ReceiveTime { get { return m_receiveTime; } }
internal NetIncomingMessage()
{
}
internal NetIncomingMessage(NetIncomingMessageType tp)
{
m_incomingMessageType = tp;
}
internal void Reset()
{
m_incomingMessageType = NetIncomingMessageType.Error;
m_readPosition = 0;
m_receivedMessageType = NetMessageType.LibraryError;
m_senderConnection = null;
m_bitLength = 0;
m_isFragment = false;
}
/// <summary>
/// Decrypt a message
/// </summary>
/// <param name="encryption">The encryption algorithm used to encrypt the message</param>
/// <returns>true on success</returns>
public bool Decrypt(NetEncryption encryption)
{
return encryption.Decrypt(this);
}
/// <summary>
/// Reads a value, in local time comparable to NetTime.Now, written using WriteTime()
/// Must have a connected sender
/// </summary>
public double ReadTime(bool highPrecision)
{
return ReadTime(m_senderConnection, highPrecision);
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetIncomingMessage #" + m_sequenceNumber + " " + this.LengthBytes + " bytes]";
}
}
}

View File

@ -0,0 +1,105 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Diagnostics.CodeAnalysis;
namespace Lidgren.Network
{
/// <summary>
/// The type of a NetIncomingMessage
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
public enum NetIncomingMessageType
{
//
// library note: values are power-of-two, but they are not flags - it's a convenience for NetPeerConfiguration.DisabledMessageTypes
//
/// <summary>
/// Error; this value should never appear
/// </summary>
Error = 0,
/// <summary>
/// Status for a connection changed
/// </summary>
StatusChanged = 1 << 0, // Data (string)
/// <summary>
/// Data sent using SendUnconnectedMessage
/// </summary>
UnconnectedData = 1 << 1, // Data Based on data received
/// <summary>
/// Connection approval is needed
/// </summary>
ConnectionApproval = 1 << 2, // Data
/// <summary>
/// Application data
/// </summary>
Data = 1 << 3, // Data Based on data received
/// <summary>
/// Receipt of delivery
/// </summary>
Receipt = 1 << 4, // Data
/// <summary>
/// Discovery request for a response
/// </summary>
DiscoveryRequest = 1 << 5, // (no data)
/// <summary>
/// Discovery response to a request
/// </summary>
DiscoveryResponse = 1 << 6, // Data
/// <summary>
/// Verbose debug message
/// </summary>
VerboseDebugMessage = 1 << 7, // Data (string)
/// <summary>
/// Debug message
/// </summary>
DebugMessage = 1 << 8, // Data (string)
/// <summary>
/// Warning message
/// </summary>
WarningMessage = 1 << 9, // Data (string)
/// <summary>
/// Error message
/// </summary>
ErrorMessage = 1 << 10, // Data (string)
/// <summary>
/// NAT introduction was successful
/// </summary>
NatIntroductionSuccess = 1 << 11, // Data (as passed to master server)
/// <summary>
/// A roundtrip was measured and NetConnection.AverageRoundtripTime was updated
/// </summary>
ConnectionLatencyUpdated = 1 << 12, // Seconds as a Single
}
}

View File

@ -0,0 +1,175 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
namespace Lidgren.Network
{
internal enum NetMessageType : byte
{
Unconnected = 0,
UserUnreliable = 1,
UserSequenced1 = 2,
UserSequenced2 = 3,
UserSequenced3 = 4,
UserSequenced4 = 5,
UserSequenced5 = 6,
UserSequenced6 = 7,
UserSequenced7 = 8,
UserSequenced8 = 9,
UserSequenced9 = 10,
UserSequenced10 = 11,
UserSequenced11 = 12,
UserSequenced12 = 13,
UserSequenced13 = 14,
UserSequenced14 = 15,
UserSequenced15 = 16,
UserSequenced16 = 17,
UserSequenced17 = 18,
UserSequenced18 = 19,
UserSequenced19 = 20,
UserSequenced20 = 21,
UserSequenced21 = 22,
UserSequenced22 = 23,
UserSequenced23 = 24,
UserSequenced24 = 25,
UserSequenced25 = 26,
UserSequenced26 = 27,
UserSequenced27 = 28,
UserSequenced28 = 29,
UserSequenced29 = 30,
UserSequenced30 = 31,
UserSequenced31 = 32,
UserSequenced32 = 33,
UserReliableUnordered = 34,
UserReliableSequenced1 = 35,
UserReliableSequenced2 = 36,
UserReliableSequenced3 = 37,
UserReliableSequenced4 = 38,
UserReliableSequenced5 = 39,
UserReliableSequenced6 = 40,
UserReliableSequenced7 = 41,
UserReliableSequenced8 = 42,
UserReliableSequenced9 = 43,
UserReliableSequenced10 = 44,
UserReliableSequenced11 = 45,
UserReliableSequenced12 = 46,
UserReliableSequenced13 = 47,
UserReliableSequenced14 = 48,
UserReliableSequenced15 = 49,
UserReliableSequenced16 = 50,
UserReliableSequenced17 = 51,
UserReliableSequenced18 = 52,
UserReliableSequenced19 = 53,
UserReliableSequenced20 = 54,
UserReliableSequenced21 = 55,
UserReliableSequenced22 = 56,
UserReliableSequenced23 = 57,
UserReliableSequenced24 = 58,
UserReliableSequenced25 = 59,
UserReliableSequenced26 = 60,
UserReliableSequenced27 = 61,
UserReliableSequenced28 = 62,
UserReliableSequenced29 = 63,
UserReliableSequenced30 = 64,
UserReliableSequenced31 = 65,
UserReliableSequenced32 = 66,
UserReliableOrdered1 = 67,
UserReliableOrdered2 = 68,
UserReliableOrdered3 = 69,
UserReliableOrdered4 = 70,
UserReliableOrdered5 = 71,
UserReliableOrdered6 = 72,
UserReliableOrdered7 = 73,
UserReliableOrdered8 = 74,
UserReliableOrdered9 = 75,
UserReliableOrdered10 = 76,
UserReliableOrdered11 = 77,
UserReliableOrdered12 = 78,
UserReliableOrdered13 = 79,
UserReliableOrdered14 = 80,
UserReliableOrdered15 = 81,
UserReliableOrdered16 = 82,
UserReliableOrdered17 = 83,
UserReliableOrdered18 = 84,
UserReliableOrdered19 = 85,
UserReliableOrdered20 = 86,
UserReliableOrdered21 = 87,
UserReliableOrdered22 = 88,
UserReliableOrdered23 = 89,
UserReliableOrdered24 = 90,
UserReliableOrdered25 = 91,
UserReliableOrdered26 = 92,
UserReliableOrdered27 = 93,
UserReliableOrdered28 = 94,
UserReliableOrdered29 = 95,
UserReliableOrdered30 = 96,
UserReliableOrdered31 = 97,
UserReliableOrdered32 = 98,
Unused1 = 99,
Unused2 = 100,
Unused3 = 101,
Unused4 = 102,
Unused5 = 103,
Unused6 = 104,
Unused7 = 105,
Unused8 = 106,
Unused9 = 107,
Unused10 = 108,
Unused11 = 109,
Unused12 = 110,
Unused13 = 111,
Unused14 = 112,
Unused15 = 113,
Unused16 = 114,
Unused17 = 115,
Unused18 = 116,
Unused19 = 117,
Unused20 = 118,
Unused21 = 119,
Unused22 = 120,
Unused23 = 121,
Unused24 = 122,
Unused25 = 123,
Unused26 = 124,
Unused27 = 125,
Unused28 = 126,
Unused29 = 127,
LibraryError = 128,
Ping = 129, // used for RTT calculation
Pong = 130, // used for RTT calculation
Connect = 131,
ConnectResponse = 132,
ConnectionEstablished = 133,
Acknowledge = 134,
Disconnect = 135,
Discovery = 136,
DiscoveryResponse = 137,
NatPunchMessage = 138, // send between peers
NatIntroduction = 139, // send to master server
ExpandMTURequest = 140,
ExpandMTUSuccess = 141,
}
}

View File

@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace Lidgren.Network
{
public partial class NetPeer
{
/// <summary>
/// Send NetIntroduction to hostExternal and clientExternal; introducing client to host
/// </summary>
public void Introduce(
IPEndPoint hostInternal,
IPEndPoint hostExternal,
IPEndPoint clientInternal,
IPEndPoint clientExternal,
string token)
{
// send message to client
NetOutgoingMessage msg = CreateMessage(10 + token.Length + 1);
msg.m_messageType = NetMessageType.NatIntroduction;
msg.Write((byte)0);
msg.Write(hostInternal);
msg.Write(hostExternal);
msg.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(clientExternal, msg));
// send message to host
msg = CreateMessage(10 + token.Length + 1);
msg.m_messageType = NetMessageType.NatIntroduction;
msg.Write((byte)1);
msg.Write(clientInternal);
msg.Write(clientExternal);
msg.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(hostExternal, msg));
}
/// <summary>
/// Called when host/client receives a NatIntroduction message from a master server
/// </summary>
internal void HandleNatIntroduction(int ptr)
{
VerifyNetworkThread();
// read intro
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
byte hostByte = tmp.ReadByte();
IPEndPoint remoteInternal = tmp.ReadIPEndPoint();
IPEndPoint remoteExternal = tmp.ReadIPEndPoint();
string token = tmp.ReadString();
bool isHost = (hostByte != 0);
LogDebug("NAT introduction received; we are designated " + (isHost ? "host" : "client"));
NetOutgoingMessage punch;
if (!isHost && m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess) == false)
return; // no need to punch - we're not listening for nat intros!
// send internal punch
punch = CreateMessage(1);
punch.m_messageType = NetMessageType.NatPunchMessage;
punch.Write(hostByte);
punch.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(remoteInternal, punch));
LogDebug("NAT punch sent to " + remoteInternal);
// send external punch
punch = CreateMessage(1);
punch.m_messageType = NetMessageType.NatPunchMessage;
punch.Write(hostByte);
punch.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(remoteExternal, punch));
LogDebug("NAT punch sent to " + remoteExternal);
}
/// <summary>
/// Called when receiving a NatPunchMessage from a remote endpoint
/// </summary>
private void HandleNatPunch(int ptr, IPEndPoint senderEndPoint)
{
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
byte fromHostByte = tmp.ReadByte();
if (fromHostByte == 0)
{
// it's from client
LogDebug("NAT punch received from " + senderEndPoint + " we're host, so we ignore this");
return; // don't alert hosts about nat punch successes; only clients
}
string token = tmp.ReadString();
LogDebug("NAT punch received from " + senderEndPoint + " we're client, so we've succeeded - token is " + token);
//
// Release punch success to client; enabling him to Connect() to msg.SenderIPEndPoint if token is ok
//
NetIncomingMessage punchSuccess = CreateIncomingMessage(NetIncomingMessageType.NatIntroductionSuccess, 10);
punchSuccess.m_senderEndPoint = senderEndPoint;
punchSuccess.Write(token);
ReleaseMessage(punchSuccess);
// send a return punch just for good measure
var punch = CreateMessage(1);
punch.m_messageType = NetMessageType.NatPunchMessage;
punch.Write((byte)0);
punch.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(senderEndPoint, punch));
}
}
}

View File

@ -0,0 +1,135 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Outgoing message used to send data to remote peer(s)
/// </summary>
[DebuggerDisplay("LengthBits={LengthBits}")]
public sealed class NetOutgoingMessage : NetBuffer
{
internal NetMessageType m_messageType;
internal bool m_isSent;
internal int m_recyclingCount;
internal int m_fragmentGroup; // which group of fragments ths belongs to
internal int m_fragmentGroupTotalBits; // total number of bits in this group
internal int m_fragmentChunkByteSize; // size, in bytes, of every chunk but the last one
internal int m_fragmentChunkNumber; // which number chunk this is, starting with 0
internal NetOutgoingMessage()
{
}
internal void Reset()
{
m_messageType = NetMessageType.LibraryError;
m_bitLength = 0;
m_isSent = false;
m_recyclingCount = 0;
m_fragmentGroup = 0;
}
internal int Encode(byte[] intoBuffer, int ptr, int sequenceNumber)
{
// 8 bits - NetMessageType
// 1 bit - Fragment?
// 15 bits - Sequence number
// 16 bits - Payload length in bits
intoBuffer[ptr++] = (byte)m_messageType;
byte low = (byte)((sequenceNumber << 1) | (m_fragmentGroup == 0 ? 0 : 1));
intoBuffer[ptr++] = low;
intoBuffer[ptr++] = (byte)(sequenceNumber >> 7);
if (m_fragmentGroup == 0)
{
intoBuffer[ptr++] = (byte)m_bitLength;
intoBuffer[ptr++] = (byte)(m_bitLength >> 8);
int byteLen = NetUtility.BytesToHoldBits(m_bitLength);
if (byteLen > 0)
{
Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen);
ptr += byteLen;
}
}
else
{
int wasPtr = ptr;
intoBuffer[ptr++] = (byte)m_bitLength;
intoBuffer[ptr++] = (byte)(m_bitLength >> 8);
//
// write fragmentation header
//
ptr = NetFragmentationHelper.WriteHeader(intoBuffer, ptr, m_fragmentGroup, m_fragmentGroupTotalBits, m_fragmentChunkByteSize, m_fragmentChunkNumber);
int hdrLen = ptr - wasPtr - 2;
// update length
int realBitLength = m_bitLength + (hdrLen * 8);
intoBuffer[wasPtr] = (byte)realBitLength;
intoBuffer[wasPtr + 1] = (byte)(realBitLength >> 8);
int byteLen = NetUtility.BytesToHoldBits(m_bitLength);
if (byteLen > 0)
{
Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen);
ptr += byteLen;
}
}
NetException.Assert(ptr > 0);
return ptr;
}
internal int GetEncodedSize()
{
int retval = NetConstants.UnfragmentedMessageHeaderSize; // regular headers
if (m_fragmentGroup != 0)
retval += NetFragmentationHelper.GetFragmentationHeaderSize(m_fragmentGroup, m_fragmentGroupTotalBits / 8, m_fragmentChunkByteSize, m_fragmentChunkNumber);
retval += this.LengthBytes;
return retval;
}
/// <summary>
/// Encrypt this message using the provided algorithm; no more writing can be done before sending it or the message will be corrupt!
/// </summary>
public bool Encrypt(NetEncryption encryption)
{
return encryption.Encrypt(this);
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
if (m_isSent)
return "[NetOutgoingMessage " + m_messageType + " " + this.LengthBytes + " bytes]";
return "[NetOutgoingMessage " + this.LengthBytes + " bytes]";
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Net;
namespace Lidgren.Network
{
public partial class NetPeer
{
/// <summary>
/// Emit a discovery signal to all hosts on your subnet
/// </summary>
public void DiscoverLocalPeers(int serverPort)
{
NetOutgoingMessage om = CreateMessage(0);
om.m_messageType = NetMessageType.Discovery;
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(new IPEndPoint(IPAddress.Broadcast, serverPort), om));
}
/// <summary>
/// Emit a discovery signal to a single known host
/// </summary>
public bool DiscoverKnownPeer(string host, int serverPort)
{
IPAddress address = NetUtility.Resolve(host);
if (address == null)
return false;
DiscoverKnownPeer(new IPEndPoint(address, serverPort));
return true;
}
/// <summary>
/// Emit a discovery signal to a single known host
/// </summary>
public void DiscoverKnownPeer(IPEndPoint endPoint)
{
NetOutgoingMessage om = CreateMessage(0);
om.m_messageType = NetMessageType.Discovery;
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(endPoint, om));
}
/// <summary>
/// Send a discovery response message
/// </summary>
public void SendDiscoveryResponse(NetOutgoingMessage msg, IPEndPoint recipient)
{
if (recipient == null)
throw new ArgumentNullException("recipient");
if (msg == null)
msg = CreateMessage(0);
else if (msg.m_isSent)
throw new NetException("Message has already been sent!");
if (msg.LengthBytes >= m_configuration.MaximumTransmissionUnit)
throw new NetException("Cannot send discovery message larger than MTU (currently " + m_configuration.MaximumTransmissionUnit + " bytes)");
msg.m_messageType = NetMessageType.DiscoveryResponse;
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(recipient, msg));
}
}
}

View File

@ -0,0 +1,167 @@
using System;
using System.Threading;
using System.Collections.Generic;
namespace Lidgren.Network
{
internal class ReceivedFragmentGroup
{
//public float LastReceived;
public byte[] Data;
public NetBitVector ReceivedChunks;
}
public partial class NetPeer
{
private int m_lastUsedFragmentGroup;
private Dictionary<NetConnection, Dictionary<int, ReceivedFragmentGroup>> m_receivedFragmentGroups;
// on user thread
private NetSendResult SendFragmentedMessage(NetOutgoingMessage msg, IList<NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel)
{
// Note: this group id is PER SENDING/NetPeer; ie. same id is sent to all recipients;
// this should be ok however; as long as recipients differentiate between same id but different sender
int group = Interlocked.Increment(ref m_lastUsedFragmentGroup);
if (group >= NetConstants.MaxFragmentationGroups)
{
// @TODO: not thread safe; but in practice probably not an issue
m_lastUsedFragmentGroup = 1;
group = 1;
}
msg.m_fragmentGroup = group;
// do not send msg; but set fragmentgroup in case user tries to recycle it immediately
// create fragmentation specifics
int totalBytes = msg.LengthBytes;
// determine minimum mtu for all recipients
int mtu = GetMTU(recipients);
int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu);
int numChunks = totalBytes / bytesPerChunk;
if (numChunks * bytesPerChunk < totalBytes)
numChunks++;
NetSendResult retval = NetSendResult.Sent;
int bitsPerChunk = bytesPerChunk * 8;
int bitsLeft = msg.LengthBits;
for (int i = 0; i < numChunks; i++)
{
NetOutgoingMessage chunk = CreateMessage(0);
chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft);
chunk.m_data = msg.m_data;
chunk.m_fragmentGroup = group;
chunk.m_fragmentGroupTotalBits = totalBytes * 8;
chunk.m_fragmentChunkByteSize = bytesPerChunk;
chunk.m_fragmentChunkNumber = i;
NetException.Assert(chunk.m_bitLength != 0);
NetException.Assert(chunk.GetEncodedSize() < mtu);
Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count);
foreach (NetConnection recipient in recipients)
{
var res = recipient.EnqueueMessage(chunk, method, sequenceChannel);
if ((int)res > (int)retval)
retval = res; // return "worst" result
}
bitsLeft -= bitsPerChunk;
}
return retval;
}
private void HandleReleasedFragment(NetIncomingMessage im)
{
VerifyNetworkThread();
//
// read fragmentation header and combine fragments
//
int group;
int totalBits;
int chunkByteSize;
int chunkNumber;
int ptr = NetFragmentationHelper.ReadHeader(
im.m_data, 0,
out group,
out totalBits,
out chunkByteSize,
out chunkNumber
);
NetException.Assert(im.LengthBytes > ptr);
NetException.Assert(group > 0);
NetException.Assert(totalBits > 0);
NetException.Assert(chunkByteSize > 0);
int totalBytes = NetUtility.BytesToHoldBits((int)totalBits);
int totalNumChunks = totalBytes / chunkByteSize;
if (totalNumChunks * chunkByteSize < totalBytes)
totalNumChunks++;
NetException.Assert(chunkNumber < totalNumChunks);
if (chunkNumber >= totalNumChunks)
{
LogWarning("Index out of bounds for chunk " + chunkNumber + " (total chunks " + totalNumChunks + ")");
return;
}
Dictionary<int, ReceivedFragmentGroup> groups;
if (!m_receivedFragmentGroups.TryGetValue(im.SenderConnection, out groups))
{
groups = new Dictionary<int, ReceivedFragmentGroup>();
m_receivedFragmentGroups[im.SenderConnection] = groups;
}
ReceivedFragmentGroup info;
if (!groups.TryGetValue(group, out info))
{
info = new ReceivedFragmentGroup();
info.Data = new byte[totalBytes];
info.ReceivedChunks = new NetBitVector(totalNumChunks);
groups[group] = info;
}
info.ReceivedChunks[chunkNumber] = true;
//info.LastReceived = (float)NetTime.Now;
// copy to data
int offset = (chunkNumber * chunkByteSize);
Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr);
int cnt = info.ReceivedChunks.Count();
//LogVerbose("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")");
LogVerbose("Received fragment " + chunkNumber + " of " + totalNumChunks + " (" + cnt + " chunks received)");
if (info.ReceivedChunks.Count() == totalNumChunks)
{
// Done! Transform this incoming message
im.m_data = info.Data;
im.m_bitLength = (int)totalBits;
im.m_isFragment = false;
LogVerbose("Fragment group #" + group + " fully received in " + totalNumChunks + " chunks (" + totalBits + " bits)");
groups.Remove(group);
ReleaseMessage(im);
}
else
{
// data has been copied; recycle this incoming message
Recycle(im);
}
return;
}
}
}

View File

@ -0,0 +1,762 @@
#if !__ANDROID__ && !IOS && !UNITY_WEBPLAYER && !UNITY_ANDROID && !UNITY_IPHONE
#define IS_MAC_AVAILABLE
#endif
using System;
using System.Net;
using System.Threading;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Net.Sockets;
using System.Collections.Generic;
namespace Lidgren.Network
{
public partial class NetPeer
{
private NetPeerStatus m_status;
private Thread m_networkThread;
private Socket m_socket;
internal byte[] m_sendBuffer;
internal byte[] m_receiveBuffer;
internal NetIncomingMessage m_readHelperMessage;
private EndPoint m_senderRemote;
private object m_initializeLock = new object();
private uint m_frameCounter;
private double m_lastHeartbeat;
private double m_lastSocketBind = float.MinValue;
private NetUPnP m_upnp;
internal readonly NetPeerConfiguration m_configuration;
private readonly NetQueue<NetIncomingMessage> m_releasedIncomingMessages;
internal readonly NetQueue<NetTuple<IPEndPoint, NetOutgoingMessage>> m_unsentUnconnectedMessages;
internal Dictionary<IPEndPoint, NetConnection> m_handshakes;
internal readonly NetPeerStatistics m_statistics;
internal long m_uniqueIdentifier;
internal bool m_executeFlushSendQueue;
private AutoResetEvent m_messageReceivedEvent;
private List<NetTuple<SynchronizationContext, SendOrPostCallback>> m_receiveCallbacks;
/// <summary>
/// Gets the socket, if Start() has been called
/// </summary>
public Socket Socket { get { return m_socket; } }
/// <summary>
/// Call this to register a callback for when a new message arrives
/// </summary>
public void RegisterReceivedCallback(SendOrPostCallback callback, SynchronizationContext syncContext = null)
{
if (syncContext == null)
syncContext = SynchronizationContext.Current;
if (syncContext == null)
throw new NetException("Need a SynchronizationContext to register callback on correct thread!");
if (m_receiveCallbacks == null)
m_receiveCallbacks = new List<NetTuple<SynchronizationContext, SendOrPostCallback>>();
m_receiveCallbacks.Add(new NetTuple<SynchronizationContext, SendOrPostCallback>(syncContext, callback));
}
/// <summary>
/// Call this to unregister a callback, but remember to do it in the same synchronization context!
/// </summary>
public void UnregisterReceivedCallback(SendOrPostCallback callback)
{
if (m_receiveCallbacks == null)
return;
// remove all callbacks regardless of sync context
RestartRemoveCallbacks:
for (int i = 0; i < m_receiveCallbacks.Count; i++)
{
if (m_receiveCallbacks[i].Item2.Equals(callback))
{
m_receiveCallbacks.RemoveAt(i);
goto RestartRemoveCallbacks;
}
}
if (m_receiveCallbacks.Count < 1)
m_receiveCallbacks = null;
}
internal void ReleaseMessage(NetIncomingMessage msg)
{
NetException.Assert(msg.m_incomingMessageType != NetIncomingMessageType.Error);
if (msg.m_isFragment)
{
HandleReleasedFragment(msg);
return;
}
m_releasedIncomingMessages.Enqueue(msg);
if (m_messageReceivedEvent != null)
m_messageReceivedEvent.Set();
if (m_receiveCallbacks != null)
{
foreach (var tuple in m_receiveCallbacks)
{
try
{
tuple.Item1.Post(tuple.Item2, this);
}
catch (Exception ex)
{
LogWarning("Receive callback exception:" + ex);
}
}
}
}
private void BindSocket(bool reBind)
{
double now = NetTime.Now;
if (now - m_lastSocketBind < 1.0)
{
LogDebug("Suppressed socket rebind; last bound " + (now - m_lastSocketBind) + " seconds ago");
return; // only allow rebind once every second
}
m_lastSocketBind = now;
if (m_socket == null)
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
if (reBind)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, (int)1);
m_socket.ReceiveBufferSize = m_configuration.ReceiveBufferSize;
m_socket.SendBufferSize = m_configuration.SendBufferSize;
m_socket.Blocking = false;
var ep = (EndPoint)new IPEndPoint(m_configuration.LocalAddress, reBind ? m_listenPort : m_configuration.Port);
m_socket.Bind(ep);
try
{
const uint IOC_IN = 0x80000000;
const uint IOC_VENDOR = 0x18000000;
uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
m_socket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
}
catch
{
// ignore; SIO_UDP_CONNRESET not supported on this platform
}
IPEndPoint boundEp = m_socket.LocalEndPoint as IPEndPoint;
LogDebug("Socket bound to " + boundEp + ": " + m_socket.IsBound);
m_listenPort = boundEp.Port;
}
private void InitializeNetwork()
{
lock (m_initializeLock)
{
m_configuration.Lock();
if (m_status == NetPeerStatus.Running)
return;
if (m_configuration.m_enableUPnP)
m_upnp = new NetUPnP(this);
InitializePools();
m_releasedIncomingMessages.Clear();
m_unsentUnconnectedMessages.Clear();
m_handshakes.Clear();
// bind to socket
BindSocket(false);
m_receiveBuffer = new byte[m_configuration.ReceiveBufferSize];
m_sendBuffer = new byte[m_configuration.SendBufferSize];
m_readHelperMessage = new NetIncomingMessage(NetIncomingMessageType.Error);
m_readHelperMessage.m_data = m_receiveBuffer;
byte[] macBytes = new byte[8];
MWCRandom.Instance.NextBytes(macBytes);
#if IS_MAC_AVAILABLE
try
{
System.Net.NetworkInformation.PhysicalAddress pa = NetUtility.GetMacAddress();
if (pa != null)
{
macBytes = pa.GetAddressBytes();
LogVerbose("Mac address is " + NetUtility.ToHexString(macBytes));
}
else
{
LogWarning("Failed to get Mac address");
}
}
catch (NotSupportedException)
{
// not supported; lets just keep the random bytes set above
}
#endif
IPEndPoint boundEp = m_socket.LocalEndPoint as IPEndPoint;
byte[] epBytes = BitConverter.GetBytes(boundEp.GetHashCode());
byte[] combined = new byte[epBytes.Length + macBytes.Length];
Array.Copy(epBytes, 0, combined, 0, epBytes.Length);
Array.Copy(macBytes, 0, combined, epBytes.Length, macBytes.Length);
m_uniqueIdentifier = BitConverter.ToInt64(NetUtility.CreateSHA1Hash(combined), 0);
m_status = NetPeerStatus.Running;
}
}
private void NetworkLoop()
{
VerifyNetworkThread();
LogDebug("Network thread started");
//
// Network loop
//
do
{
try
{
Heartbeat();
}
catch (Exception ex)
{
LogWarning(ex.ToString());
}
} while (m_status == NetPeerStatus.Running);
//
// perform shutdown
//
ExecutePeerShutdown();
}
private void ExecutePeerShutdown()
{
VerifyNetworkThread();
LogDebug("Shutting down...");
// disconnect and make one final heartbeat
var list = new List<NetConnection>(m_handshakes.Count + m_connections.Count);
lock (m_connections)
{
foreach (var conn in m_connections)
if (conn != null)
list.Add(conn);
lock (m_handshakes)
{
foreach (var hs in m_handshakes.Values)
if (hs != null)
list.Add(hs);
// shut down connections
foreach (NetConnection conn in list)
conn.Shutdown(m_shutdownReason);
}
}
FlushDelayedPackets();
// one final heartbeat, will send stuff and do disconnect
Heartbeat();
Thread.Sleep(10);
lock (m_initializeLock)
{
try
{
if (m_socket != null)
{
try
{
m_socket.Shutdown(SocketShutdown.Receive);
}
catch(Exception ex)
{
LogDebug("Socket.Shutdown exception: " + ex.ToString());
}
try
{
m_socket.Close(2); // 2 seconds timeout
}
catch (Exception ex)
{
LogDebug("Socket.Close exception: " + ex.ToString());
}
}
}
finally
{
m_socket = null;
m_status = NetPeerStatus.NotRunning;
LogDebug("Shutdown complete");
// wake up any threads waiting for server shutdown
if (m_messageReceivedEvent != null)
m_messageReceivedEvent.Set();
}
m_lastSocketBind = float.MinValue;
m_receiveBuffer = null;
m_sendBuffer = null;
m_unsentUnconnectedMessages.Clear();
m_connections.Clear();
m_connectionLookup.Clear();
m_handshakes.Clear();
}
return;
}
private void Heartbeat()
{
VerifyNetworkThread();
double dnow = NetTime.Now;
float now = (float)dnow;
double delta = dnow - m_lastHeartbeat;
int maxCHBpS = 1250 - m_connections.Count;
if (maxCHBpS < 250)
maxCHBpS = 250;
if (delta > (1.0 / (double)maxCHBpS) || delta < 0.0) // max connection heartbeats/second max
{
m_frameCounter++;
m_lastHeartbeat = dnow;
// do handshake heartbeats
if ((m_frameCounter % 3) == 0)
{
foreach (var kvp in m_handshakes)
{
NetConnection conn = kvp.Value as NetConnection;
#if DEBUG
// sanity check
if (kvp.Key != kvp.Key)
LogWarning("Sanity fail! Connection in handshake list under wrong key!");
#endif
conn.UnconnectedHeartbeat(now);
if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Disconnected)
{
#if DEBUG
// sanity check
if (conn.m_status == NetConnectionStatus.Disconnected && m_handshakes.ContainsKey(conn.RemoteEndPoint))
{
LogWarning("Sanity fail! Handshakes list contained disconnected connection!");
m_handshakes.Remove(conn.RemoteEndPoint);
}
#endif
break; // collection has been modified
}
}
}
#if DEBUG
SendDelayedPackets();
#endif
// update m_executeFlushSendQueue
if (m_configuration.m_autoFlushSendQueue)
m_executeFlushSendQueue = true;
// do connection heartbeats
lock (m_connections)
{
foreach (NetConnection conn in m_connections)
{
conn.Heartbeat(now, m_frameCounter);
if (conn.m_status == NetConnectionStatus.Disconnected)
{
//
// remove connection
//
m_connections.Remove(conn);
m_connectionLookup.Remove(conn.RemoteEndPoint);
break; // can't continue iteration here
}
}
}
m_executeFlushSendQueue = false;
// send unsent unconnected messages
NetTuple<IPEndPoint, NetOutgoingMessage> unsent;
while (m_unsentUnconnectedMessages.TryDequeue(out unsent))
{
NetOutgoingMessage om = unsent.Item2;
bool connReset;
int len = om.Encode(m_sendBuffer, 0, 0);
SendPacket(len, unsent.Item1, 1, out connReset);
Interlocked.Decrement(ref om.m_recyclingCount);
if (om.m_recyclingCount <= 0)
Recycle(om);
}
}
//
// read from socket
//
if (m_socket == null)
return;
if (!m_socket.Poll(1000, SelectMode.SelectRead)) // wait up to 1 ms for data to arrive
return;
//if (m_socket == null || m_socket.Available < 1)
// return;
// update now
dnow = NetTime.Now;
now = (float)dnow;
do
{
int bytesReceived = 0;
try
{
bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
}
catch (SocketException sx)
{
switch (sx.SocketErrorCode)
{
case SocketError.ConnectionReset:
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
// we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?!
// So, what to do?
LogWarning("ConnectionReset");
return;
case SocketError.NotConnected:
// socket is unbound; try to rebind it (happens on mobile when process goes to sleep)
BindSocket(true);
return;
default:
LogWarning("Socket exception: " + sx.ToString());
return;
}
}
if (bytesReceived < NetConstants.HeaderByteSize)
return;
//LogVerbose("Received " + bytesReceived + " bytes");
IPEndPoint ipsender = (IPEndPoint)m_senderRemote;
if (m_upnp != null && now < m_upnp.m_discoveryResponseDeadline && bytesReceived > 32)
{
// is this an UPnP response?
string resp = System.Text.Encoding.ASCII.GetString(m_receiveBuffer, 0, bytesReceived);
if (resp.Contains("upnp:rootdevice") || resp.Contains("UPnP/1.0"))
{
try
{
resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
m_upnp.ExtractServiceUrl(resp);
return;
}
catch (Exception ex)
{
LogDebug("Failed to parse UPnP response: " + ex.ToString());
// don't try to parse this packet further
return;
}
}
}
NetConnection sender = null;
m_connectionLookup.TryGetValue(ipsender, out sender);
//
// parse packet into messages
//
int numMessages = 0;
int numFragments = 0;
int ptr = 0;
while ((bytesReceived - ptr) >= NetConstants.HeaderByteSize)
{
// decode header
// 8 bits - NetMessageType
// 1 bit - Fragment?
// 15 bits - Sequence number
// 16 bits - Payload length in bits
numMessages++;
NetMessageType tp = (NetMessageType)m_receiveBuffer[ptr++];
byte low = m_receiveBuffer[ptr++];
byte high = m_receiveBuffer[ptr++];
bool isFragment = ((low & 1) == 1);
ushort sequenceNumber = (ushort)((low >> 1) | (((int)high) << 7));
numFragments++;
ushort payloadBitLength = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8));
int payloadByteLength = NetUtility.BytesToHoldBits(payloadBitLength);
if (bytesReceived - ptr < payloadByteLength)
{
LogWarning("Malformed packet; stated payload length " + payloadByteLength + ", remaining bytes " + (bytesReceived - ptr));
return;
}
if (tp >= NetMessageType.Unused1 && tp <= NetMessageType.Unused29)
{
ThrowOrLog("Unexpected NetMessageType: " + tp);
return;
}
try
{
if (tp >= NetMessageType.LibraryError)
{
if (sender != null)
sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength);
else
ReceivedUnconnectedLibraryMessage(dnow, ipsender, tp, ptr, payloadByteLength);
}
else
{
if (sender == null && !m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData))
return; // dropping unconnected message since it's not enabled
NetIncomingMessage msg = CreateIncomingMessage(NetIncomingMessageType.Data, payloadByteLength);
msg.m_isFragment = isFragment;
msg.m_receiveTime = dnow;
msg.m_sequenceNumber = sequenceNumber;
msg.m_receivedMessageType = tp;
msg.m_senderConnection = sender;
msg.m_senderEndPoint = ipsender;
msg.m_bitLength = payloadBitLength;
Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength);
if (sender != null)
{
if (tp == NetMessageType.Unconnected)
{
// We're connected; but we can still send unconnected messages to this peer
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
ReleaseMessage(msg);
}
else
{
// connected application (non-library) message
sender.ReceivedMessage(msg);
}
}
else
{
// at this point we know the message type is enabled
// unconnected application (non-library) message
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
ReleaseMessage(msg);
}
}
}
catch (Exception ex)
{
LogError("Packet parsing error: " + ex.Message + " from " + ipsender);
}
ptr += payloadByteLength;
}
m_statistics.PacketReceived(bytesReceived, numMessages, numFragments);
if (sender != null)
sender.m_statistics.PacketReceived(bytesReceived, numMessages, numFragments);
} while (m_socket.Available > 0);
}
/// <summary>
/// If NetPeerConfiguration.AutoFlushSendQueue() is false; you need to call this to send all messages queued using SendMessage()
/// </summary>
public void FlushSendQueue()
{
m_executeFlushSendQueue = true;
}
internal void HandleIncomingDiscoveryRequest(double now, IPEndPoint senderEndPoint, int ptr, int payloadByteLength)
{
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryRequest))
{
NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadByteLength);
if (payloadByteLength > 0)
Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadByteLength);
dm.m_receiveTime = now;
dm.m_bitLength = payloadByteLength * 8;
dm.m_senderEndPoint = senderEndPoint;
ReleaseMessage(dm);
}
}
internal void HandleIncomingDiscoveryResponse(double now, IPEndPoint senderEndPoint, int ptr, int payloadByteLength)
{
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryResponse))
{
NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadByteLength);
if (payloadByteLength > 0)
Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadByteLength);
dr.m_receiveTime = now;
dr.m_bitLength = payloadByteLength * 8;
dr.m_senderEndPoint = senderEndPoint;
ReleaseMessage(dr);
}
}
private void ReceivedUnconnectedLibraryMessage(double now, IPEndPoint senderEndPoint, NetMessageType tp, int ptr, int payloadByteLength)
{
NetConnection shake;
if (m_handshakes.TryGetValue(senderEndPoint, out shake))
{
shake.ReceivedHandshake(now, tp, ptr, payloadByteLength);
return;
}
//
// Library message from a completely unknown sender; lets just accept Connect
//
switch (tp)
{
case NetMessageType.Discovery:
HandleIncomingDiscoveryRequest(now, senderEndPoint, ptr, payloadByteLength);
return;
case NetMessageType.DiscoveryResponse:
HandleIncomingDiscoveryResponse(now, senderEndPoint, ptr, payloadByteLength);
return;
case NetMessageType.NatIntroduction:
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess))
HandleNatIntroduction(ptr);
return;
case NetMessageType.NatPunchMessage:
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess))
HandleNatPunch(ptr, senderEndPoint);
return;
case NetMessageType.ConnectResponse:
lock (m_handshakes)
{
foreach (var hs in m_handshakes)
{
if (hs.Key.Address.Equals(senderEndPoint.Address))
{
if (hs.Value.m_connectionInitiator)
{
//
// We are currently trying to connection to XX.XX.XX.XX:Y
// ... but we just received a ConnectResponse from XX.XX.XX.XX:Z
// Lets just assume the router decided to use this port instead
//
var hsconn = hs.Value;
m_connectionLookup.Remove(hs.Key);
m_handshakes.Remove(hs.Key);
LogDebug("Detected host port change; rerouting connection to " + senderEndPoint);
hsconn.MutateEndPoint(senderEndPoint);
m_connectionLookup.Add(senderEndPoint, hsconn);
m_handshakes.Add(senderEndPoint, hsconn);
hsconn.ReceivedHandshake(now, tp, ptr, payloadByteLength);
return;
}
}
}
}
LogWarning("Received unhandled library message " + tp + " from " + senderEndPoint);
return;
case NetMessageType.Connect:
if (m_configuration.AcceptIncomingConnections == false)
{
LogWarning("Received Connect, but we're not accepting incoming connections!");
return;
}
// handle connect
// It's someone wanting to shake hands with us!
int reservedSlots = m_handshakes.Count + m_connections.Count;
if (reservedSlots >= m_configuration.m_maximumConnections)
{
// server full
NetOutgoingMessage full = CreateMessage("Server full");
full.m_messageType = NetMessageType.Disconnect;
SendLibrary(full, senderEndPoint);
return;
}
// Ok, start handshake!
NetConnection conn = new NetConnection(this, senderEndPoint);
conn.m_status = NetConnectionStatus.ReceivedInitiation;
m_handshakes.Add(senderEndPoint, conn);
conn.ReceivedHandshake(now, tp, ptr, payloadByteLength);
return;
case NetMessageType.Disconnect:
// this is probably ok
LogVerbose("Received Disconnect from unconnected source: " + senderEndPoint);
return;
default:
LogWarning("Received unhandled library message " + tp + " from " + senderEndPoint);
return;
}
}
internal void AcceptConnection(NetConnection conn)
{
// LogDebug("Accepted connection " + conn);
conn.InitExpandMTU(NetTime.Now);
if (m_handshakes.Remove(conn.m_remoteEndPoint) == false)
LogWarning("AcceptConnection called but m_handshakes did not contain it!");
lock (m_connections)
{
if (m_connections.Contains(conn))
{
LogWarning("AcceptConnection called but m_connection already contains it!");
}
else
{
m_connections.Add(conn);
m_connectionLookup.Add(conn.m_remoteEndPoint, conn);
}
}
}
[Conditional("DEBUG")]
internal void VerifyNetworkThread()
{
Thread ct = Thread.CurrentThread;
if (Thread.CurrentThread != m_networkThread)
throw new NetException("Executing on wrong thread! Should be library system thread (is " + ct.Name + " mId " + ct.ManagedThreadId + ")");
}
internal NetIncomingMessage SetupReadHelperMessage(int ptr, int payloadLength)
{
VerifyNetworkThread();
m_readHelperMessage.m_bitLength = (ptr + payloadLength) * 8;
m_readHelperMessage.m_readPosition = (ptr * 8);
return m_readHelperMessage;
}
}
}

View File

@ -0,0 +1,308 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
//#define USE_RELEASE_STATISTICS
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
namespace Lidgren.Network
{
public partial class NetPeer
{
#if DEBUG
private readonly List<DelayedPacket> m_delayedPackets = new List<DelayedPacket>();
private class DelayedPacket
{
public byte[] Data;
public double DelayedUntil;
public IPEndPoint Target;
}
internal void SendPacket(int numBytes, IPEndPoint target, int numMessages, out bool connectionReset)
{
connectionReset = false;
// simulate loss
float loss = m_configuration.m_loss;
if (loss > 0.0f)
{
if ((float)MWCRandom.Instance.NextDouble() < loss)
{
LogVerbose("Sending packet " + numBytes + " bytes - SIMULATED LOST!");
return; // packet "lost"
}
}
m_statistics.PacketSent(numBytes, numMessages);
// simulate latency
float m = m_configuration.m_minimumOneWayLatency;
float r = m_configuration.m_randomOneWayLatency;
if (m == 0.0f && r == 0.0f)
{
// no latency simulation
// LogVerbose("Sending packet " + numBytes + " bytes");
bool wasSent = ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset);
// TODO: handle wasSent == false?
if (m_configuration.m_duplicates > 0.0f && MWCRandom.Instance.NextDouble() < m_configuration.m_duplicates)
ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset); // send it again!
return;
}
int num = 1;
if (m_configuration.m_duplicates > 0.0f && MWCRandom.Instance.NextSingle() < m_configuration.m_duplicates)
num++;
float delay = 0;
for (int i = 0; i < num; i++)
{
delay = m_configuration.m_minimumOneWayLatency + (MWCRandom.Instance.NextSingle() * m_configuration.m_randomOneWayLatency);
// Enqueue delayed packet
DelayedPacket p = new DelayedPacket();
p.Target = target;
p.Data = new byte[numBytes];
Buffer.BlockCopy(m_sendBuffer, 0, p.Data, 0, numBytes);
p.DelayedUntil = NetTime.Now + delay;
m_delayedPackets.Add(p);
}
// LogVerbose("Sending packet " + numBytes + " bytes - delayed " + NetTime.ToReadable(delay));
}
private void SendDelayedPackets()
{
if (m_delayedPackets.Count <= 0)
return;
double now = NetTime.Now;
bool connectionReset;
RestartDelaySending:
foreach (DelayedPacket p in m_delayedPackets)
{
if (now > p.DelayedUntil)
{
ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset);
m_delayedPackets.Remove(p);
goto RestartDelaySending;
}
}
}
private void FlushDelayedPackets()
{
try
{
bool connectionReset;
foreach (DelayedPacket p in m_delayedPackets)
ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset);
m_delayedPackets.Clear();
}
catch { }
}
internal bool ActuallySendPacket(byte[] data, int numBytes, IPEndPoint target, out bool connectionReset)
{
connectionReset = false;
try
{
// TODO: refactor this check outta here
if (target.Address == IPAddress.Broadcast)
{
// Some networks do not allow
// a global broadcast so we use the BroadcastAddress from the configuration
// this can be resolved to a local broadcast addresss e.g 192.168.x.255
target.Address = m_configuration.BroadcastAddress;
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
}
int bytesSent = m_socket.SendTo(data, 0, numBytes, SocketFlags.None, target);
if (numBytes != bytesSent)
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
// LogDebug("Sent " + numBytes + " bytes");
}
catch (SocketException sx)
{
if (sx.SocketErrorCode == SocketError.WouldBlock)
{
// send buffer full?
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
return false;
}
if (sx.SocketErrorCode == SocketError.ConnectionReset)
{
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
connectionReset = true;
return false;
}
LogError("Failed to send packet: " + sx);
}
catch (Exception ex)
{
LogError("Failed to send packet: " + ex);
}
finally
{
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
}
return true;
}
internal bool SendMTUPacket(int numBytes, IPEndPoint target)
{
try
{
m_socket.DontFragment = true;
int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
if (numBytes != bytesSent)
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
m_statistics.PacketSent(numBytes, 1);
}
catch (SocketException sx)
{
if (sx.SocketErrorCode == SocketError.MessageSize)
return false;
if (sx.SocketErrorCode == SocketError.WouldBlock)
{
// send buffer full?
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
return true;
}
if (sx.SocketErrorCode == SocketError.ConnectionReset)
return true;
LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx);
}
catch (Exception ex)
{
LogError("Failed to send packet: " + ex);
}
finally
{
m_socket.DontFragment = false;
}
return true;
}
#else
internal bool SendMTUPacket(int numBytes, IPEndPoint target)
{
try
{
m_socket.DontFragment = true;
int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
if (numBytes != bytesSent)
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
}
catch (SocketException sx)
{
if (sx.SocketErrorCode == SocketError.MessageSize)
return false;
if (sx.SocketErrorCode == SocketError.WouldBlock)
{
// send buffer full?
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
return true;
}
if (sx.SocketErrorCode == SocketError.ConnectionReset)
return true;
LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx);
}
catch (Exception ex)
{
LogError("Failed to send packet: " + ex);
}
finally
{
m_socket.DontFragment = false;
}
return true;
}
//
// Release - just send the packet straight away
//
internal void SendPacket(int numBytes, IPEndPoint target, int numMessages, out bool connectionReset)
{
#if USE_RELEASE_STATISTICS
m_statistics.PacketSent(numBytes, numMessages);
#endif
connectionReset = false;
try
{
// TODO: refactor this check outta here
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
if (numBytes != bytesSent)
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
}
catch (SocketException sx)
{
if (sx.SocketErrorCode == SocketError.WouldBlock)
{
// send buffer full?
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
return;
}
if (sx.SocketErrorCode == SocketError.ConnectionReset)
{
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
connectionReset = true;
return;
}
LogError("Failed to send packet: " + sx);
}
catch (Exception ex)
{
LogError("Failed to send packet: " + ex);
}
finally
{
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
}
return;
}
private void FlushDelayedPackets()
{
}
private void SendCallBack(IAsyncResult res)
{
NetException.Assert(res.IsCompleted == true);
m_socket.EndSendTo(res);
}
#endif
}
}

View File

@ -0,0 +1,63 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Diagnostics;
namespace Lidgren.Network
{
public partial class NetPeer
{
[Conditional("DEBUG")]
internal void LogVerbose(string message)
{
#if __ANDROID__
Android.Util.Log.WriteLine(Android.Util.LogPriority.Verbose, "", message);
#endif
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage))
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.VerboseDebugMessage, message));
}
[Conditional("DEBUG")]
internal void LogDebug(string message)
{
#if __ANDROID__
Android.Util.Log.WriteLine(Android.Util.LogPriority.Debug, "", message);
#endif
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DebugMessage))
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.DebugMessage, message));
}
internal void LogWarning(string message)
{
#if __ANDROID__
Android.Util.Log.WriteLine(Android.Util.LogPriority.Warn, "", message);
#endif
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.WarningMessage))
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.WarningMessage, message));
}
internal void LogError(string message)
{
#if __ANDROID__
Android.Util.Log.WriteLine(Android.Util.LogPriority.Error, "", message);
#endif
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ErrorMessage))
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.ErrorMessage, message));
}
}
}

View File

@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
public partial class NetPeer
{
private List<byte[]> m_storagePool; // sorted smallest to largest
private NetQueue<NetOutgoingMessage> m_outgoingMessagesPool;
private NetQueue<NetIncomingMessage> m_incomingMessagesPool;
internal int m_storagePoolBytes;
private void InitializePools()
{
if (m_configuration.UseMessageRecycling)
{
m_storagePool = new List<byte[]>(16);
m_outgoingMessagesPool = new NetQueue<NetOutgoingMessage>(4);
m_incomingMessagesPool = new NetQueue<NetIncomingMessage>(4);
}
else
{
m_storagePool = null;
m_outgoingMessagesPool = null;
m_incomingMessagesPool = null;
}
}
internal byte[] GetStorage(int minimumCapacityInBytes)
{
if (m_storagePool == null)
return new byte[minimumCapacityInBytes];
lock (m_storagePool)
{
for (int i = 0; i < m_storagePool.Count; i++)
{
byte[] retval = m_storagePool[i];
if (retval != null && retval.Length >= minimumCapacityInBytes)
{
m_storagePool[i] = null;
m_storagePoolBytes -= retval.Length;
return retval;
}
}
}
m_statistics.m_bytesAllocated += minimumCapacityInBytes;
return new byte[minimumCapacityInBytes];
}
internal void Recycle(byte[] storage)
{
if (m_storagePool == null || storage == null)
return;
lock (m_storagePool)
{
m_storagePoolBytes += storage.Length;
int cnt = m_storagePool.Count;
for (int i = 0; i < cnt; i++)
{
if (m_storagePool[i] == null)
{
m_storagePool[i] = storage;
return;
}
}
m_storagePool.Add(storage);
}
}
/// <summary>
/// Creates a new message for sending
/// </summary>
public NetOutgoingMessage CreateMessage()
{
return CreateMessage(m_configuration.m_defaultOutgoingMessageCapacity);
}
/// <summary>
/// Creates a new message for sending and writes the provided string to it
/// </summary>
public NetOutgoingMessage CreateMessage(string content)
{
var om = CreateMessage(2 + content.Length); // fair guess
om.Write(content);
return om;
}
/// <summary>
/// Creates a new message for sending
/// </summary>
/// <param name="initialCapacity">initial capacity in bytes</param>
public NetOutgoingMessage CreateMessage(int initialCapacity)
{
NetOutgoingMessage retval;
if (m_outgoingMessagesPool == null || !m_outgoingMessagesPool.TryDequeue(out retval))
retval = new NetOutgoingMessage();
if (initialCapacity > 0)
retval.m_data = GetStorage(initialCapacity);
return retval;
}
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] useStorageData)
{
NetIncomingMessage retval;
if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval))
retval = new NetIncomingMessage(tp);
else
retval.m_incomingMessageType = tp;
retval.m_data = useStorageData;
return retval;
}
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, int minimumByteSize)
{
NetIncomingMessage retval;
if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval))
retval = new NetIncomingMessage(tp);
else
retval.m_incomingMessageType = tp;
retval.m_data = GetStorage(minimumByteSize);
return retval;
}
/// <summary>
/// Recycles a NetIncomingMessage instance for reuse; taking pressure off the garbage collector
/// </summary>
public void Recycle(NetIncomingMessage msg)
{
if (m_incomingMessagesPool == null)
return;
NetException.Assert(m_incomingMessagesPool.Contains(msg) == false, "Recyling already recycled message! Thread race?");
byte[] storage = msg.m_data;
msg.m_data = null;
Recycle(storage);
msg.Reset();
m_incomingMessagesPool.Enqueue(msg);
}
/// <summary>
/// Recycles a list of NetIncomingMessage instances for reuse; taking pressure off the garbage collector
/// </summary>
public void Recycle(IEnumerable<NetIncomingMessage> toRecycle)
{
if (m_incomingMessagesPool == null)
return;
// first recycle the storage of each message
if (m_storagePool != null)
{
lock (m_storagePool)
{
foreach (var msg in toRecycle)
{
var storage = msg.m_data;
msg.m_data = null;
m_storagePoolBytes += storage.Length;
int cnt = m_storagePool.Count;
for (int i = 0; i < cnt; i++)
{
if (m_storagePool[i] == null)
{
m_storagePool[i] = storage;
return;
}
}
msg.Reset();
m_storagePool.Add(storage);
}
}
}
// then recycle the message objects
m_incomingMessagesPool.Enqueue(toRecycle);
}
internal void Recycle(NetOutgoingMessage msg)
{
if (m_outgoingMessagesPool == null)
return;
NetException.Assert(m_outgoingMessagesPool.Contains(msg) == false, "Recyling already recycled message! Thread race?");
byte[] storage = msg.m_data;
msg.m_data = null;
// message fragments cannot be recycled
// TODO: find a way to recycle large message after all fragments has been acknowledged; or? possibly better just to garbage collect them
if (msg.m_fragmentGroup == 0)
Recycle(storage);
msg.Reset();
m_outgoingMessagesPool.Enqueue(msg);
}
/// <summary>
/// Creates an incoming message with the required capacity for releasing to the application
/// </summary>
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, string text)
{
NetIncomingMessage retval;
if (string.IsNullOrEmpty(text))
{
retval = CreateIncomingMessage(tp, 1);
retval.Write(string.Empty);
return retval;
}
int numBytes = System.Text.Encoding.UTF8.GetByteCount(text);
retval = CreateIncomingMessage(tp, numBytes + (numBytes > 127 ? 2 : 1));
retval.Write(text);
return retval;
}
}
}

View File

@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Net;
namespace Lidgren.Network
{
public partial class NetPeer
{
/// <summary>
/// Send a message to a specific connection
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="recipient">The recipient connection</param>
/// <param name="method">How to deliver the message</param>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method)
{
return SendMessage(msg, recipient, method, 0);
}
/// <summary>
/// Send a message to a specific connection
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="recipient">The recipient connection</param>
/// <param name="method">How to deliver the message</param>
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method, int sequenceChannel)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (recipient == null)
throw new ArgumentNullException("recipient");
if (sequenceChannel >= NetConstants.NetChannelsPerDeliveryMethod)
throw new ArgumentOutOfRangeException("sequenceChannel");
NetException.Assert(
((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.ReliableUnordered) ||
((method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered) && sequenceChannel == 0)),
"Delivery method " + method + " cannot use sequence channels other than 0!"
);
NetException.Assert(method != NetDeliveryMethod.Unknown, "Bad delivery method!");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
msg.m_isSent = true;
bool suppressFragmentation = (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.UnreliableSequenced) && m_configuration.UnreliableSizeBehaviour != NetUnreliableSizeBehaviour.NormalFragmentation;
int len = NetConstants.UnfragmentedMessageHeaderSize + msg.LengthBytes; // headers + length, faster than calling msg.GetEncodedSize
if (len <= recipient.m_currentMTU || suppressFragmentation)
{
Interlocked.Increment(ref msg.m_recyclingCount);
return recipient.EnqueueMessage(msg, method, sequenceChannel);
}
else
{
// message must be fragmented!
if (recipient.m_status != NetConnectionStatus.Connected)
return NetSendResult.FailedNotConnected;
return SendFragmentedMessage(msg, new NetConnection[] { recipient }, method, sequenceChannel);
}
}
internal static int GetMTU(IList<NetConnection> recipients)
{
int count = recipients.Count;
int mtu = int.MaxValue;
if (count < 1)
{
#if DEBUG
throw new NetException("GetMTU called with no recipients");
#else
// we don't have access to the particular peer, so just use default MTU
return NetPeerConfiguration.kDefaultMTU;
#endif
}
for(int i=0;i<count;i++)
{
var conn = recipients[i];
int cmtu = conn.m_currentMTU;
if (cmtu < mtu)
mtu = cmtu;
}
return mtu;
}
/// <summary>
/// Send a message to a list of connections
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="recipients">The list of recipients to send to</param>
/// <param name="method">How to deliver the message</param>
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
public void SendMessage(NetOutgoingMessage msg, List<NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (recipients == null)
throw new ArgumentNullException("recipients");
if (recipients.Count < 1)
throw new NetException("recipients must contain at least one item");
if (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered)
NetException.Assert(sequenceChannel == 0, "Delivery method " + method + " cannot use sequence channels other than 0!");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
int mtu = GetMTU(recipients);
msg.m_isSent = true;
int len = msg.GetEncodedSize();
if (len <= mtu)
{
Interlocked.Add(ref msg.m_recyclingCount, recipients.Count);
foreach (NetConnection conn in recipients)
{
if (conn == null)
{
Interlocked.Decrement(ref msg.m_recyclingCount);
continue;
}
NetSendResult res = conn.EnqueueMessage(msg, method, sequenceChannel);
if (res != NetSendResult.Queued && res != NetSendResult.Sent)
Interlocked.Decrement(ref msg.m_recyclingCount);
}
}
else
{
// message must be fragmented!
SendFragmentedMessage(msg, recipients, method, sequenceChannel);
}
return;
}
/// <summary>
/// Send a message to an unconnected host
/// </summary>
public void SendUnconnectedMessage(NetOutgoingMessage msg, string host, int port)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (host == null)
throw new ArgumentNullException("host");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
IPAddress adr = NetUtility.Resolve(host);
if (adr == null)
throw new NetException("Failed to resolve " + host);
msg.m_messageType = NetMessageType.Unconnected;
msg.m_isSent = true;
Interlocked.Increment(ref msg.m_recyclingCount);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(new IPEndPoint(adr, port), msg));
}
/// <summary>
/// Send a message to an unconnected host
/// </summary>
public void SendUnconnectedMessage(NetOutgoingMessage msg, IPEndPoint recipient)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (recipient == null)
throw new ArgumentNullException("recipient");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
msg.m_messageType = NetMessageType.Unconnected;
msg.m_isSent = true;
Interlocked.Increment(ref msg.m_recyclingCount);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(recipient, msg));
}
/// <summary>
/// Send a message to an unconnected host
/// </summary>
public void SendUnconnectedMessage(NetOutgoingMessage msg, IList<IPEndPoint> recipients)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (recipients == null)
throw new ArgumentNullException("recipients");
if (recipients.Count < 1)
throw new NetException("recipients must contain at least one item");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
msg.m_messageType = NetMessageType.Unconnected;
msg.m_isSent = true;
Interlocked.Add(ref msg.m_recyclingCount, recipients.Count);
foreach(IPEndPoint ep in recipients)
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(ep, msg));
}
/// <summary>
/// Send a message to this exact same netpeer (loopback)
/// </summary>
public void SendUnconnectedToSelf(NetOutgoingMessage msg)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
msg.m_messageType = NetMessageType.Unconnected;
msg.m_isSent = true;
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData) == false)
return; // dropping unconnected message since it's not enabled for receiving
NetIncomingMessage om = CreateIncomingMessage(NetIncomingMessageType.UnconnectedData, msg.LengthBytes);
om.Write(msg);
om.m_isFragment = false;
om.m_receiveTime = NetTime.Now;
om.m_senderConnection = null;
om.m_senderEndPoint = m_socket.LocalEndPoint as IPEndPoint;
NetException.Assert(om.m_bitLength == msg.LengthBits);
ReleaseMessage(om);
}
}
}

357
Lidgren.Network/NetPeer.cs Normal file
View File

@ -0,0 +1,357 @@
using System;
using System.Threading;
using System.Collections.Generic;
using System.Net;
namespace Lidgren.Network
{
/// <summary>
/// Represents a local peer capable of holding zero, one or more connections to remote peers
/// </summary>
public partial class NetPeer
{
private static int s_initializedPeersCount;
private int m_listenPort;
private object m_tag;
private object m_messageReceivedEventCreationLock = new object();
internal readonly List<NetConnection> m_connections;
private readonly Dictionary<IPEndPoint, NetConnection> m_connectionLookup;
private string m_shutdownReason;
/// <summary>
/// Gets the NetPeerStatus of the NetPeer
/// </summary>
public NetPeerStatus Status { get { return m_status; } }
/// <summary>
/// Signalling event which can be waited on to determine when a message is queued for reading.
/// Note that there is no guarantee that after the event is signaled the blocked thread will
/// find the message in the queue. Other user created threads could be preempted and dequeue
/// the message before the waiting thread wakes up.
/// </summary>
public AutoResetEvent MessageReceivedEvent
{
get
{
if (m_messageReceivedEvent == null)
{
lock (m_messageReceivedEventCreationLock) // make sure we don't create more than one event object
{
if (m_messageReceivedEvent == null)
m_messageReceivedEvent = new AutoResetEvent(false);
}
}
return m_messageReceivedEvent;
}
}
/// <summary>
/// Gets a unique identifier for this NetPeer based on Mac address and ip/port. Note! Not available until Start() has been called!
/// </summary>
public long UniqueIdentifier { get { return m_uniqueIdentifier; } }
/// <summary>
/// Gets the port number this NetPeer is listening and sending on, if Start() has been called
/// </summary>
public int Port { get { return m_listenPort; } }
/// <summary>
/// Returns an UPnP object if enabled in the NetPeerConfiguration
/// </summary>
public NetUPnP UPnP { get { return m_upnp; } }
/// <summary>
/// Gets or sets the application defined object containing data about the peer
/// </summary>
public object Tag
{
get { return m_tag; }
set { m_tag = value; }
}
/// <summary>
/// Gets a copy of the list of connections
/// </summary>
public List<NetConnection> Connections
{
get
{
lock (m_connections)
return new List<NetConnection>(m_connections);
}
}
/// <summary>
/// Gets the number of active connections
/// </summary>
public int ConnectionsCount
{
get { return m_connections.Count; }
}
/// <summary>
/// Statistics on this NetPeer since it was initialized
/// </summary>
public NetPeerStatistics Statistics
{
get { return m_statistics; }
}
/// <summary>
/// Gets the configuration used to instanciate this NetPeer
/// </summary>
public NetPeerConfiguration Configuration { get { return m_configuration; } }
/// <summary>
/// NetPeer constructor
/// </summary>
public NetPeer(NetPeerConfiguration config)
{
m_configuration = config;
m_statistics = new NetPeerStatistics(this);
m_releasedIncomingMessages = new NetQueue<NetIncomingMessage>(4);
m_unsentUnconnectedMessages = new NetQueue<NetTuple<IPEndPoint, NetOutgoingMessage>>(2);
m_connections = new List<NetConnection>();
m_connectionLookup = new Dictionary<IPEndPoint, NetConnection>();
m_handshakes = new Dictionary<IPEndPoint, NetConnection>();
m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
m_status = NetPeerStatus.NotRunning;
m_receivedFragmentGroups = new Dictionary<NetConnection, Dictionary<int, ReceivedFragmentGroup>>();
}
/// <summary>
/// Binds to socket and spawns the networking thread
/// </summary>
public void Start()
{
if (m_status != NetPeerStatus.NotRunning)
{
// already running! Just ignore...
LogWarning("Start() called on already running NetPeer - ignoring.");
return;
}
m_status = NetPeerStatus.Starting;
// fix network thread name
if (m_configuration.NetworkThreadName == "Lidgren network thread")
{
int pc = Interlocked.Increment(ref s_initializedPeersCount);
m_configuration.NetworkThreadName = "Lidgren network thread " + pc.ToString();
}
InitializeNetwork();
// start network thread
m_networkThread = new Thread(new ThreadStart(NetworkLoop));
m_networkThread.Name = m_configuration.NetworkThreadName;
m_networkThread.IsBackground = true;
m_networkThread.Start();
// send upnp discovery
if (m_upnp != null)
m_upnp.Discover(this);
// allow some time for network thread to start up in case they call Connect() or UPnP calls immediately
Thread.Sleep(50);
}
/// <summary>
/// Get the connection, if any, for a certain remote endpoint
/// </summary>
public NetConnection GetConnection(IPEndPoint ep)
{
NetConnection retval;
// this should not pose a threading problem, m_connectionLookup is never added to concurrently
// and TryGetValue will not throw an exception on fail, only yield null, which is acceptable
m_connectionLookup.TryGetValue(ep, out retval);
return retval;
}
/// <summary>
/// Read a pending message from any connection, blocking up to maxMillis if needed
/// </summary>
public NetIncomingMessage WaitMessage(int maxMillis)
{
var msg = ReadMessage();
if (msg != null)
return msg; // no need to wait; we already have a message to deliver
var msgEvt = MessageReceivedEvent;
msgEvt.WaitOne(maxMillis);
return ReadMessage();
}
/// <summary>
/// Read a pending message from any connection, if any
/// </summary>
public NetIncomingMessage ReadMessage()
{
NetIncomingMessage retval;
if (m_releasedIncomingMessages.TryDequeue(out retval))
{
if (retval.MessageType == NetIncomingMessageType.StatusChanged)
{
NetConnectionStatus status = (NetConnectionStatus)retval.PeekByte();
retval.SenderConnection.m_visibleStatus = status;
}
}
return retval;
}
/// <summary>
/// Read a pending message from any connection, if any
/// </summary>
public int ReadMessages(IList<NetIncomingMessage> addTo)
{
int added = m_releasedIncomingMessages.TryDrain(addTo);
if (added > 0)
{
for (int i = 0; i < added; i++)
{
var index = addTo.Count - added + i;
var nim = addTo[index];
if (nim.MessageType == NetIncomingMessageType.StatusChanged)
{
NetConnectionStatus status = (NetConnectionStatus)nim.PeekByte();
nim.SenderConnection.m_visibleStatus = status;
}
}
}
return added;
}
// send message immediately
internal void SendLibrary(NetOutgoingMessage msg, IPEndPoint recipient)
{
VerifyNetworkThread();
NetException.Assert(msg.m_isSent == false);
bool connReset;
int len = msg.Encode(m_sendBuffer, 0, 0);
SendPacket(len, recipient, 1, out connReset);
}
/// <summary>
/// Create a connection to a remote endpoint
/// </summary>
public NetConnection Connect(string host, int port)
{
return Connect(new IPEndPoint(NetUtility.Resolve(host), port), null);
}
/// <summary>
/// Create a connection to a remote endpoint
/// </summary>
public NetConnection Connect(string host, int port, NetOutgoingMessage hailMessage)
{
return Connect(new IPEndPoint(NetUtility.Resolve(host), port), hailMessage);
}
/// <summary>
/// Create a connection to a remote endpoint
/// </summary>
public NetConnection Connect(IPEndPoint remoteEndPoint)
{
return Connect(remoteEndPoint, null);
}
/// <summary>
/// Create a connection to a remote endpoint
/// </summary>
public virtual NetConnection Connect(IPEndPoint remoteEndPoint, NetOutgoingMessage hailMessage)
{
if (remoteEndPoint == null)
throw new ArgumentNullException("remoteEndPoint");
lock (m_connections)
{
if (m_status == NetPeerStatus.NotRunning)
throw new NetException("Must call Start() first");
if (m_connectionLookup.ContainsKey(remoteEndPoint))
throw new NetException("Already connected to that endpoint!");
NetConnection hs;
if (m_handshakes.TryGetValue(remoteEndPoint, out hs))
{
// already trying to connect to that endpoint; make another try
switch (hs.m_status)
{
case NetConnectionStatus.InitiatedConnect:
// send another connect
hs.m_connectRequested = true;
break;
case NetConnectionStatus.RespondedConnect:
// send another response
hs.SendConnectResponse((float)NetTime.Now, false);
break;
default:
// weird
LogWarning("Weird situation; Connect() already in progress to remote endpoint; but hs status is " + hs.m_status);
break;
}
return hs;
}
NetConnection conn = new NetConnection(this, remoteEndPoint);
conn.m_status = NetConnectionStatus.InitiatedConnect;
conn.m_localHailMessage = hailMessage;
// handle on network thread
conn.m_connectRequested = true;
conn.m_connectionInitiator = true;
m_handshakes.Add(remoteEndPoint, conn);
return conn;
}
}
/// <summary>
/// Send raw bytes; only used for debugging
/// </summary>
#if DEBUG
public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination)
#else
public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination)
#endif
{
// wrong thread - this miiiight crash with network thread... but what's a boy to do.
Array.Copy(arr, offset, m_sendBuffer, 0, length);
bool unused;
SendPacket(length, destination, 1, out unused);
}
/// <summary>
/// In DEBUG, throws an exception, in RELEASE logs an error message
/// </summary>
/// <param name="message"></param>
internal void ThrowOrLog(string message)
{
#if DEBUG
throw new NetException(message);
#else
LogError(message);
#endif
}
/// <summary>
/// Disconnects all active connections and closes the socket
/// </summary>
public void Shutdown(string bye)
{
// called on user thread
if (m_socket == null)
return; // already shut down
LogDebug("Shutdown requested");
m_shutdownReason = bye;
m_status = NetPeerStatus.ShutdownRequested;
}
}
}

View File

@ -0,0 +1,509 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Net;
namespace Lidgren.Network
{
/// <summary>
/// Partly immutable after NetPeer has been initialized
/// </summary>
public sealed class NetPeerConfiguration
{
// Maximum transmission unit
// Ethernet can take 1500 bytes of payload, so lets stay below that.
// The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468)
// -20 bytes IP header
// -8 bytes UDP header
// -4 bytes to be on the safe side and align to 8-byte boundary
// Total 1408 bytes
// Note that lidgren headers (5 bytes) are not included here; since it's part of the "mtu payload"
/// <summary>
/// Default MTU value in bytes
/// </summary>
public const int kDefaultMTU = 1408;
private const string c_isLockedMessage = "You may not modify the NetPeerConfiguration after it has been used to initialize a NetPeer";
private bool m_isLocked;
private readonly string m_appIdentifier;
private string m_networkThreadName;
private IPAddress m_localAddress;
private IPAddress m_broadcastAddress;
internal bool m_acceptIncomingConnections;
internal int m_maximumConnections;
internal int m_defaultOutgoingMessageCapacity;
internal float m_pingInterval;
internal bool m_useMessageRecycling;
internal float m_connectionTimeout;
internal bool m_enableUPnP;
internal bool m_autoFlushSendQueue;
private NetUnreliableSizeBehaviour m_unreliableSizeBehaviour;
internal NetIncomingMessageType m_disabledTypes;
internal int m_port;
internal int m_receiveBufferSize;
internal int m_sendBufferSize;
internal float m_resendHandshakeInterval;
internal int m_maximumHandshakeAttempts;
// bad network simulation
internal float m_loss;
internal float m_duplicates;
internal float m_minimumOneWayLatency;
internal float m_randomOneWayLatency;
// MTU
internal int m_maximumTransmissionUnit;
internal bool m_autoExpandMTU;
internal float m_expandMTUFrequency;
internal int m_expandMTUFailAttempts;
/// <summary>
/// NetPeerConfiguration constructor
/// </summary>
public NetPeerConfiguration(string appIdentifier)
{
if (string.IsNullOrEmpty(appIdentifier))
throw new NetException("App identifier must be at least one character long");
m_appIdentifier = appIdentifier;
//
// default values
//
m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage | NetIncomingMessageType.ConnectionLatencyUpdated | NetIncomingMessageType.NatIntroductionSuccess;
m_networkThreadName = "Lidgren network thread";
m_localAddress = IPAddress.Any;
m_broadcastAddress = IPAddress.Broadcast;
var ip = NetUtility.GetBroadcastAddress();
if (ip != null)
{
m_broadcastAddress = ip;
}
m_port = 0;
m_receiveBufferSize = 131071;
m_sendBufferSize = 131071;
m_acceptIncomingConnections = false;
m_maximumConnections = 32;
m_defaultOutgoingMessageCapacity = 16;
m_pingInterval = 4.0f;
m_connectionTimeout = 25.0f;
m_useMessageRecycling = true;
m_resendHandshakeInterval = 3.0f;
m_maximumHandshakeAttempts = 5;
m_autoFlushSendQueue = true;
m_maximumTransmissionUnit = kDefaultMTU;
m_autoExpandMTU = false;
m_expandMTUFrequency = 2.0f;
m_expandMTUFailAttempts = 5;
m_unreliableSizeBehaviour = NetUnreliableSizeBehaviour.IgnoreMTU;
m_loss = 0.0f;
m_minimumOneWayLatency = 0.0f;
m_randomOneWayLatency = 0.0f;
m_duplicates = 0.0f;
m_isLocked = false;
}
internal void Lock()
{
m_isLocked = true;
}
/// <summary>
/// Gets the identifier of this application; the library can only connect to matching app identifier peers
/// </summary>
public string AppIdentifier
{
get { return m_appIdentifier; }
}
/// <summary>
/// Enables receiving of the specified type of message
/// </summary>
public void EnableMessageType(NetIncomingMessageType type)
{
m_disabledTypes &= (~type);
}
/// <summary>
/// Disables receiving of the specified type of message
/// </summary>
public void DisableMessageType(NetIncomingMessageType type)
{
m_disabledTypes |= type;
}
/// <summary>
/// Enables or disables receiving of the specified type of message
/// </summary>
public void SetMessageTypeEnabled(NetIncomingMessageType type, bool enabled)
{
if (enabled)
m_disabledTypes &= (~type);
else
m_disabledTypes |= type;
}
/// <summary>
/// Gets if receiving of the specified type of message is enabled
/// </summary>
public bool IsMessageTypeEnabled(NetIncomingMessageType type)
{
return !((m_disabledTypes & type) == type);
}
/// <summary>
/// Gets or sets the behaviour of unreliable sends above MTU
/// </summary>
public NetUnreliableSizeBehaviour UnreliableSizeBehaviour
{
get { return m_unreliableSizeBehaviour; }
set { m_unreliableSizeBehaviour = value; }
}
/// <summary>
/// Gets or sets the name of the library network thread. Cannot be changed once NetPeer is initialized.
/// </summary>
public string NetworkThreadName
{
get { return m_networkThreadName; }
set
{
if (m_isLocked)
throw new NetException("NetworkThreadName may not be set after the NetPeer which uses the configuration has been started");
m_networkThreadName = value;
}
}
/// <summary>
/// Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized.
/// </summary>
public int MaximumConnections
{
get { return m_maximumConnections; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_maximumConnections = value;
}
}
/// <summary>
/// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers. Cannot be changed once NetPeer is initialized.
/// </summary>
public int MaximumTransmissionUnit
{
get { return m_maximumTransmissionUnit; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
if (value < 1 || value >= ((ushort.MaxValue + 1) / 8))
throw new NetException("MaximumTransmissionUnit must be between 1 and " + (((ushort.MaxValue + 1) / 8) - 1) + " bytes");
m_maximumTransmissionUnit = value;
}
}
/// <summary>
/// Gets or sets the default capacity in bytes when NetPeer.CreateMessage() is called without argument
/// </summary>
public int DefaultOutgoingMessageCapacity
{
get { return m_defaultOutgoingMessageCapacity; }
set { m_defaultOutgoingMessageCapacity = value; }
}
/// <summary>
/// Gets or sets the time between latency calculating pings
/// </summary>
public float PingInterval
{
get { return m_pingInterval; }
set { m_pingInterval = value; }
}
/// <summary>
/// Gets or sets if the library should recycling messages to avoid excessive garbage collection. Cannot be changed once NetPeer is initialized.
/// </summary>
public bool UseMessageRecycling
{
get { return m_useMessageRecycling; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_useMessageRecycling = value;
}
}
/// <summary>
/// Gets or sets the number of seconds timeout will be postponed on a successful ping/pong
/// </summary>
public float ConnectionTimeout
{
get { return m_connectionTimeout; }
set
{
if (value < m_pingInterval)
throw new NetException("Connection timeout cannot be lower than ping interval!");
m_connectionTimeout = value;
}
}
/// <summary>
/// Enables UPnP support; enabling port forwarding and getting external ip
/// </summary>
public bool EnableUPnP
{
get { return m_enableUPnP; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_enableUPnP = value;
}
}
/// <summary>
/// Enables or disables automatic flushing of the send queue. If disabled, you must manully call NetPeer.FlushSendQueue() to flush sent messages to network.
/// </summary>
public bool AutoFlushSendQueue
{
get { return m_autoFlushSendQueue; }
set { m_autoFlushSendQueue = value; }
}
/// <summary>
/// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized.
/// </summary>
public IPAddress LocalAddress
{
get { return m_localAddress; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_localAddress = value;
}
}
/// <summary>
/// Gets or sets the local broadcast address to use when broadcasting
/// </summary>
public IPAddress BroadcastAddress
{
get { return m_broadcastAddress; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_broadcastAddress = value;
}
}
/// <summary>
/// Gets or sets the local port to bind to. Defaults to 0. Cannot be changed once NetPeer is initialized.
/// </summary>
public int Port
{
get { return m_port; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_port = value;
}
}
/// <summary>
/// Gets or sets the size in bytes of the receiving buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized.
/// </summary>
public int ReceiveBufferSize
{
get { return m_receiveBufferSize; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_receiveBufferSize = value;
}
}
/// <summary>
/// Gets or sets the size in bytes of the sending buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized.
/// </summary>
public int SendBufferSize
{
get { return m_sendBufferSize; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_sendBufferSize = value;
}
}
/// <summary>
/// Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient.
/// </summary>
public bool AcceptIncomingConnections
{
get { return m_acceptIncomingConnections; }
set { m_acceptIncomingConnections = value; }
}
/// <summary>
/// Gets or sets the number of seconds between handshake attempts
/// </summary>
public float ResendHandshakeInterval
{
get { return m_resendHandshakeInterval; }
set { m_resendHandshakeInterval = value; }
}
/// <summary>
/// Gets or sets the maximum number of handshake attempts before failing to connect
/// </summary>
public int MaximumHandshakeAttempts
{
get { return m_maximumHandshakeAttempts; }
set
{
if (value < 1)
throw new NetException("MaximumHandshakeAttempts must be at least 1");
m_maximumHandshakeAttempts = value;
}
}
/// <summary>
/// Gets or sets if the NetPeer should send large messages to try to expand the maximum transmission unit size
/// </summary>
public bool AutoExpandMTU
{
get { return m_autoExpandMTU; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_autoExpandMTU = value;
}
}
/// <summary>
/// Gets or sets how often to send large messages to expand MTU if AutoExpandMTU is enabled
/// </summary>
public float ExpandMTUFrequency
{
get { return m_expandMTUFrequency; }
set { m_expandMTUFrequency = value; }
}
/// <summary>
/// Gets or sets the number of failed expand mtu attempts to perform before setting final MTU
/// </summary>
public int ExpandMTUFailAttempts
{
get { return m_expandMTUFailAttempts; }
set { m_expandMTUFailAttempts = value; }
}
#if DEBUG
/// <summary>
/// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f
/// </summary>
public float SimulatedLoss
{
get { return m_loss; }
set { m_loss = value; }
}
/// <summary>
/// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds
/// </summary>
public float SimulatedMinimumLatency
{
get { return m_minimumOneWayLatency; }
set { m_minimumOneWayLatency = value; }
}
/// <summary>
/// Gets or sets the simulated added random amount of one way latency for sent packets in seconds
/// </summary>
public float SimulatedRandomLatency
{
get { return m_randomOneWayLatency; }
set { m_randomOneWayLatency = value; }
}
/// <summary>
/// Gets the average simulated one way latency in seconds
/// </summary>
public float SimulatedAverageLatency
{
get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); }
}
/// <summary>
/// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f
/// </summary>
public float SimulatedDuplicatesChance
{
get { return m_duplicates; }
set { m_duplicates = value; }
}
#endif
/// <summary>
/// Creates a memberwise shallow clone of this configuration
/// </summary>
public NetPeerConfiguration Clone()
{
NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration;
retval.m_isLocked = false;
return retval;
}
}
/// <summary>
/// Behaviour of unreliable sends above MTU
/// </summary>
public enum NetUnreliableSizeBehaviour
{
/// <summary>
/// Sending an unreliable message will ignore MTU and send everything in a single packet; this is the new default
/// </summary>
IgnoreMTU = 0,
/// <summary>
/// Old behaviour; use normal fragmentation for unreliable messages - if a fragment is dropped, memory for received fragments are never reclaimed!
/// </summary>
NormalFragmentation = 1,
/// <summary>
/// Alternate behaviour; just drops unreliable messages above MTU
/// </summary>
DropAboveMTU = 2,
}
}

View File

@ -0,0 +1,164 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Uncomment the line below to get statistics in RELEASE builds
//#define USE_RELEASE_STATISTICS
using System;
using System.Text;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Statistics for a NetPeer instance
/// </summary>
public sealed class NetPeerStatistics
{
private readonly NetPeer m_peer;
internal int m_sentPackets;
internal int m_receivedPackets;
internal int m_sentMessages;
internal int m_receivedMessages;
internal int m_receivedFragments;
internal int m_sentBytes;
internal int m_receivedBytes;
internal long m_bytesAllocated;
internal NetPeerStatistics(NetPeer peer)
{
m_peer = peer;
Reset();
}
internal void Reset()
{
m_sentPackets = 0;
m_receivedPackets = 0;
m_sentMessages = 0;
m_receivedMessages = 0;
m_receivedFragments = 0;
m_sentBytes = 0;
m_receivedBytes = 0;
m_bytesAllocated = 0;
}
/// <summary>
/// Gets the number of sent packets since the NetPeer was initialized
/// </summary>
public int SentPackets { get { return m_sentPackets; } }
/// <summary>
/// Gets the number of received packets since the NetPeer was initialized
/// </summary>
public int ReceivedPackets { get { return m_receivedPackets; } }
/// <summary>
/// Gets the number of sent messages since the NetPeer was initialized
/// </summary>
public int SentMessages { get { return m_sentMessages; } }
/// <summary>
/// Gets the number of received messages since the NetPeer was initialized
/// </summary>
public int ReceivedMessages { get { return m_receivedMessages; } }
/// <summary>
/// Gets the number of sent bytes since the NetPeer was initialized
/// </summary>
public int SentBytes { get { return m_sentBytes; } }
/// <summary>
/// Gets the number of received bytes since the NetPeer was initialized
/// </summary>
public int ReceivedBytes { get { return m_receivedBytes; } }
/// <summary>
/// Gets the number of bytes allocated (and possibly garbage collected) for message storage
/// </summary>
public long StorageBytesAllocated { get { return m_bytesAllocated; } }
/// <summary>
/// Gets the number of bytes in the recycled pool
/// </summary>
public int BytesInRecyclePool { get { return m_peer.m_storagePoolBytes; } }
#if USE_RELEASE_STATISTICS
internal void PacketSent(int numBytes, int numMessages)
{
m_sentPackets++;
m_sentBytes += numBytes;
m_sentMessages += numMessages;
}
#else
[Conditional("DEBUG")]
internal void PacketSent(int numBytes, int numMessages)
{
m_sentPackets++;
m_sentBytes += numBytes;
m_sentMessages += numMessages;
}
#endif
#if USE_RELEASE_STATISTICS
internal void PacketReceived(int numBytes, int numMessages)
{
m_receivedPackets++;
m_receivedBytes += numBytes;
m_receivedMessages += numMessages;
}
#else
[Conditional("DEBUG")]
internal void PacketReceived(int numBytes, int numMessages, int numFragments)
{
m_receivedPackets++;
m_receivedBytes += numBytes;
m_receivedMessages += numMessages;
m_receivedFragments += numFragments;
}
#endif
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
StringBuilder bdr = new StringBuilder();
bdr.AppendLine(m_peer.ConnectionsCount.ToString() + " connections");
#if DEBUG || USE_RELEASE_STATISTICS
bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets");
bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets");
#else
bdr.AppendLine("Sent (n/a) bytes in (n/a) messages in (n/a) packets");
bdr.AppendLine("Received (n/a) bytes in (n/a) messages in (n/a) packets");
#endif
bdr.AppendLine("Storage allocated " + m_bytesAllocated + " bytes");
bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes");
return bdr.ToString();
}
}
}

View File

@ -0,0 +1,49 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
namespace Lidgren.Network
{
/// <summary>
/// Status for a NetPeer instance
/// </summary>
public enum NetPeerStatus
{
/// <summary>
/// NetPeer is not running; socket is not bound
/// </summary>
NotRunning = 0,
/// <summary>
/// NetPeer is in the process of starting up
/// </summary>
Starting = 1,
/// <summary>
/// NetPeer is bound to socket and listening for packets
/// </summary>
Running = 2,
/// <summary>
/// Shutdown has been requested and will be executed shortly
/// </summary>
ShutdownRequested = 3,
}
}

335
Lidgren.Network/NetQueue.cs Normal file
View File

@ -0,0 +1,335 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Threading;
namespace Lidgren.Network
{
/// <summary>
/// Thread safe (blocking) expanding queue with TryDequeue() and EnqueueFirst()
/// </summary>
[DebuggerDisplay("Count={Count} Capacity={Capacity}")]
public sealed class NetQueue<T>
{
// Example:
// m_capacity = 8
// m_size = 6
// m_head = 4
//
// [0] item
// [1] item (tail = ((head + size - 1) % capacity)
// [2]
// [3]
// [4] item (head)
// [5] item
// [6] item
// [7] item
//
private T[] m_items;
private readonly ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim();
private int m_size;
private int m_head;
/// <summary>
/// Gets the number of items in the queue
/// </summary>
public int Count { get { return m_size; } }
/// <summary>
/// Gets the current capacity for the queue
/// </summary>
public int Capacity { get { return m_items.Length; } }
/// <summary>
/// NetQueue constructor
/// </summary>
public NetQueue(int initialCapacity)
{
m_items = new T[initialCapacity];
}
/// <summary>
/// Adds an item last/tail of the queue
/// </summary>
public void Enqueue(T item)
{
m_lock.EnterWriteLock();
try
{
if (m_size == m_items.Length)
SetCapacity(m_items.Length + 8);
int slot = (m_head + m_size) % m_items.Length;
m_items[slot] = item;
m_size++;
}
finally
{
m_lock.ExitWriteLock();
}
}
/// <summary>
/// Adds an item last/tail of the queue
/// </summary>
public void Enqueue(IEnumerable<T> items)
{
m_lock.EnterWriteLock();
try
{
foreach (var item in items)
{
if (m_size == m_items.Length)
SetCapacity(m_items.Length + 8); // @TODO move this out of loop
int slot = (m_head + m_size) % m_items.Length;
m_items[slot] = item;
m_size++;
}
}
finally
{
m_lock.ExitWriteLock();
}
}
/// <summary>
/// Places an item first, at the head of the queue
/// </summary>
public void EnqueueFirst(T item)
{
m_lock.EnterWriteLock();
try
{
if (m_size >= m_items.Length)
SetCapacity(m_items.Length + 8);
m_head--;
if (m_head < 0)
m_head = m_items.Length - 1;
m_items[m_head] = item;
m_size++;
}
finally
{
m_lock.ExitWriteLock();
}
}
// must be called from within a write locked m_lock!
private void SetCapacity(int newCapacity)
{
if (m_size == 0)
{
if (m_size == 0)
{
m_items = new T[newCapacity];
m_head = 0;
return;
}
}
T[] newItems = new T[newCapacity];
if (m_head + m_size - 1 < m_items.Length)
{
Array.Copy(m_items, m_head, newItems, 0, m_size);
}
else
{
Array.Copy(m_items, m_head, newItems, 0, m_items.Length - m_head);
Array.Copy(m_items, 0, newItems, m_items.Length - m_head, (m_size - (m_items.Length - m_head)));
}
m_items = newItems;
m_head = 0;
}
/// <summary>
/// Gets an item from the head of the queue, or returns default(T) if empty
/// </summary>
public bool TryDequeue(out T item)
{
if (m_size == 0)
{
item = default(T);
return false;
}
m_lock.EnterWriteLock();
try
{
if (m_size == 0)
{
item = default(T);
return false;
}
item = m_items[m_head];
m_items[m_head] = default(T);
m_head = (m_head + 1) % m_items.Length;
m_size--;
return true;
}
catch
{
#if DEBUG
throw;
#else
item = default(T);
return false;
#endif
}
finally
{
m_lock.ExitWriteLock();
}
}
/// <summary>
/// Gets all items from the head of the queue, or returns number of items popped
/// </summary>
public int TryDrain(IList<T> addTo)
{
if (m_size == 0)
return 0;
m_lock.EnterWriteLock();
try
{
int added = m_size;
while (m_size > 0)
{
var item = m_items[m_head];
addTo.Add(item);
m_items[m_head] = default(T);
m_head = (m_head + 1) % m_items.Length;
m_size--;
}
return added;
}
finally
{
m_lock.ExitWriteLock();
}
}
/// <summary>
/// Returns default(T) if queue is empty
/// </summary>
public T TryPeek(int offset)
{
if (m_size == 0)
return default(T);
m_lock.EnterReadLock();
try
{
if (m_size == 0)
return default(T);
return m_items[(m_head + offset) % m_items.Length];
}
finally
{
m_lock.ExitReadLock();
}
}
/// <summary>
/// Determines whether an item is in the queue
/// </summary>
public bool Contains(T item)
{
m_lock.EnterReadLock();
try
{
int ptr = m_head;
for (int i = 0; i < m_size; i++)
{
if (m_items[ptr] == null)
{
if (item == null)
return true;
}
else
{
if (m_items[ptr].Equals(item))
return true;
}
ptr = (ptr + 1) % m_items.Length;
}
return false;
}
finally
{
m_lock.ExitReadLock();
}
}
/// <summary>
/// Copies the queue items to a new array
/// </summary>
public T[] ToArray()
{
m_lock.EnterReadLock();
try
{
T[] retval = new T[m_size];
int ptr = m_head;
for (int i = 0; i < m_size; i++)
{
retval[i] = m_items[ptr++];
if (ptr >= m_items.Length)
ptr = 0;
}
return retval;
}
finally
{
m_lock.ExitReadLock();
}
}
/// <summary>
/// Removes all objects from the queue
/// </summary>
public void Clear()
{
m_lock.EnterWriteLock();
try
{
for (int i = 0; i < m_items.Length; i++)
m_items[i] = default(T);
m_head = 0;
m_size = 0;
}
finally
{
m_lock.ExitWriteLock();
}
}
}
}

View File

@ -0,0 +1,281 @@
using System;
using System.Security.Cryptography;
namespace Lidgren.Network
{
/// <summary>
/// Multiply With Carry random
/// </summary>
public class MWCRandom : NetRandom
{
/// <summary>
/// Get global instance of MWCRandom
/// </summary>
public static new readonly MWCRandom Instance = new MWCRandom();
private uint m_w, m_z;
/// <summary>
/// Constructor with randomized seed
/// </summary>
public MWCRandom()
{
Initialize(NetRandomSeed.GetUInt64());
}
/// <summary>
/// (Re)initialize this instance with provided 32 bit seed
/// </summary>
[CLSCompliant(false)]
public override void Initialize(uint seed)
{
m_w = seed;
m_z = seed * 16777619;
}
/// <summary>
/// (Re)initialize this instance with provided 64 bit seed
/// </summary>
[CLSCompliant(false)]
public void Initialize(ulong seed)
{
m_w = (uint)seed;
m_z = (uint)(seed >> 32);
}
/// <summary>
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
/// </summary>
[CLSCompliant(false)]
public override uint NextUInt32()
{
m_z = 36969 * (m_z & 65535) + (m_z >> 16);
m_w = 18000 * (m_w & 65535) + (m_w >> 16);
return ((m_z << 16) + m_w);
}
}
/// <summary>
/// Xor Shift based random
/// </summary>
public sealed class XorShiftRandom : NetRandom
{
/// <summary>
/// Get global instance of XorShiftRandom
/// </summary>
public static new readonly XorShiftRandom Instance = new XorShiftRandom();
private const uint c_x = 123456789;
private const uint c_y = 362436069;
private const uint c_z = 521288629;
private const uint c_w = 88675123;
private uint m_x, m_y, m_z, m_w;
/// <summary>
/// Constructor with randomized seed
/// </summary>
public XorShiftRandom()
{
Initialize(NetRandomSeed.GetUInt64());
}
/// <summary>
/// Constructor with provided 64 bit seed
/// </summary>
[CLSCompliant(false)]
public XorShiftRandom(ulong seed)
{
Initialize(seed);
}
/// <summary>
/// (Re)initialize this instance with provided 32 bit seed
/// </summary>
[CLSCompliant(false)]
public override void Initialize(uint seed)
{
m_x = (uint)seed;
m_y = c_y;
m_z = c_z;
m_w = c_w;
}
/// <summary>
/// (Re)initialize this instance with provided 64 bit seed
/// </summary>
[CLSCompliant(false)]
public void Initialize(ulong seed)
{
m_x = (uint)seed;
m_y = c_y;
m_z = (uint)(seed << 32);
m_w = c_w;
}
/// <summary>
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
/// </summary>
[CLSCompliant(false)]
public override uint NextUInt32()
{
uint t = (m_x ^ (m_x << 11));
m_x = m_y; m_y = m_z; m_z = m_w;
return (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)));
}
}
/// <summary>
/// Mersenne Twister based random
/// </summary>
public sealed class MersenneTwisterRandom : NetRandom
{
/// <summary>
/// Get global instance of MersenneTwisterRandom
/// </summary>
public static new readonly MersenneTwisterRandom Instance = new MersenneTwisterRandom();
private const int N = 624;
private const int M = 397;
private const uint MATRIX_A = 0x9908b0dfU;
private const uint UPPER_MASK = 0x80000000U;
private const uint LOWER_MASK = 0x7fffffffU;
private const uint TEMPER1 = 0x9d2c5680U;
private const uint TEMPER2 = 0xefc60000U;
private const int TEMPER3 = 11;
private const int TEMPER4 = 7;
private const int TEMPER5 = 15;
private const int TEMPER6 = 18;
private UInt32[] mt;
private int mti;
private UInt32[] mag01;
private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0);
/// <summary>
/// Constructor with randomized seed
/// </summary>
public MersenneTwisterRandom()
{
Initialize(NetRandomSeed.GetUInt32());
}
/// <summary>
/// Constructor with provided 32 bit seed
/// </summary>
[CLSCompliant(false)]
public MersenneTwisterRandom(uint seed)
{
Initialize(seed);
}
/// <summary>
/// (Re)initialize this instance with provided 32 bit seed
/// </summary>
[CLSCompliant(false)]
public override void Initialize(uint seed)
{
mt = new UInt32[N];
mti = N + 1;
mag01 = new UInt32[] { 0x0U, MATRIX_A };
mt[0] = seed;
for (int i = 1; i < N; i++)
mt[i] = (UInt32)(1812433253 * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i);
}
/// <summary>
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
/// </summary>
[CLSCompliant(false)]
public override uint NextUInt32()
{
UInt32 y;
if (mti >= N)
{
GenRandAll();
mti = 0;
}
y = mt[mti++];
y ^= (y >> TEMPER3);
y ^= (y << TEMPER4) & TEMPER1;
y ^= (y << TEMPER5) & TEMPER2;
y ^= (y >> TEMPER6);
return y;
}
private void GenRandAll()
{
int kk = 1;
UInt32 y;
UInt32 p;
y = mt[0] & UPPER_MASK;
do
{
p = mt[kk];
mt[kk - 1] = mt[kk + (M - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
y = p & UPPER_MASK;
} while (++kk < N - M + 1);
do
{
p = mt[kk];
mt[kk - 1] = mt[kk + (M - N - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
y = p & UPPER_MASK;
} while (++kk < N);
p = mt[0];
mt[N - 1] = mt[M - 1] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
}
}
/// <summary>
/// RNGCryptoServiceProvider based random; very slow but cryptographically safe
/// </summary>
public class CryptoRandom : NetRandom
{
/// <summary>
/// Global instance of CryptoRandom
/// </summary>
public static new readonly CryptoRandom Instance = new CryptoRandom();
private RandomNumberGenerator m_rnd = new RNGCryptoServiceProvider();
/// <summary>
/// Seed in CryptoRandom does not create deterministic sequences
/// </summary>
[CLSCompliant(false)]
public override void Initialize(uint seed)
{
byte[] tmp = new byte[seed % 16];
m_rnd.GetBytes(tmp); // just prime it
}
/// <summary>
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
/// </summary>
[CLSCompliant(false)]
public override uint NextUInt32()
{
var bytes = new byte[4];
m_rnd.GetBytes(bytes);
return (uint)bytes[0] | (((uint)bytes[1]) << 8) | (((uint)bytes[2]) << 16) | (((uint)bytes[3]) << 24);
}
/// <summary>
/// Fill the specified buffer with random values
/// </summary>
public override void NextBytes(byte[] buffer)
{
m_rnd.GetBytes(buffer);
}
/// <summary>
/// Fills all bytes from offset to offset + length in buffer with random values
/// </summary>
public override void NextBytes(byte[] buffer, int offset, int length)
{
var bytes = new byte[length];
m_rnd.GetBytes(bytes);
Array.Copy(bytes, 0, buffer, offset, length);
}
}
}

View File

@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace Lidgren.Network
{
/// <summary>
/// NetRandom base class
/// </summary>
public abstract class NetRandom : Random
{
/// <summary>
/// Get global instance of NetRandom (uses MWCRandom)
/// </summary>
public static NetRandom Instance = new MWCRandom();
private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0);
/// <summary>
/// Constructor with randomized seed
/// </summary>
public NetRandom()
{
Initialize(NetRandomSeed.GetUInt32());
}
/// <summary>
/// Constructor with provided 32 bit seed
/// </summary>
public NetRandom(int seed)
{
Initialize((uint)seed);
}
/// <summary>
/// (Re)initialize this instance with provided 32 bit seed
/// </summary>
[CLSCompliant(false)]
public virtual void Initialize(uint seed)
{
// should be abstract, but non-CLS compliant methods can't be abstract!
throw new NotImplementedException("Implement this in inherited classes");
}
/// <summary>
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
/// </summary>
[CLSCompliant(false)]
public virtual uint NextUInt32()
{
// should be abstract, but non-CLS compliant methods can't be abstract!
throw new NotImplementedException("Implement this in inherited classes");
}
/// <summary>
/// Generates a random value that is greater or equal than 0 and less than Int32.MaxValue
/// </summary>
public override int Next()
{
var retval = (int)(0x7FFFFFFF & NextUInt32());
if (retval == 0x7FFFFFFF)
return NextInt32();
return retval;
}
/// <summary>
/// Generates a random value greater or equal than 0 and less or equal than Int32.MaxValue (inclusively)
/// </summary>
public int NextInt32()
{
return (int)(0x7FFFFFFF & NextUInt32());
}
/// <summary>
/// Returns random value larger or equal to 0.0 and less than 1.0
/// </summary>
public override double NextDouble()
{
return c_realUnitInt * NextInt32();
}
/// <summary>
/// Returns random value is greater or equal than 0.0 and less than 1.0
/// </summary>
protected override double Sample()
{
return c_realUnitInt * NextInt32();
}
/// <summary>
/// Returns random value is greater or equal than 0.0f and less than 1.0f
/// </summary>
public float NextSingle()
{
var retval = (float)(c_realUnitInt * NextInt32());
if (retval == 1.0f)
return NextSingle();
return retval;
}
/// <summary>
/// Returns a random value is greater or equal than 0 and less than maxValue
/// </summary>
public override int Next(int maxValue)
{
return (int)(NextDouble() * maxValue);
}
/// <summary>
/// Returns a random value is greater or equal than minValue and less than maxValue
/// </summary>
public override int Next(int minValue, int maxValue)
{
return minValue + (int)(NextDouble() * (double)(maxValue - minValue));
}
/// <summary>
/// Generates a random value between UInt64.MinValue to UInt64.MaxValue
/// </summary>
[CLSCompliant(false)]
public ulong NextUInt64()
{
ulong retval = NextUInt32();
retval |= NextUInt32() << 32;
return retval;
}
private uint m_boolValues;
private int m_nextBoolIndex;
/// <summary>
/// Returns true or false, randomly
/// </summary>
public bool NextBool()
{
if (m_nextBoolIndex >= 32)
{
m_boolValues = NextUInt32();
m_nextBoolIndex = 1;
}
var retval = ((m_boolValues >> m_nextBoolIndex) & 1) == 1;
m_nextBoolIndex++;
return retval;
}
/// <summary>
/// Fills all bytes from offset to offset + length in buffer with random values
/// </summary>
public virtual void NextBytes(byte[] buffer, int offset, int length)
{
int full = length / 4;
int ptr = offset;
for (int i = 0; i < full; i++)
{
uint r = NextUInt32();
buffer[ptr++] = (byte)r;
buffer[ptr++] = (byte)(r >> 8);
buffer[ptr++] = (byte)(r >> 16);
buffer[ptr++] = (byte)(r >> 24);
}
int rest = length - (full * 4);
for (int i = 0; i < rest; i++)
buffer[ptr++] = (byte)NextUInt32();
}
/// <summary>
/// Fill the specified buffer with random values
/// </summary>
public override void NextBytes(byte[] buffer)
{
NextBytes(buffer, 0, buffer.Length);
}
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Threading;
namespace Lidgren.Network
{
/// <summary>
/// Class for generating random seeds
/// </summary>
public static class NetRandomSeed
{
private static int m_seedIncrement = -1640531527;
/// <summary>
/// Generates a 32 bit random seed
/// </summary>
[CLSCompliant(false)]
public static uint GetUInt32()
{
ulong seed = GetUInt64();
uint low = (uint)seed;
uint high = (uint)(seed >> 32);
return low ^ high;
}
/// <summary>
/// Generates a 64 bit random seed
/// </summary>
[CLSCompliant(false)]
public static ulong GetUInt64()
{
#if !__ANDROID__ && !IOS && !UNITY_WEBPLAYER && !UNITY_ANDROID && !UNITY_IPHONE
ulong seed = (ulong)System.Diagnostics.Stopwatch.GetTimestamp();
seed ^= (ulong)Environment.WorkingSet;
ulong s2 = (ulong)Interlocked.Increment(ref m_seedIncrement);
s2 |= (((ulong)Guid.NewGuid().GetHashCode()) << 32);
seed ^= s2;
#else
ulong seed = (ulong)Environment.TickCount;
seed |= (((ulong)(new object().GetHashCode())) << 32);
ulong s2 = (ulong)Guid.NewGuid().GetHashCode();
s2 |= (((ulong)Interlocked.Increment(ref m_seedIncrement)) << 32);
seed ^= s2;
#endif
return seed;
}
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace Lidgren.Network
{
internal abstract class NetReceiverChannelBase
{
internal NetPeer m_peer;
internal NetConnection m_connection;
public NetReceiverChannelBase(NetConnection connection)
{
m_connection = connection;
m_peer = connection.m_peer;
}
internal abstract void ReceiveMessage(NetIncomingMessage msg);
}
}

View File

@ -0,0 +1,87 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetReliableOrderedReceiver : NetReceiverChannelBase
{
private int m_windowStart;
private int m_windowSize;
private NetBitVector m_earlyReceived;
internal NetIncomingMessage[] m_withheldMessages;
public NetReliableOrderedReceiver(NetConnection connection, int windowSize)
: base(connection)
{
m_windowSize = windowSize;
m_withheldMessages = new NetIncomingMessage[windowSize];
m_earlyReceived = new NetBitVector(windowSize);
}
private void AdvanceWindow()
{
m_earlyReceived.Set(m_windowStart % m_windowSize, false);
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
}
internal override void ReceiveMessage(NetIncomingMessage message)
{
int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart);
// ack no matter what
m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber);
if (relate == 0)
{
// Log("Received message #" + message.SequenceNumber + " right on time");
//
// excellent, right on time
//
//m_peer.LogVerbose("Received RIGHT-ON-TIME " + message);
AdvanceWindow();
m_peer.ReleaseMessage(message);
// release withheld messages
int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers;
while (m_earlyReceived[nextSeqNr % m_windowSize])
{
message = m_withheldMessages[nextSeqNr % m_windowSize];
NetException.Assert(message != null);
// remove it from withheld messages
m_withheldMessages[nextSeqNr % m_windowSize] = null;
m_peer.LogVerbose("Releasing withheld message #" + message);
m_peer.ReleaseMessage(message);
AdvanceWindow();
nextSeqNr++;
}
return;
}
if (relate < 0)
{
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
// duplicate
return;
}
// relate > 0 = early message
if (relate > m_windowSize)
{
// too early message!
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
return;
}
m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true);
m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart);
m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message;
}
}
}

View File

@ -0,0 +1,257 @@
using System;
using System.Threading;
namespace Lidgren.Network
{
/// <summary>
/// Sender part of Selective repeat ARQ for a particular NetChannel
/// </summary>
internal sealed class NetReliableSenderChannel : NetSenderChannelBase
{
private NetConnection m_connection;
private int m_windowStart;
private int m_windowSize;
private int m_sendStart;
private NetBitVector m_receivedAcks;
internal NetStoredReliableMessage[] m_storedMessages;
internal float m_resendDelay;
internal override int WindowSize { get { return m_windowSize; } }
internal NetReliableSenderChannel(NetConnection connection, int windowSize)
{
m_connection = connection;
m_windowSize = windowSize;
m_windowStart = 0;
m_sendStart = 0;
m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers);
m_storedMessages = new NetStoredReliableMessage[m_windowSize];
m_queuedSends = new NetQueue<NetOutgoingMessage>(8);
m_resendDelay = m_connection.GetResendDelay();
}
internal override int GetAllowedSends()
{
int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers;
NetException.Assert(retval >= 0 && retval <= m_windowSize);
return retval;
}
internal override void Reset()
{
m_receivedAcks.Clear();
for (int i = 0; i < m_storedMessages.Length; i++)
m_storedMessages[i].Reset();
m_queuedSends.Clear();
m_windowStart = 0;
m_sendStart = 0;
}
internal override NetSendResult Enqueue(NetOutgoingMessage message)
{
m_queuedSends.Enqueue(message);
if (m_queuedSends.Count <= GetAllowedSends())
return NetSendResult.Sent;
return NetSendResult.Queued;
}
// call this regularely
internal override void SendQueuedMessages(float now)
{
//
// resends
//
for (int i = 0; i < m_storedMessages.Length; i++)
{
NetOutgoingMessage om = m_storedMessages[i].Message;
if (om == null)
continue;
float t = m_storedMessages[i].LastSent;
if (t > 0 && (now - t) > m_resendDelay)
{
// deduce sequence number
/*
int startSlot = m_windowStart % m_windowSize;
int seqNr = m_windowStart;
while (startSlot != i)
{
startSlot--;
if (startSlot < 0)
startSlot = m_windowSize - 1;
seqNr--;
}
*/
//m_connection.m_peer.LogVerbose("Resending due to delay #" + m_storedMessages[i].SequenceNumber + " " + om.ToString());
m_connection.m_statistics.MessageResent(MessageResendReason.Delay);
m_connection.QueueSendMessage(om, m_storedMessages[i].SequenceNumber);
m_storedMessages[i].LastSent = now;
m_storedMessages[i].NumSent++;
}
}
int num = GetAllowedSends();
if (num < 1)
return;
// queued sends
while (m_queuedSends.Count > 0 && num > 0)
{
NetOutgoingMessage om;
if (m_queuedSends.TryDequeue(out om))
ExecuteSend(now, om);
num--;
NetException.Assert(num == GetAllowedSends());
}
}
private void ExecuteSend(float now, NetOutgoingMessage message)
{
int seqNr = m_sendStart;
m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers;
m_connection.QueueSendMessage(message, seqNr);
int storeIndex = seqNr % m_windowSize;
NetException.Assert(m_storedMessages[storeIndex].Message == null);
m_storedMessages[storeIndex].NumSent++;
m_storedMessages[storeIndex].Message = message;
m_storedMessages[storeIndex].LastSent = now;
m_storedMessages[storeIndex].SequenceNumber = seqNr;
return;
}
private void DestoreMessage(int storeIndex)
{
NetOutgoingMessage storedMessage = m_storedMessages[storeIndex].Message;
#if DEBUG
if (storedMessage == null)
throw new NetException("m_storedMessages[" + storeIndex + "].Message is null; sent " + m_storedMessages[storeIndex].NumSent + " times, last time " + (NetTime.Now - m_storedMessages[storeIndex].LastSent) + " seconds ago");
#else
if (storedMessage != null)
{
#endif
if (Interlocked.Decrement(ref storedMessage.m_recyclingCount) <= 0)
m_connection.m_peer.Recycle(storedMessage);
#if !DEBUG
}
#endif
m_storedMessages[storeIndex] = new NetStoredReliableMessage();
}
// remoteWindowStart is remote expected sequence number; everything below this has arrived properly
// seqNr is the actual nr received
internal override void ReceiveAcknowledge(float now, int seqNr)
{
// late (dupe), on time or early ack?
int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart);
if (relate < 0)
{
//m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
return; // late/duplicate ack
}
if (relate == 0)
{
//m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);
// ack arrived right on time
NetException.Assert(seqNr == m_windowStart);
m_receivedAcks[m_windowStart] = false;
DestoreMessage(m_windowStart % m_windowSize);
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
// advance window if we already have early acks
while (m_receivedAcks.Get(m_windowStart))
{
//m_connection.m_peer.LogDebug("Using early ack for #" + m_windowStart + "...");
m_receivedAcks[m_windowStart] = false;
DestoreMessage(m_windowStart % m_windowSize);
NetException.Assert(m_storedMessages[m_windowStart % m_windowSize].Message == null); // should already be destored
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
//m_connection.m_peer.LogDebug("Advancing window to #" + m_windowStart);
}
return;
}
//
// early ack... (if it has been sent!)
//
// If it has been sent either the m_windowStart message was lost
// ... or the ack for that message was lost
//
//m_connection.m_peer.LogDebug("Received early ack for #" + seqNr);
int sendRelate = NetUtility.RelativeSequenceNumber(seqNr, m_sendStart);
if (sendRelate <= 0)
{
// yes, we've sent this message - it's an early (but valid) ack
if (m_receivedAcks[seqNr])
{
// we've already destored/been acked for this message
}
else
{
m_receivedAcks[seqNr] = true;
}
}
else if (sendRelate > 0)
{
// uh... we haven't sent this message yet? Weird, dupe or error...
NetException.Assert(false, "Got ack for message not yet sent?");
return;
}
// Ok, lets resend all missing acks
int rnr = seqNr;
do
{
rnr--;
if (rnr < 0)
rnr = NetConstants.NumSequenceNumbers - 1;
if (m_receivedAcks[rnr])
{
// m_connection.m_peer.LogDebug("Not resending #" + rnr + " (since we got ack)");
}
else
{
int slot = rnr % m_windowSize;
NetException.Assert(m_storedMessages[slot].Message != null);
if (m_storedMessages[slot].NumSent == 1)
{
// just sent once; resend immediately since we found gap in ack sequence
NetOutgoingMessage rmsg = m_storedMessages[slot].Message;
//m_connection.m_peer.LogVerbose("Resending #" + rnr + " (" + rmsg + ")");
if (now - m_storedMessages[slot].LastSent < (m_resendDelay * 0.35f))
{
// already resent recently
}
else
{
m_storedMessages[slot].LastSent = now;
m_storedMessages[slot].NumSent++;
m_connection.m_statistics.MessageResent(MessageResendReason.HoleInSequence);
m_connection.QueueSendMessage(rmsg, rnr);
}
}
}
} while (rnr != m_windowStart);
}
}
}

View File

@ -0,0 +1,63 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetReliableSequencedReceiver : NetReceiverChannelBase
{
private int m_windowStart;
private int m_windowSize;
public NetReliableSequencedReceiver(NetConnection connection, int windowSize)
: base(connection)
{
m_windowSize = windowSize;
}
private void AdvanceWindow()
{
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
}
internal override void ReceiveMessage(NetIncomingMessage message)
{
int nr = message.m_sequenceNumber;
int relate = NetUtility.RelativeSequenceNumber(nr, m_windowStart);
// ack no matter what
m_connection.QueueAck(message.m_receivedMessageType, nr);
if (relate == 0)
{
// Log("Received message #" + message.SequenceNumber + " right on time");
//
// excellent, right on time
//
AdvanceWindow();
m_peer.ReleaseMessage(message);
return;
}
if (relate < 0)
{
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING LATE or DUPE");
return;
}
// relate > 0 = early message
if (relate > m_windowSize)
{
// too early message!
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
return;
}
// ok
m_windowStart = (m_windowStart + relate) % NetConstants.NumSequenceNumbers;
m_peer.ReleaseMessage(message);
return;
}
}
}

View File

@ -0,0 +1,87 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetReliableUnorderedReceiver : NetReceiverChannelBase
{
private int m_windowStart;
private int m_windowSize;
private NetBitVector m_earlyReceived;
public NetReliableUnorderedReceiver(NetConnection connection, int windowSize)
: base(connection)
{
m_windowSize = windowSize;
m_earlyReceived = new NetBitVector(windowSize);
}
private void AdvanceWindow()
{
m_earlyReceived.Set(m_windowStart % m_windowSize, false);
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
}
internal override void ReceiveMessage(NetIncomingMessage message)
{
int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart);
// ack no matter what
m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber);
if (relate == 0)
{
// Log("Received message #" + message.SequenceNumber + " right on time");
//
// excellent, right on time
//
//m_peer.LogVerbose("Received RIGHT-ON-TIME " + message);
AdvanceWindow();
m_peer.ReleaseMessage(message);
// release withheld messages
int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers;
while (m_earlyReceived[nextSeqNr % m_windowSize])
{
//message = m_withheldMessages[nextSeqNr % m_windowSize];
//NetException.Assert(message != null);
// remove it from withheld messages
//m_withheldMessages[nextSeqNr % m_windowSize] = null;
//m_peer.LogVerbose("Releasing withheld message #" + message);
//m_peer.ReleaseMessage(message);
AdvanceWindow();
nextSeqNr++;
}
return;
}
if (relate < 0)
{
// duplicate
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
return;
}
// relate > 0 = early message
if (relate > m_windowSize)
{
// too early message!
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
return;
}
m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true);
//m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart);
//m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message;
m_peer.ReleaseMessage(message);
}
}
}

204
Lidgren.Network/NetSRP.cs Normal file
View File

@ -0,0 +1,204 @@
#define USE_SHA256
using System;
using System.Security.Cryptography;
using System.Text;
namespace Lidgren.Network
{
/// <summary>
/// Helper methods for implementing SRP authentication
/// </summary>
public static class NetSRP
{
private static readonly NetBigInteger N = new NetBigInteger("0115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3", 16);
private static readonly NetBigInteger g = NetBigInteger.Two;
private static readonly NetBigInteger k = ComputeMultiplier();
private static HashAlgorithm GetHashAlgorithm()
{
#if USE_SHA256
// this does not seem to work as of yet
return SHA256.Create();
#else
return SHA1.Create();
#endif
}
/// <summary>
/// Compute multiplier (k)
/// </summary>
private static NetBigInteger ComputeMultiplier()
{
string one = NetUtility.ToHexString(N.ToByteArrayUnsigned());
string two = NetUtility.ToHexString(g.ToByteArrayUnsigned());
string ccstr = one + two.PadLeft(one.Length, '0');
byte[] cc = NetUtility.ToByteArray(ccstr);
var sha = GetHashAlgorithm();
var ccHashed = sha.ComputeHash(cc);
return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16);
}
/// <summary>
/// Create 16 bytes of random salt
/// </summary>
public static byte[] CreateRandomSalt()
{
byte[] retval = new byte[16];
CryptoRandom.Instance.NextBytes(retval);
return retval;
}
/// <summary>
/// Create 32 bytes of random ephemeral value
/// </summary>
public static byte[] CreateRandomEphemeral()
{
byte[] retval = new byte[32];
CryptoRandom.Instance.NextBytes(retval);
return retval;
}
/// <summary>
/// Computer private key (x)
/// </summary>
public static byte[] ComputePrivateKey(string username, string password, byte[] salt)
{
var sha = GetHashAlgorithm();
byte[] tmp = Encoding.UTF8.GetBytes(username + ":" + password);
byte[] innerHash = sha.ComputeHash(tmp);
byte[] total = new byte[innerHash.Length + salt.Length];
Buffer.BlockCopy(salt, 0, total, 0, salt.Length);
Buffer.BlockCopy(innerHash, 0, total, salt.Length, innerHash.Length);
// x ie. H(salt || H(username || ":" || password))
return new NetBigInteger(NetUtility.ToHexString(sha.ComputeHash(total)), 16).ToByteArrayUnsigned();
}
/// <summary>
/// Creates a verifier that the server can later use to authenticate users later on (v)
/// </summary>
public static byte[] ComputeServerVerifier(byte[] privateKey)
{
NetBigInteger x = new NetBigInteger(NetUtility.ToHexString(privateKey), 16);
// Verifier (v) = g^x (mod N)
var serverVerifier = g.ModPow(x, N);
return serverVerifier.ToByteArrayUnsigned();
}
/// <summary>
/// SHA hash data
/// </summary>
public static byte[] Hash(byte[] data)
{
var sha = GetHashAlgorithm();
return sha.ComputeHash(data);
}
/// <summary>
/// Compute client public ephemeral value (A)
/// </summary>
public static byte[] ComputeClientEphemeral(byte[] clientPrivateEphemeral) // a
{
// A= g^a (mod N)
NetBigInteger a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16);
NetBigInteger retval = g.ModPow(a, N);
return retval.ToByteArrayUnsigned();
}
/// <summary>
/// Compute server ephemeral value (B)
/// </summary>
public static byte[] ComputeServerEphemeral(byte[] serverPrivateEphemeral, byte[] verifier) // b
{
var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16);
var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16);
// B = kv + g^b (mod N)
var bb = g.ModPow(b, N);
var kv = v.Multiply(k);
var B = (kv.Add(bb)).Mod(N);
return B.ToByteArrayUnsigned();
}
/// <summary>
/// Compute intermediate value (u)
/// </summary>
public static byte[] ComputeU(byte[] clientPublicEphemeral, byte[] serverPublicEphemeral)
{
// u = SHA-1(A || B)
string one = NetUtility.ToHexString(clientPublicEphemeral);
string two = NetUtility.ToHexString(serverPublicEphemeral);
int len = 66; // Math.Max(one.Length, two.Length);
string ccstr = one.PadLeft(len, '0') + two.PadLeft(len, '0');
byte[] cc = NetUtility.ToByteArray(ccstr);
var sha = GetHashAlgorithm();
var ccHashed = sha.ComputeHash(cc);
return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16).ToByteArrayUnsigned();
}
/// <summary>
/// Computes the server session value
/// </summary>
public static byte[] ComputeServerSessionValue(byte[] clientPublicEphemeral, byte[] verifier, byte[] udata, byte[] serverPrivateEphemeral)
{
// S = (Av^u) ^ b (mod N)
var A = new NetBigInteger(NetUtility.ToHexString(clientPublicEphemeral), 16);
var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16);
var u = new NetBigInteger(NetUtility.ToHexString(udata), 16);
var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16);
NetBigInteger retval = v.ModPow(u, N).Multiply(A).Mod(N).ModPow(b, N).Mod(N);
return retval.ToByteArrayUnsigned();
}
/// <summary>
/// Computes the client session value
/// </summary>
public static byte[] ComputeClientSessionValue(byte[] serverPublicEphemeral, byte[] xdata, byte[] udata, byte[] clientPrivateEphemeral)
{
// (B - kg^x) ^ (a + ux) (mod N)
var B = new NetBigInteger(NetUtility.ToHexString(serverPublicEphemeral), 16);
var x = new NetBigInteger(NetUtility.ToHexString(xdata), 16);
var u = new NetBigInteger(NetUtility.ToHexString(udata), 16);
var a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16);
var bx = g.ModPow(x, N);
var btmp = B.Add(N.Multiply(k)).Subtract(bx.Multiply(k)).Mod(N);
return btmp.ModPow(x.Multiply(u).Add(a), N).ToByteArrayUnsigned();
}
/// <summary>
/// Create XTEA symmetrical encryption object from sessionValue
/// </summary>
public static NetXtea CreateEncryption(NetPeer peer, byte[] sessionValue)
{
var sha = GetHashAlgorithm();
var hash = sha.ComputeHash(sessionValue);
var key = new byte[16];
for(int i=0;i<16;i++)
{
key[i] = hash[i];
for (int j = 1; j < hash.Length / 16; j++)
key[i] ^= hash[i + (j * 16)];
}
return new NetXtea(peer, key);
}
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace Lidgren.Network
{
/// <summary>
/// Result of a SendMessage call
/// </summary>
public enum NetSendResult
{
/// <summary>
/// Message failed to enqueue because there is no connection
/// </summary>
FailedNotConnected = 0,
/// <summary>
/// Message was immediately sent
/// </summary>
Sent = 1,
/// <summary>
/// Message was queued for delivery
/// </summary>
Queued = 2,
/// <summary>
/// Message was dropped immediately since too many message were queued
/// </summary>
Dropped = 3
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace Lidgren.Network
{
internal abstract class NetSenderChannelBase
{
// access this directly to queue things in this channel
internal NetQueue<NetOutgoingMessage> m_queuedSends;
internal abstract int WindowSize { get; }
internal abstract int GetAllowedSends();
internal abstract NetSendResult Enqueue(NetOutgoingMessage message);
internal abstract void SendQueuedMessages(float now);
internal abstract void Reset();
internal abstract void ReceiveAcknowledge(float now, int sequenceNumber);
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
namespace Lidgren.Network
{
/// <summary>
/// Specialized version of NetPeer used for "server" peers
/// </summary>
public class NetServer : NetPeer
{
/// <summary>
/// NetServer constructor
/// </summary>
public NetServer(NetPeerConfiguration config)
: base(config)
{
config.AcceptIncomingConnections = true;
}
/// <summary>
/// Send a message to all connections
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="method">How to deliver the message</param>
public void SendToAll(NetOutgoingMessage msg, NetDeliveryMethod method)
{
var all = this.Connections;
if (all.Count <= 0)
return;
SendMessage(msg, all, method, 0);
}
/// <summary>
/// Send a message to all connections except one
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="method">How to deliver the message</param>
/// <param name="except">Don't send to this particular connection</param>
/// <param name="sequenceChannel">Which sequence channel to use for the message</param>
public void SendToAll(NetOutgoingMessage msg, NetConnection except, NetDeliveryMethod method, int sequenceChannel)
{
var all = this.Connections;
if (all.Count <= 0)
return;
if (except == null)
{
SendMessage(msg, all, method, sequenceChannel);
return;
}
List<NetConnection> recipients = new List<NetConnection>(all.Count - 1);
foreach (var conn in all)
if (conn != except)
recipients.Add(conn);
if (recipients.Count > 0)
SendMessage(msg, recipients, method, sequenceChannel);
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetServer " + ConnectionsCount + " connections]";
}
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace Lidgren.Network
{
internal struct NetStoredReliableMessage
{
public int NumSent;
public float LastSent;
public NetOutgoingMessage Message;
public int SequenceNumber;
public void Reset()
{
NumSent = 0;
LastSent = 0;
Message = null;
}
}
}

View File

@ -0,0 +1,61 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define IS_STOPWATCH_AVAILABLE
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Time service
/// </summary>
public static class NetTime
{
#if IS_STOPWATCH_AVAILABLE
private static readonly long s_timeInitialized = Stopwatch.GetTimestamp();
private static readonly double s_dInvFreq = 1.0 / (double)Stopwatch.Frequency;
/// <summary>
/// Get number of seconds since the application started
/// </summary>
public static double Now { get { return (double)(Stopwatch.GetTimestamp() - s_timeInitialized) * s_dInvFreq; } }
#else
private static readonly uint s_timeInitialized = (uint)Environment.TickCount;
/// <summary>
/// Get number of seconds since the application started
/// </summary>
public static double Now { get { return (double)((uint)Environment.TickCount - s_timeInitialized) / 1000.0; } }
#endif
/// <summary>
/// Given seconds it will output a human friendly readable string (milliseconds if less than 60 seconds)
/// </summary>
public static string ToReadable(double seconds)
{
if (seconds > 60)
return TimeSpan.FromSeconds(seconds).ToString();
return (seconds * 1000.0).ToString("N2") + " ms";
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
// replace with BCL 4.0 Tuple<> when appropriate
internal struct NetTuple<A, B>
{
public A Item1;
public B Item2;
public NetTuple(A item1, B item2)
{
Item1 = item1;
Item2 = item2;
}
}
}

266
Lidgren.Network/NetUPnP.cs Normal file
View File

@ -0,0 +1,266 @@
using System;
using System.IO;
using System.Xml;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Lidgren.Network
{
/// <summary>
/// Status of the UPnP capabilities
/// </summary>
public enum UPnPStatus
{
/// <summary>
/// Still discovering UPnP capabilities
/// </summary>
Discovering,
/// <summary>
/// UPnP is not available
/// </summary>
NotAvailable,
/// <summary>
/// UPnP is available and ready to use
/// </summary>
Available
}
/// <summary>
/// UPnP support class
/// </summary>
public class NetUPnP
{
private const int c_discoveryTimeOutMillis = 1000;
private string m_serviceUrl;
private string m_serviceName = "";
private NetPeer m_peer;
private ManualResetEvent m_discoveryComplete = new ManualResetEvent(false);
internal float m_discoveryResponseDeadline;
private UPnPStatus m_status;
/// <summary>
/// Status of the UPnP capabilities of this NetPeer
/// </summary>
public UPnPStatus Status { get { return m_status; } }
/// <summary>
/// NetUPnP constructor
/// </summary>
public NetUPnP(NetPeer peer)
{
m_peer = peer;
m_discoveryResponseDeadline = float.MinValue;
}
internal void Discover(NetPeer peer)
{
string str =
"M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"ST:upnp:rootdevice\r\n" +
"MAN:\"ssdp:discover\"\r\n" +
"MX:3\r\n\r\n";
m_status = UPnPStatus.Discovering;
byte[] arr = System.Text.Encoding.UTF8.GetBytes(str);
m_peer.LogDebug("Attempting UPnP discovery");
peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
peer.RawSend(arr, 0, arr.Length, new IPEndPoint(IPAddress.Broadcast, 1900));
peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
// allow some extra time for router to respond
// System.Threading.Thread.Sleep(50);
m_discoveryResponseDeadline = (float)NetTime.Now + 6.0f; // arbitrarily chosen number, router gets 6 seconds to respond
m_status = UPnPStatus.Discovering;
}
internal void ExtractServiceUrl(string resp)
{
#if !DEBUG
try
{
#endif
XmlDocument desc = new XmlDocument();
desc.Load(WebRequest.Create(resp).GetResponse().GetResponseStream());
XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
if (!typen.Value.Contains("InternetGatewayDevice"))
return;
m_serviceName = "WANIPConnection";
XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr);
if (node == null)
{
//try another service name
m_serviceName = "WANPPPConnection";
node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr);
if (node == null)
return;
}
m_serviceUrl = CombineUrls(resp, node.Value);
m_peer.LogDebug("UPnP service ready");
m_status = UPnPStatus.Available;
m_discoveryComplete.Set();
#if !DEBUG
}
catch
{
m_peer.LogVerbose("Exception ignored trying to parse UPnP XML response");
return;
}
#endif
}
private static string CombineUrls(string gatewayURL, string subURL)
{
// Is Control URL an absolute URL?
if ((subURL.Contains("http:")) || (subURL.Contains(".")))
return subURL;
gatewayURL = gatewayURL.Replace("http://", ""); // strip any protocol
int n = gatewayURL.IndexOf("/");
if (n != -1)
gatewayURL = gatewayURL.Substring(0, n); // Use first portion of URL
return "http://" + gatewayURL + subURL;
}
private bool CheckAvailability()
{
switch (m_status)
{
case UPnPStatus.NotAvailable:
return false;
case UPnPStatus.Available:
return true;
case UPnPStatus.Discovering:
if (m_discoveryComplete.WaitOne(c_discoveryTimeOutMillis))
return true;
if (NetTime.Now > m_discoveryResponseDeadline)
m_status = UPnPStatus.NotAvailable;
return false;
}
return false;
}
/// <summary>
/// Add a forwarding rule to the router using UPnP
/// </summary>
public bool ForwardPort(int port, string description)
{
if (!CheckAvailability())
return false;
IPAddress mask;
var client = NetUtility.GetMyAddress(out mask);
if (client == null)
return false;
try
{
SOAPRequest(m_serviceUrl,
"<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\">" +
"<NewRemoteHost></NewRemoteHost>" +
"<NewExternalPort>" + port.ToString() + "</NewExternalPort>" +
"<NewProtocol>" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "</NewProtocol>" +
"<NewInternalPort>" + port.ToString() + "</NewInternalPort>" +
"<NewInternalClient>" + client.ToString() + "</NewInternalClient>" +
"<NewEnabled>1</NewEnabled>" +
"<NewPortMappingDescription>" + description + "</NewPortMappingDescription>" +
"<NewLeaseDuration>0</NewLeaseDuration>" +
"</u:AddPortMapping>",
"AddPortMapping");
m_peer.LogDebug("Sent UPnP port forward request");
System.Threading.Thread.Sleep(50);
}
catch (Exception ex)
{
m_peer.LogWarning("UPnP port forward failed: " + ex.Message);
return false;
}
return true;
}
/// <summary>
/// Delete a forwarding rule from the router using UPnP
/// </summary>
public bool DeleteForwardingRule(int port)
{
if (!CheckAvailability())
return false;
try
{
SOAPRequest(m_serviceUrl,
"<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\">" +
"<NewRemoteHost>" +
"</NewRemoteHost>" +
"<NewExternalPort>" + port + "</NewExternalPort>" +
"<NewProtocol>" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "</NewProtocol>" +
"</u:DeletePortMapping>", "DeletePortMapping");
return true;
}
catch (Exception ex)
{
m_peer.LogWarning("UPnP delete forwarding rule failed: " + ex.Message);
return false;
}
}
/// <summary>
/// Retrieve the extern ip using UPnP
/// </summary>
public IPAddress GetExternalIP()
{
if (!CheckAvailability())
return null;
try
{
XmlDocument xdoc = SOAPRequest(m_serviceUrl, "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\">" +
"</u:GetExternalIPAddress>", "GetExternalIPAddress");
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value;
return IPAddress.Parse(IP);
}
catch (Exception ex)
{
m_peer.LogWarning("Failed to get external IP: " + ex.Message);
return null;
}
}
private XmlDocument SOAPRequest(string url, string soap, string function)
{
string req = "<?xml version=\"1.0\"?>" +
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
"<s:Body>" +
soap +
"</s:Body>" +
"</s:Envelope>";
WebRequest r = HttpWebRequest.Create(url);
r.Method = "POST";
byte[] b = System.Text.Encoding.UTF8.GetBytes(req);
r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:" + m_serviceName + ":1#" + function + "\"");
r.ContentType = "text/xml; charset=\"utf-8\"";
r.ContentLength = b.Length;
r.GetRequestStream().Write(b, 0, b.Length);
XmlDocument resp = new XmlDocument();
WebResponse wres = r.GetResponse();
Stream ress = wres.GetResponseStream();
resp.Load(ress);
return resp;
}
}
}

View File

@ -0,0 +1,128 @@
using System;
using System.Threading;
namespace Lidgren.Network
{
/// <summary>
/// Sender part of Selective repeat ARQ for a particular NetChannel
/// </summary>
internal sealed class NetUnreliableSenderChannel : NetSenderChannelBase
{
private NetConnection m_connection;
private int m_windowStart;
private int m_windowSize;
private int m_sendStart;
private NetBitVector m_receivedAcks;
internal override int WindowSize { get { return m_windowSize; } }
internal NetUnreliableSenderChannel(NetConnection connection, int windowSize)
{
m_connection = connection;
m_windowSize = windowSize;
m_windowStart = 0;
m_sendStart = 0;
m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers);
m_queuedSends = new NetQueue<NetOutgoingMessage>(8);
}
internal override int GetAllowedSends()
{
int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % m_windowSize;
NetException.Assert(retval >= 0 && retval <= m_windowSize);
return retval;
}
internal override void Reset()
{
m_receivedAcks.Clear();
m_queuedSends.Clear();
m_windowStart = 0;
m_sendStart = 0;
}
internal override NetSendResult Enqueue(NetOutgoingMessage message)
{
int queueLen = m_queuedSends.Count + 1;
int left = GetAllowedSends();
if (queueLen > left || (message.LengthBytes > m_connection.m_currentMTU && m_connection.m_peerConfiguration.UnreliableSizeBehaviour == NetUnreliableSizeBehaviour.DropAboveMTU))
{
m_connection.Peer.Recycle(message);
return NetSendResult.Dropped;
}
m_queuedSends.Enqueue(message);
return NetSendResult.Sent;
}
// call this regularely
internal override void SendQueuedMessages(float now)
{
int num = GetAllowedSends();
if (num < 1)
return;
// queued sends
while (m_queuedSends.Count > 0 && num > 0)
{
NetOutgoingMessage om;
if (m_queuedSends.TryDequeue(out om))
ExecuteSend(om);
num--;
}
}
private void ExecuteSend(NetOutgoingMessage message)
{
m_connection.m_peer.VerifyNetworkThread();
int seqNr = m_sendStart;
m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers;
m_connection.QueueSendMessage(message, seqNr);
Interlocked.Decrement(ref message.m_recyclingCount);
if (message.m_recyclingCount <= 0)
m_connection.m_peer.Recycle(message);
return;
}
// remoteWindowStart is remote expected sequence number; everything below this has arrived properly
// seqNr is the actual nr received
internal override void ReceiveAcknowledge(float now, int seqNr)
{
// late (dupe), on time or early ack?
int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart);
if (relate < 0)
{
//m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
return; // late/duplicate ack
}
if (relate == 0)
{
//m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);
// ack arrived right on time
NetException.Assert(seqNr == m_windowStart);
m_receivedAcks[m_windowStart] = false;
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
return;
}
// Advance window to this position
m_receivedAcks[seqNr] = true;
while (m_windowStart != seqNr)
{
m_receivedAcks[m_windowStart] = false;
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
}
}
}
}

View File

@ -0,0 +1,29 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetUnreliableSequencedReceiver : NetReceiverChannelBase
{
private int m_lastReceivedSequenceNumber = -1;
public NetUnreliableSequencedReceiver(NetConnection connection)
: base(connection)
{
}
internal override void ReceiveMessage(NetIncomingMessage msg)
{
int nr = msg.m_sequenceNumber;
// ack no matter what
m_connection.QueueAck(msg.m_receivedMessageType, nr);
int relate = NetUtility.RelativeSequenceNumber(nr, m_lastReceivedSequenceNumber + 1);
if (relate < 0)
return; // drop if late
m_lastReceivedSequenceNumber = nr;
m_peer.ReleaseMessage(msg);
}
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetUnreliableUnorderedReceiver : NetReceiverChannelBase
{
public NetUnreliableUnorderedReceiver(NetConnection connection)
: base(connection)
{
}
internal override void ReceiveMessage(NetIncomingMessage msg)
{
// ack no matter what
m_connection.QueueAck(msg.m_receivedMessageType, msg.m_sequenceNumber);
m_peer.ReleaseMessage(msg);
}
}
}

View File

@ -0,0 +1,632 @@
/* Copyright (c) 2010 Michael Lidgren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !__ANDROID__ && !IOS && !UNITY_WEBPLAYER && !UNITY_ANDROID && !UNITY_IPHONE
#define IS_FULL_NET_AVAILABLE
#endif
using System;
using System.Net;
#if IS_FULL_NET_AVAILABLE
using System.Net.NetworkInformation;
#endif
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace Lidgren.Network
{
/// <summary>
/// Utility methods
/// </summary>
public static class NetUtility
{
/// <summary>
/// Resolve endpoint callback
/// </summary>
public delegate void ResolveEndPointCallback(IPEndPoint endPoint);
/// <summary>
/// Resolve address callback
/// </summary>
public delegate void ResolveAddressCallback(IPAddress adr);
/// <summary>
/// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number (asynchronous version)
/// </summary>
public static void ResolveAsync(string ipOrHost, int port, ResolveEndPointCallback callback)
{
ResolveAsync(ipOrHost, delegate(IPAddress adr)
{
if (adr == null)
{
callback(null);
}
else
{
callback(new IPEndPoint(adr, port));
}
});
}
/// <summary>
/// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number
/// </summary>
public static IPEndPoint Resolve(string ipOrHost, int port)
{
IPAddress adr = Resolve(ipOrHost);
return new IPEndPoint(adr, port);
}
/// <summary>
/// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname (asynchronous version)
/// </summary>
public static void ResolveAsync(string ipOrHost, ResolveAddressCallback callback)
{
if (string.IsNullOrEmpty(ipOrHost))
throw new ArgumentException("Supplied string must not be empty", "ipOrHost");
ipOrHost = ipOrHost.Trim();
IPAddress ipAddress = null;
if (IPAddress.TryParse(ipOrHost, out ipAddress))
{
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
callback(ipAddress);
return;
}
throw new ArgumentException("This method will not currently resolve other than ipv4 addresses");
}
// ok must be a host name
IPHostEntry entry;
try
{
Dns.BeginGetHostEntry(ipOrHost, delegate(IAsyncResult result)
{
try
{
entry = Dns.EndGetHostEntry(result);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.HostNotFound)
{
//LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost));
callback(null);
return;
}
else
{
throw;
}
}
if (entry == null)
{
callback(null);
return;
}
// check each entry for a valid IP address
foreach (IPAddress ipCurrent in entry.AddressList)
{
if (ipCurrent.AddressFamily == AddressFamily.InterNetwork)
{
callback(ipCurrent);
return;
}
}
callback(null);
}, null);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.HostNotFound)
{
//LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost));
callback(null);
}
else
{
throw;
}
}
}
/// <summary>
/// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname
/// </summary>
public static IPAddress Resolve(string ipOrHost)
{
if (string.IsNullOrEmpty(ipOrHost))
throw new ArgumentException("Supplied string must not be empty", "ipOrHost");
ipOrHost = ipOrHost.Trim();
IPAddress ipAddress = null;
if (IPAddress.TryParse(ipOrHost, out ipAddress))
{
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
return ipAddress;
throw new ArgumentException("This method will not currently resolve other than ipv4 addresses");
}
// ok must be a host name
try
{
var addresses = Dns.GetHostAddresses(ipOrHost);
if (addresses == null)
return null;
foreach (var address in addresses)
{
if (address.AddressFamily == AddressFamily.InterNetwork)
return address;
}
return null;
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.HostNotFound)
{
//LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost));
return null;
}
else
{
throw;
}
}
}
#if IS_FULL_NET_AVAILABLE
private static NetworkInterface GetNetworkInterface()
{
IPGlobalProperties computerProperties = IPGlobalProperties.GetIPGlobalProperties();
if (computerProperties == null)
return null;
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
if (nics == null || nics.Length < 1)
return null;
NetworkInterface best = null;
foreach (NetworkInterface adapter in nics)
{
if (adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback || adapter.NetworkInterfaceType == NetworkInterfaceType.Unknown)
continue;
if (!adapter.Supports(NetworkInterfaceComponent.IPv4))
continue;
if (best == null)
best = adapter;
if (adapter.OperationalStatus != OperationalStatus.Up)
continue;
// make sure this adapter has any ipv4 addresses
IPInterfaceProperties properties = adapter.GetIPProperties();
foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
{
if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
{
// Yes it does, return this network interface.
return adapter;
}
}
}
return best;
}
/// <summary>
/// Returns the physical (MAC) address for the first usable network interface
/// </summary>
public static PhysicalAddress GetMacAddress()
{
NetworkInterface ni = GetNetworkInterface();
if (ni == null)
return null;
return ni.GetPhysicalAddress();
}
#endif
/// <summary>
/// Create a hex string from an Int64 value
/// </summary>
public static string ToHexString(long data)
{
return ToHexString(BitConverter.GetBytes(data));
}
/// <summary>
/// Create a hex string from an array of bytes
/// </summary>
public static string ToHexString(byte[] data)
{
return ToHexString(data, 0, data.Length);
}
/// <summary>
/// Create a hex string from an array of bytes
/// </summary>
public static string ToHexString(byte[] data, int offset, int length)
{
char[] c = new char[length * 2];
byte b;
for (int i = 0; i < length; ++i)
{
b = ((byte)(data[offset + i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(data[offset + i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
/// <summary>
/// Gets the local broadcast address
/// </summary>
public static IPAddress GetBroadcastAddress()
{
#if __ANDROID__
try{
Android.Net.Wifi.WifiManager wifi = (Android.Net.Wifi.WifiManager)Android.App.Application.Context.GetSystemService(Android.App.Activity.WifiService);
if (wifi.IsWifiEnabled)
{
var dhcp = wifi.DhcpInfo;
int broadcast = (dhcp.IpAddress & dhcp.Netmask) | ~dhcp.Netmask;
byte[] quads = new byte[4];
for (int k = 0; k < 4; k++)
{
quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
}
return new IPAddress(quads);
}
}
catch // Catch Access Denied Errors
{
return IPAddress.Broadcast;
}
#endif
#if IS_FULL_NET_AVAILABLE
try
{
NetworkInterface ni = GetNetworkInterface();
if (ni == null)
{
return null;
}
IPInterfaceProperties properties = ni.GetIPProperties();
foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
{
if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
{
var mask = unicastAddress.IPv4Mask;
byte[] ipAdressBytes = unicastAddress.Address.GetAddressBytes();
byte[] subnetMaskBytes = mask.GetAddressBytes();
if (ipAdressBytes.Length != subnetMaskBytes.Length)
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
for (int i = 0; i < broadcastAddress.Length; i++)
{
broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
}
return new IPAddress(broadcastAddress);
}
}
}
catch // Catch any errors
{
return IPAddress.Broadcast;
}
#endif
return IPAddress.Broadcast;
}
/// <summary>
/// Gets my local IPv4 address (not necessarily external) and subnet mask
/// </summary>
public static IPAddress GetMyAddress(out IPAddress mask)
{
mask = null;
#if __ANDROID__
try
{
Android.Net.Wifi.WifiManager wifi = (Android.Net.Wifi.WifiManager)Android.App.Application.Context.GetSystemService(Android.App.Activity.WifiService);
if (!wifi.IsWifiEnabled) return null;
var dhcp = wifi.DhcpInfo;
int addr = dhcp.IpAddress;
byte[] quads = new byte[4];
for (int k = 0; k < 4; k++)
{
quads[k] = (byte) ((addr >> k * 8) & 0xFF);
}
return new IPAddress(quads);
}
catch // Catch Access Denied errors
{
return null;
}
#endif
#if IS_FULL_NET_AVAILABLE
NetworkInterface ni = GetNetworkInterface();
if (ni == null)
{
mask = null;
return null;
}
IPInterfaceProperties properties = ni.GetIPProperties();
foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
{
if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
{
mask = unicastAddress.IPv4Mask;
return unicastAddress.Address;
}
}
#endif
return null;
}
/// <summary>
/// Returns true if the IPEndPoint supplied is on the same subnet as this host
/// </summary>
public static bool IsLocal(IPEndPoint endPoint)
{
if (endPoint == null)
return false;
return IsLocal(endPoint.Address);
}
/// <summary>
/// Returns true if the IPAddress supplied is on the same subnet as this host
/// </summary>
public static bool IsLocal(IPAddress remote)
{
IPAddress mask;
IPAddress local = GetMyAddress(out mask);
if (mask == null)
return false;
uint maskBits = BitConverter.ToUInt32(mask.GetAddressBytes(), 0);
uint remoteBits = BitConverter.ToUInt32(remote.GetAddressBytes(), 0);
uint localBits = BitConverter.ToUInt32(local.GetAddressBytes(), 0);
// compare network portions
return ((remoteBits & maskBits) == (localBits & maskBits));
}
/// <summary>
/// Returns how many bits are necessary to hold a certain number
/// </summary>
[CLSCompliant(false)]
public static int BitsToHoldUInt(uint value)
{
int bits = 1;
while ((value >>= 1) != 0)
bits++;
return bits;
}
/// <summary>
/// Returns how many bytes are required to hold a certain number of bits
/// </summary>
public static int BytesToHoldBits(int numBits)
{
return (numBits + 7) / 8;
}
internal static UInt32 SwapByteOrder(UInt32 value)
{
return
((value & 0xff000000) >> 24) |
((value & 0x00ff0000) >> 8) |
((value & 0x0000ff00) << 8) |
((value & 0x000000ff) << 24);
}
internal static UInt64 SwapByteOrder(UInt64 value)
{
return
((value & 0xff00000000000000L) >> 56) |
((value & 0x00ff000000000000L) >> 40) |
((value & 0x0000ff0000000000L) >> 24) |
((value & 0x000000ff00000000L) >> 8) |
((value & 0x00000000ff000000L) << 8) |
((value & 0x0000000000ff0000L) << 24) |
((value & 0x000000000000ff00L) << 40) |
((value & 0x00000000000000ffL) << 56);
}
internal static bool CompareElements(byte[] one, byte[] two)
{
if (one.Length != two.Length)
return false;
for (int i = 0; i < one.Length; i++)
if (one[i] != two[i])
return false;
return true;
}
/// <summary>
/// Convert a hexadecimal string to a byte array
/// </summary>
public static byte[] ToByteArray(String hexString)
{
byte[] retval = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
return retval;
}
/// <summary>
/// Converts a number of bytes to a shorter, more readable string representation
/// </summary>
public static string ToHumanReadable(long bytes)
{
if (bytes < 4000) // 1-4 kb is printed in bytes
return bytes + " bytes";
if (bytes < 1000 * 1000) // 4-999 kb is printed in kb
return Math.Round(((double)bytes / 1000.0), 2) + " kilobytes";
return Math.Round(((double)bytes / (1000.0 * 1000.0)), 2) + " megabytes"; // else megabytes
}
internal static int RelativeSequenceNumber(int nr, int expected)
{
return (nr - expected + NetConstants.NumSequenceNumbers + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers - (NetConstants.NumSequenceNumbers / 2);
// old impl:
//int retval = ((nr + NetConstants.NumSequenceNumbers) - expected) % NetConstants.NumSequenceNumbers;
//if (retval > (NetConstants.NumSequenceNumbers / 2))
// retval -= NetConstants.NumSequenceNumbers;
//return retval;
}
/// <summary>
/// Gets the window size used internally in the library for a certain delivery method
/// </summary>
public static int GetWindowSize(NetDeliveryMethod method)
{
switch (method)
{
case NetDeliveryMethod.Unknown:
return 0;
case NetDeliveryMethod.Unreliable:
case NetDeliveryMethod.UnreliableSequenced:
return NetConstants.UnreliableWindowSize;
case NetDeliveryMethod.ReliableOrdered:
return NetConstants.ReliableOrderedWindowSize;
case NetDeliveryMethod.ReliableSequenced:
case NetDeliveryMethod.ReliableUnordered:
default:
return NetConstants.DefaultWindowSize;
}
}
// shell sort
internal static void SortMembersList(System.Reflection.MemberInfo[] list)
{
int h;
int j;
System.Reflection.MemberInfo tmp;
h = 1;
while (h * 3 + 1 <= list.Length)
h = 3 * h + 1;
while (h > 0)
{
for (int i = h - 1; i < list.Length; i++)
{
tmp = list[i];
j = i;
while (true)
{
if (j >= h)
{
if (string.Compare(list[j - h].Name, tmp.Name, StringComparison.InvariantCulture) > 0)
{
list[j] = list[j - h];
j -= h;
}
else
break;
}
else
break;
}
list[j] = tmp;
}
h /= 3;
}
}
internal static NetDeliveryMethod GetDeliveryMethod(NetMessageType mtp)
{
if (mtp >= NetMessageType.UserReliableOrdered1)
return NetDeliveryMethod.ReliableOrdered;
else if (mtp >= NetMessageType.UserReliableSequenced1)
return NetDeliveryMethod.ReliableSequenced;
else if (mtp >= NetMessageType.UserReliableUnordered)
return NetDeliveryMethod.ReliableUnordered;
else if (mtp >= NetMessageType.UserSequenced1)
return NetDeliveryMethod.UnreliableSequenced;
return NetDeliveryMethod.Unreliable;
}
/// <summary>
/// Creates a comma delimited string from a lite of items
/// </summary>
public static string MakeCommaDelimitedList<T>(IList<T> list)
{
var cnt = list.Count;
StringBuilder bdr = new StringBuilder(cnt * 5); // educated guess
for(int i=0;i<cnt;i++)
{
bdr.Append(list[i].ToString());
if (i != cnt - 1)
bdr.Append(", ");
}
return bdr.ToString();
}
/// <summary>
/// Create a SHA1 digest from a string
/// </summary>
public static byte[] CreateSHA1Hash(string key)
{
using (var sha = new SHA1CryptoServiceProvider())
return sha.ComputeHash(Encoding.UTF8.GetBytes(key));
}
/// <summary>
/// Create a SHA1 digest from a byte buffer
/// </summary>
public static byte[] CreateSHA1Hash(byte[] data)
{
using (var sha = new SHA1CryptoServiceProvider())
return sha.ComputeHash(data);
}
/// <summary>
/// Create a SHA1 digest from a byte buffer
/// </summary>
public static byte[] CreateSHA1Hash(byte[] data, int offset, int count)
{
using (var sha = new SHA1CryptoServiceProvider())
return sha.ComputeHash(data, offset, count);
}
}
}

View File

@ -0,0 +1,37 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Lidgren.Network")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Lidgren.Network")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("78f2a988-46e1-4fc4-b7b4-dd2cbe46da6a")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2012.1.7.0")]
[assembly: AssemblyFileVersion("2012.1.7.0")]
[assembly: System.CLSCompliant(true)]

286
Neoforce/ArchiveManager.cs Normal file
View File

@ -0,0 +1,286 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: ArchiveManager.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework.Content;
using TomShane.Neoforce.External.Zip;
using System.Globalization;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
#region //// Classes ///////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ArchiveManager.xml' path='ArchiveManager/Class[@name="ArchiveManager"]/*' />
public class ArchiveManager : ContentManager
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private string archivePath = null;
private ZipFile archive = null;
private bool useArchive = false;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ArchiveManager.xml' path='ArchiveManager/Member[@name="ArchivePath"]/*' />
public virtual string ArchivePath
{
get { return archivePath; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public bool UseArchive
{
get { return useArchive; }
set { useArchive = value; }
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Constructors //////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ArchiveManager.xml' path='ArchiveManager/Member[@name="ArchiveManager"]/*' />
public ArchiveManager(IServiceProvider serviceProvider) : this(serviceProvider, null) { }
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ArchiveManager.xml' path='ArchiveManager/Member[@name="ArchiveManager1"]/*' />
public ArchiveManager(IServiceProvider serviceProvider, string archive): base(serviceProvider)
{
if (archive != null)
{
this.archive = ZipFile.Read(archive);
archivePath = archive;
useArchive = true;
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ArchiveManager.xml' path='ArchiveManager/Member[@name="OpenStream"]/*' />
protected override Stream OpenStream(string assetName)
{
if (useArchive && archive != null)
{
assetName = assetName.Replace("\\", "/");
if (assetName.StartsWith("/")) assetName = assetName.Remove(0, 1);
string fullAssetName = (assetName + ".xnb").ToLower();
foreach (ZipEntry entry in archive)
{
ZipDirEntry ze = new ZipDirEntry(entry);
string entryName = entry.FileName.ToLower();
if (entryName == fullAssetName)
{
return entry.GetStream();
}
}
throw new Exception("Cannot find asset \"" + assetName + "\" in the archive.");
}
else
{
return base.OpenStream(assetName);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ArchiveManager.xml' path='ArchiveManager/Member[@name="GetAssetNames"]/*' />
public string[] GetAssetNames()
{
if (useArchive && archive != null)
{
List<string> filenames = new List<string>();
foreach (ZipEntry entry in archive)
{
string name = entry.FileName;
if (name.EndsWith(".xnb"))
{
name = name.Remove(name.Length - 4, 4);
filenames.Add(name);
}
}
return filenames.ToArray();
}
else
{
return null;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ArchiveManager.xml' path='ArchiveManager/Member[@name="GetAssetNames1"]/*' />
public string[] GetAssetNames(string path)
{
if (useArchive && archive != null)
{
if (path != null && path != "" && path != "\\" && path != "/")
{
List<string> filenames = new List<string>();
foreach (ZipEntry entry in archive)
{
string name = entry.FileName;
if (name.EndsWith(".xnb"))
{
name = name.Remove(name.Length - 4, 4);
}
string[] parts = name.Split('/');
string dir = "";
for (int i = 0; i < parts.Length - 1; i++)
{
dir += parts[i] + '/';
}
path = path.Replace("\\", "/");
if (path.StartsWith("/")) path = path.Remove(0, 1);
if (!path.EndsWith("/")) path += '/';
if (dir.ToLower() == path.ToLower() && !name.EndsWith("/"))
{
filenames.Add(name);
}
}
return filenames.ToArray();
}
else
{
return GetAssetNames();
}
}
else
{
return null;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ArchiveManager.xml' path='ArchiveManager/Member[@name="GetFileStream"]/*' />
public Stream GetFileStream(string filename)
{
if (useArchive && archive != null)
{
filename = filename.Replace("\\", "/").ToLower();
if (filename.StartsWith("/")) filename = filename.Remove(0, 1);
foreach (ZipEntry entry in archive)
{
string entryName = entry.FileName.ToLower();
if (entryName.Equals(filename))
return entry.GetStream();
}
throw new Exception("Cannot find file \"" + filename + "\" in the archive.");
}
else
{
return null;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public string[] GetDirectories(string path)
{
if (useArchive && archive != null)
{
if (path != null && path != "" && path != "\\" && path != "/")
{
List<string> dirs = new List<string>();
path = path.Replace("\\", "/");
if (path.StartsWith("/")) path = path.Remove(0, 1);
if (!path.EndsWith("/")) path += '/';
foreach (ZipEntry entry in archive)
{
string name = entry.FileName;
if (name.ToLower().StartsWith(path.ToLower()))
{
int i = name.IndexOf("/", path.Length);
string item = name.Substring(path.Length, i - path.Length) + "\\";
if (!dirs.Contains(item))
{
dirs.Add(item);
}
}
}
return dirs.ToArray();
}
else
{
return GetAssetNames();
}
}
else if (Directory.Exists(path))
{
string[] dirs = Directory.GetDirectories(path);
for (int i = 0; i < dirs.Length; i++)
{
string[] parts = dirs[i].Split('\\');
dirs[i] = parts[parts.Length - 1] + '\\';
}
return dirs;
}
else return null;
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
////////////////////////////////////////////////////////////////////////////
#endregion
}

306
Neoforce/Bevel.cs Normal file
View File

@ -0,0 +1,306 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: Bevel.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
using Microsoft.Xna.Framework;
////////////////////////////////////////////////////////////////////////////
using Microsoft.Xna.Framework.Graphics;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
#region //// Enums /////////////
////////////////////////////////////////////////////////////////////////////
public enum BevelStyle
{
None,
Flat,
Etched,
Bumped,
Lowered,
Raised
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public enum BevelBorder
{
None,
Left,
Top,
Right,
Bottom,
All
}
////////////////////////////////////////////////////////////////////////////
#endregion
public class Bevel: Control
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private BevelBorder border = BevelBorder.All;
private BevelStyle style = BevelStyle.Etched;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
public BevelBorder Border
{
get { return border; }
set
{
if (border != value)
{
border = value;
if (!Suspended) OnBorderChanged(new EventArgs());
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public BevelStyle Style
{
get { return style; }
set
{
if (style != value)
{
style = value;
if (!Suspended) OnStyleChanged(new EventArgs());
}
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Events ////////////
////////////////////////////////////////////////////////////////////////////
public event EventHandler BorderChanged;
public event EventHandler StyleChanged;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Construstors //////
////////////////////////////////////////////////////////////////////////////
public Bevel(Manager manager): base(manager)
{
CanFocus = false;
Passive = true;
Width = 64;
Height = 64;
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void InitSkin()
{
base.InitSkin();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
if (Border != BevelBorder.None && Style != BevelStyle.None)
{
if (Border != BevelBorder.All)
{
DrawPart(renderer, rect, Border, Style, false);
}
else
{
DrawPart(renderer, rect, BevelBorder.Left, Style, true);
DrawPart(renderer, rect, BevelBorder.Top, Style, true);
DrawPart(renderer, rect, BevelBorder.Right, Style, true);
DrawPart(renderer, rect, BevelBorder.Bottom, Style, true);
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void DrawPart(Renderer renderer, Rectangle rect, BevelBorder pos, BevelStyle style, bool all)
{
SkinLayer layer = Skin.Layers["Control"];
Color c1 = Utilities.ParseColor(layer.Attributes["LightColor"].Value);
Color c2 = Utilities.ParseColor(layer.Attributes["DarkColor"].Value);
Color c3 = Utilities.ParseColor(layer.Attributes["FlatColor"].Value);
if (Color != UndefinedColor) c3 = Color;
Texture2D img = Skin.Layers["Control"].Image.Resource;
int x1 = 0; int y1 = 0; int w1 = 0; int h1 = 0;
int x2 = 0; int y2 = 0; int w2 = 0; int h2 = 0;
if (style == BevelStyle.Bumped || style == BevelStyle.Etched)
{
if (all && (pos == BevelBorder.Top || pos == BevelBorder.Bottom))
{
rect = new Rectangle(rect.Left + 1, rect.Top, rect.Width - 2, rect.Height);
}
else if (all && (pos == BevelBorder.Left))
{
rect = new Rectangle(rect.Left, rect.Top, rect.Width, rect.Height - 1);
}
switch (pos)
{
case BevelBorder.Left:
{
x1 = rect.Left; y1 = rect.Top; w1 = 1; h1 = rect.Height;
x2 = x1 + 1; y2 = y1; w2 = w1; h2 = h1;
break;
}
case BevelBorder.Top:
{
x1 = rect.Left; y1 = rect.Top; w1 = rect.Width; h1 = 1;
x2 = x1; y2 = y1 + 1; w2 = w1; h2 = h1;
break;
}
case BevelBorder.Right:
{
x1 = rect.Left + rect.Width - 2; y1 = rect.Top; w1 = 1; h1 = rect.Height;
x2 = x1 + 1; y2 = y1; w2 = w1; h2 = h1;
break;
}
case BevelBorder.Bottom:
{
x1 = rect.Left; y1 = rect.Top + rect.Height - 2; w1 = rect.Width; h1 = 1;
x2 = x1; y2 = y1 + 1; w2 = w1; h2 = h1;
break;
}
}
}
else
{
switch (pos)
{
case BevelBorder.Left:
{
x1 = rect.Left; y1 = rect.Top; w1 = 1; h1 = rect.Height;
break;
}
case BevelBorder.Top:
{
x1 = rect.Left; y1 = rect.Top; w1 = rect.Width; h1 = 1;
break;
}
case BevelBorder.Right:
{
x1 = rect.Left + rect.Width - 1; y1 = rect.Top; w1 = 1; h1 = rect.Height;
break;
}
case BevelBorder.Bottom:
{
x1 = rect.Left; y1 = rect.Top + rect.Height - 1; w1 = rect.Width; h1 = 1;
break;
}
}
}
switch (Style)
{
case BevelStyle.Bumped:
{
renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c1);
renderer.Draw(img, new Rectangle(x2, y2, w2, h2), c2);
break;
}
case BevelStyle.Etched:
{
renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c2);
renderer.Draw(img, new Rectangle(x2, y2, w2, h2), c1);
break;
}
case BevelStyle.Raised:
{
Color c = c1;
if (pos == BevelBorder.Left || pos == BevelBorder.Top) c = c1;
else c = c2;
renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c);
break;
}
case BevelStyle.Lowered:
{
Color c = c1;
if (pos == BevelBorder.Left || pos == BevelBorder.Top) c = c2;
else c = c1;
renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c);
break;
}
default:
{
renderer.Draw(img, new Rectangle(x1, y1, w1, h1), c3);
break;
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected virtual void OnBorderChanged(EventArgs e)
{
if (BorderChanged != null) BorderChanged.Invoke(this, e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected virtual void OnStyleChanged(EventArgs e)
{
if (StyleChanged != null) StyleChanged.Invoke(this, e);
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
}

294
Neoforce/Button.cs Normal file
View File

@ -0,0 +1,294 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: Button.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
using Microsoft.Xna.Framework;
////////////////////////////////////////////////////////////////////////////
using Microsoft.Xna.Framework.Graphics;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
#region //// Enums /////////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/Button.xml' path='Button/Class[@name="SizeMode"]/*' />
public enum SizeMode
{
Normal,
Auto,
Centered,
Stretched,
/// <summary>
/// Only Supported by ImageBox
/// </summary>
Tiled
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/Button.xml' path='Button/Class[@name="SizeMode"]/*' />
public enum ButtonMode
{
Normal,
PushButton
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Classes ///////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/Button.xml' path='Button/Class[@name="Glyph"]/*' />
public class Glyph
{
////////////////////////////////////////////////////////////////////////////
public Texture2D Image = null;
public SizeMode SizeMode = SizeMode.Stretched;
public Color Color = Color.White;
public Point Offset = Point.Zero;
public Rectangle SourceRect = Rectangle.Empty;
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public Glyph(Texture2D image)
{
Image = image;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public Glyph(Texture2D image, Rectangle sourceRect): this(image)
{
SourceRect = sourceRect;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/Button.xml' path='Button/Class[@name="Button"]/*' />
public class Button: ButtonBase
{
#region //// Consts ////////////
////////////////////////////////////////////////////////////////////////////
private const string skButton = "Button";
private const string lrButton = "Control";
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private Glyph glyph = null;
private ModalResult modalResult = ModalResult.None;
private ButtonMode mode = ButtonMode.Normal;
private bool pushed = false;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
public Glyph Glyph
{
get { return glyph; }
set
{
glyph = value;
if (!Suspended) OnGlyphChanged(new EventArgs());
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public ModalResult ModalResult
{
get { return modalResult; }
set { modalResult = value; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public ButtonMode Mode
{
get { return mode; }
set { mode = value; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public bool Pushed
{
get { return pushed; }
set
{
pushed = value;
Invalidate();
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Events ////////////
////////////////////////////////////////////////////////////////////////////
public event EventHandler GlyphChanged;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Construstors //////
////////////////////////////////////////////////////////////////////////////
public Button(Manager manager): base(manager)
{
SetDefaultSize(72, 24);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Destructors ///////
////////////////////////////////////////////////////////////////////////////
protected override void Dispose(bool disposing)
{
if (disposing)
{
}
base.Dispose(disposing);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void InitSkin()
{
base.InitSkin();
Skin = new SkinControl(Manager.Skin.Controls[skButton]);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
if (mode == ButtonMode.PushButton && pushed)
{
SkinLayer l = Skin.Layers[lrButton];
renderer.DrawLayer(l, rect, l.States.Pressed.Color, l.States.Pressed.Index);
if (l.States.Pressed.Overlay)
{
renderer.DrawLayer(l, rect, l.Overlays.Pressed.Color, l.Overlays.Pressed.Index);
}
}
else
{
base.DrawControl(renderer, rect, gameTime);
}
SkinLayer layer = Skin.Layers[lrButton];
SpriteFont font = (layer.Text != null && layer.Text.Font != null) ? layer.Text.Font.Resource : null;
Color col = Color.White;
int ox = 0; int oy = 0;
if (ControlState == ControlState.Pressed)
{
if (layer.Text != null) col = layer.Text.Colors.Pressed;
ox = 1; oy = 1;
}
if (glyph != null)
{
Margins cont = layer.ContentMargins;
Rectangle r = new Rectangle(rect.Left + cont.Left,
rect.Top + cont.Top,
rect.Width - cont.Horizontal,
rect.Height - cont.Vertical);
renderer.DrawGlyph(glyph, r);
}
else
{
renderer.DrawString(this, layer, Text, rect, true, ox, oy);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void OnGlyphChanged(EventArgs e)
{
if (GlyphChanged != null) GlyphChanged.Invoke(this, e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnClick(EventArgs e)
{
MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs();
if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None)
{
pushed = !pushed;
}
base.OnClick(e);
if ((ex.Button == MouseButton.Left || ex.Button == MouseButton.None) && Root != null)
{
if (Root is Window)
{
Window wnd = (Window)Root;
if (ModalResult != ModalResult.None)
{
wnd.Close(ModalResult);
}
}
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
#endregion
}

117
Neoforce/ButtonBase.cs Normal file
View File

@ -0,0 +1,117 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: ButtonBase.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
#region //// Classes ///////////
////////////////////////////////////////////////////////////////////////////
/// <include file='Documents/ButtonBase.xml' path='ButtonBase/Class[@name="ButtonBase"]/*' />
public abstract class ButtonBase: Control
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
public override ControlState ControlState
{
get
{
if (DesignMode) return ControlState.Enabled;
else if (Suspended) return ControlState.Disabled;
else
{
if (!Enabled) return ControlState.Disabled;
if ((Pressed[(int)MouseButton.Left] && Inside) || (Focused && (Pressed[(int)GamePadActions.Press] || Pressed[(int)MouseButton.None]))) return ControlState.Pressed;
else if (Hovered && Inside) return ControlState.Hovered;
else if ((Focused && !Inside) || (Hovered && !Inside) || (Focused && !Hovered && Inside)) return ControlState.Focused;
else return ControlState.Enabled;
}
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Events ////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Construstors //////
////////////////////////////////////////////////////////////////////////////
protected ButtonBase(Manager manager)
: base(manager)
{
SetDefaultSize(72, 24);
DoubleClicks = false;
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnClick(EventArgs e)
{
MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs();
if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None)
{
base.OnClick(e);
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
////////////////////////////////////////////////////////////////////////////
#endregion
}

158
Neoforce/CheckBox.cs Normal file
View File

@ -0,0 +1,158 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: CheckBox.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
using Microsoft.Xna.Framework;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
public class CheckBox: ButtonBase
{
#region //// Consts ////////////
////////////////////////////////////////////////////////////////////////////
private const string skCheckBox = "CheckBox";
private const string lrCheckBox = "Control";
private const string lrChecked = "Checked";
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private bool state = false;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
public virtual bool Checked
{
get
{
return state;
}
set
{
state = value;
Invalidate();
if (!Suspended) OnCheckedChanged(new EventArgs());
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Events ////////////
////////////////////////////////////////////////////////////////////////////
public event EventHandler CheckedChanged;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Construstors //////
////////////////////////////////////////////////////////////////////////////
public CheckBox(Manager manager): base(manager)
{
CheckLayer(Skin, lrChecked);
Width = 64;
Height = 16;
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void InitSkin()
{
base.InitSkin();
Skin = new SkinControl(Manager.Skin.Controls[skCheckBox]);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
SkinLayer layer = Skin.Layers[lrChecked];
SkinText font = Skin.Layers[lrChecked].Text;
if (!state)
{
layer = Skin.Layers[lrCheckBox];
font = Skin.Layers[lrCheckBox].Text;
}
rect.Width = layer.Width;
rect.Height = layer.Height;
Rectangle rc = new Rectangle(rect.Left + rect.Width + 4, rect.Y, Width - (layer.Width + 4), rect.Height);
renderer.DrawLayer(this, layer, rect);
renderer.DrawString(this, layer, Text, rc, false, 0, 0);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnClick(EventArgs e)
{
MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs();
if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None)
{
Checked = !Checked;
}
base.OnClick(e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected virtual void OnCheckedChanged(EventArgs e)
{
if (CheckedChanged != null) CheckedChanged.Invoke(this, e);
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
}

65
Neoforce/ClipBox.cs Normal file
View File

@ -0,0 +1,65 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: ClipBox.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
public class ClipBox: Control
{
#region //// Constructors //////
////////////////////////////////////////////////////////////////////////////
public ClipBox(Manager manager): base(manager)
{
Color = Color.Transparent;
BackColor = Color.Transparent;
CanFocus = false;
Passive = true;
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
base.DrawControl(renderer, rect, gameTime);
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
}

181
Neoforce/ClipControl.cs Normal file
View File

@ -0,0 +1,181 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: Control.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
public class ClipControl: Control
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private ClipBox clientArea;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
public virtual ClipBox ClientArea
{
get { return clientArea; }
set { clientArea = value; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override Margins ClientMargins
{
get
{
return base.ClientMargins;
}
set
{
base.ClientMargins = value;
if (clientArea != null)
{
clientArea.Left = ClientLeft;
clientArea.Top = ClientTop;
clientArea.Width = ClientWidth;
clientArea.Height = ClientHeight;
}
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Constructors //////
////////////////////////////////////////////////////////////////////////////
public ClipControl(Manager manager): base(manager)
{
clientArea = new ClipBox(manager);
clientArea.Init();
clientArea.MinimumWidth = 0;
clientArea.MinimumHeight = 0;
clientArea.Left = ClientLeft;
clientArea.Top = ClientTop;
clientArea.Width = ClientWidth;
clientArea.Height = ClientHeight;
base.Add(clientArea);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void InitSkin()
{
base.InitSkin();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual void Add(Control control, bool client)
{
if (client)
{
clientArea.Add(control);
}
else
{
base.Add(control);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override void Add(Control control)
{
Add(control, true);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override void Remove(Control control)
{
base.Remove(control);
clientArea.Remove(control);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
base.DrawControl(renderer, rect, gameTime);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
if (clientArea != null)
{
clientArea.Left = ClientLeft;
clientArea.Top = ClientTop;
clientArea.Width = ClientWidth;
clientArea.Height = ClientHeight;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected virtual void AdjustMargins()
{
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
}

418
Neoforce/ComboBox.cs Normal file
View File

@ -0,0 +1,418 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: ComboBox.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
using System.Collections.Generic;
////////////////////////////////////////////////////////////////////////////
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System;
using Microsoft.Xna.Framework.Graphics;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
public class ComboBox: TextBox
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private Button btnDown = null;
private List<object> items = new List<object>();
private ListBox lstCombo = null;
private int maxItems = 5;
private bool drawSelection = true;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
public override bool ReadOnly
{
get { return base.ReadOnly; }
set
{
base.ReadOnly = value;
CaretVisible = !value;
if (value)
{
#if (!XBOX && !XBOX_FAKE)
Cursor = Manager.Skin.Cursors["Default"].Resource;
#endif
}
else
{
#if (!XBOX && !XBOX_FAKE)
Cursor = Manager.Skin.Cursors["Text"].Resource;
#endif
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public bool DrawSelection
{
get { return drawSelection; }
set { drawSelection = value; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
//if (!items.Contains(value)) --- bug
if (!items.ConvertAll(item => item.ToString()).Contains(value))
{
ItemIndex = -1;
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual List<object> Items
{
get { return items; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public int MaxItems
{
get { return maxItems; }
set
{
if (maxItems != value)
{
maxItems = value;
if (!Suspended) OnMaxItemsChanged(new EventArgs());
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public int ItemIndex
{
get { return lstCombo.ItemIndex; }
set
{
if (lstCombo != null)
{
if (value >= 0 && value < items.Count)
{
lstCombo.ItemIndex = value;
Text = lstCombo.Items[value].ToString();
}
else
{
lstCombo.ItemIndex = -1;
}
}
if (!Suspended) OnItemIndexChanged(new EventArgs());
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Events ////////////
////////////////////////////////////////////////////////////////////////////
public event EventHandler MaxItemsChanged;
public event EventHandler ItemIndexChanged;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Construstors //////
////////////////////////////////////////////////////////////////////////////
public ComboBox(Manager manager): base(manager)
{
Height = 20;
Width = 64;
ReadOnly = true;
btnDown = new Button(Manager);
btnDown.Init();
btnDown.Skin = new SkinControl(Manager.Skin.Controls["ComboBox.Button"]);
btnDown.CanFocus = false;
btnDown.Click += new EventHandler(btnDown_Click);
Add(btnDown, false);
lstCombo = new ListBox(Manager);
lstCombo.Init();
lstCombo.HotTrack = true;
lstCombo.Detached = true;
lstCombo.Visible = false;
lstCombo.Click += new EventHandler(lstCombo_Click);
lstCombo.FocusLost += new EventHandler(lstCombo_FocusLost);
lstCombo.Items = items;
manager.Input.MouseDown += new MouseEventHandler(Input_MouseDown);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Destructors ///////
////////////////////////////////////////////////////////////////////////////
protected override void Dispose(bool disposing)
{
if (disposing)
{
// We added the listbox to another parent than this control, so we dispose it manually
if (lstCombo != null)
{
lstCombo.Dispose();
lstCombo = null;
}
Manager.Input.MouseDown -= Input_MouseDown;
}
base.Dispose(disposing);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
lstCombo.Skin = new SkinControl(Manager.Skin.Controls["ComboBox.ListBox"]);
btnDown.Glyph = new Glyph(Manager.Skin.Images["Shared.ArrowDown"].Resource);
btnDown.Glyph.Color = Manager.Skin.Controls["ComboBox.Button"].Layers["Control"].Text.Colors.Enabled;
btnDown.Glyph.SizeMode = SizeMode.Centered;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void InitSkin()
{
base.InitSkin();
Skin = new SkinControl(Manager.Skin.Controls["ComboBox"]);
AdjustMargins();
ReadOnly = ReadOnly; // To init the right cursor
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
base.DrawControl(renderer, rect, gameTime);
if (ReadOnly && (Focused || lstCombo.Focused) && drawSelection)
{
SkinLayer lr = Skin.Layers[0];
Rectangle rc = new Rectangle(rect.Left + lr.ContentMargins.Left,
rect.Top + lr.ContentMargins.Top,
Width - lr.ContentMargins.Horizontal - btnDown.Width,
Height - lr.ContentMargins.Vertical);
renderer.Draw(Manager.Skin.Images["ListBox.Selection"].Resource, rc , Color.FromNonPremultiplied(255, 255, 255, 128));
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
if (btnDown != null)
{
btnDown.Width = 16;
btnDown.Height = Height - Skin.Layers[0].ContentMargins.Vertical;
btnDown.Top = Skin.Layers[0].ContentMargins.Top;
btnDown.Left = Width - btnDown.Width - 2;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void btnDown_Click(object sender, EventArgs e)
{
if (items != null && items.Count > 0)
{
if (this.Root != null && this.Root is Container)
{
(this.Root as Container).Add(lstCombo, false);
lstCombo.Alpha = Root.Alpha;
lstCombo.Left = AbsoluteLeft - Root.Left;
lstCombo.Top = AbsoluteTop - Root.Top + Height + 1;
}
else
{
Manager.Add(lstCombo);
lstCombo.Alpha = Alpha;
lstCombo.Left = AbsoluteLeft;
lstCombo.Top = AbsoluteTop + Height + 1;
}
lstCombo.AutoHeight(maxItems);
if (lstCombo.AbsoluteTop + lstCombo.Height > Manager.TargetHeight)
{
lstCombo.Top = lstCombo.Top - Height - lstCombo.Height - 2;
}
lstCombo.Visible = !lstCombo.Visible;
lstCombo.Focused = true;
lstCombo.Width = Width;
lstCombo.AutoHeight(maxItems);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void Input_MouseDown(object sender, MouseEventArgs e)
{
if (ReadOnly &&
(e.Position.X >= AbsoluteLeft &&
e.Position.X <= AbsoluteLeft + Width &&
e.Position.Y >= AbsoluteTop &&
e.Position.Y <= AbsoluteTop + Height)) return;
if (lstCombo.Visible &&
(e.Position.X < lstCombo.AbsoluteLeft ||
e.Position.X > lstCombo.AbsoluteLeft + lstCombo.Width ||
e.Position.Y < lstCombo.AbsoluteTop ||
e.Position.Y > lstCombo.AbsoluteTop + lstCombo.Height) &&
(e.Position.X < btnDown.AbsoluteLeft ||
e.Position.X > btnDown.AbsoluteLeft + btnDown.Width ||
e.Position.Y < btnDown.AbsoluteTop ||
e.Position.Y > btnDown.AbsoluteTop + btnDown.Height))
{
//lstCombo.Visible = false;
btnDown_Click(sender, e);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void lstCombo_Click(object sender, EventArgs e)
{
MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs();
if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None)
{
lstCombo.Visible = false;
if (lstCombo.ItemIndex >= 0)
{
Text = lstCombo.Items[lstCombo.ItemIndex].ToString();
Focused = true;
ItemIndex = lstCombo.ItemIndex;
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Keys.Down)
{
e.Handled = true;
btnDown_Click(this, new MouseEventArgs());
}
base.OnKeyDown(e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnGamePadDown(GamePadEventArgs e)
{
if (!e.Handled)
{
if (e.Button == GamePadActions.Click || e.Button == GamePadActions.Press || e.Button == GamePadActions.Down)
{
e.Handled = true;
btnDown_Click(this, new MouseEventArgs());
}
}
base.OnGamePadDown(e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (ReadOnly && e.Button == MouseButton.Left)
{
btnDown_Click(this, new MouseEventArgs());
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected virtual void OnMaxItemsChanged(EventArgs e)
{
if (MaxItemsChanged != null) MaxItemsChanged.Invoke(this, e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected virtual void OnItemIndexChanged(EventArgs e)
{
if (ItemIndexChanged != null) ItemIndexChanged.Invoke(this, e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void lstCombo_FocusLost(object sender, EventArgs e)
{
//lstCombo.Visible = false;
Invalidate();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void AdjustMargins()
{
base.AdjustMargins();
ClientMargins = new Margins(ClientMargins.Left, ClientMargins.Top, ClientMargins.Right + 16, ClientMargins.Bottom);
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
}

105
Neoforce/Component.cs Normal file
View File

@ -0,0 +1,105 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: Component.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
using System;
using Microsoft.Xna.Framework;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
public class Component: Disposable
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private Manager manager = null;
private bool initialized = false;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
public virtual Manager Manager { get { return manager; } set { manager = value; } }
public virtual bool Initialized { get { return initialized; } }
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Constructors //////
////////////////////////////////////////////////////////////////////////////
public Component(Manager manager)
{
if (manager != null)
{
this.manager = manager;
}
else
{
throw new Exception("Component cannot be created. Manager instance is needed.");
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Destructors ///////
////////////////////////////////////////////////////////////////////////////
protected override void Dispose(bool disposing)
{
if (disposing)
{
}
base.Dispose(disposing);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public virtual void Init()
{
initialized = true;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal virtual void Update(GameTime gameTime)
{
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
}

595
Neoforce/Console.cs Normal file
View File

@ -0,0 +1,595 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: Console.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework.GamerServices;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
public struct ConsoleMessage
{
public string Text;
public byte Channel;
public DateTime Time;
public string Sender;
public ConsoleMessage(string sender, string text, byte channel)
{
this.Text = text;
this.Channel = channel;
this.Time = DateTime.Now;
this.Sender = sender;
}
}
public class ChannelList : EventedList<ConsoleChannel>
{
#region //// Indexers //////////
////////////////////////////////////////////////////////////////////////////
public ConsoleChannel this[string name]
{
get
{
for (int i = 0; i < this.Count; i++)
{
ConsoleChannel s = (ConsoleChannel)this[i];
if (s.Name.ToLower() == name.ToLower())
{
return s;
}
}
return default(ConsoleChannel);
}
set
{
for (int i = 0; i < this.Count; i++)
{
ConsoleChannel s = (ConsoleChannel)this[i];
if (s.Name.ToLower() == name.ToLower())
{
this[i] = value;
}
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public ConsoleChannel this[byte index]
{
get
{
for (int i = 0; i < this.Count; i++)
{
ConsoleChannel s = (ConsoleChannel)this[i];
if (s.Index == index)
{
return s;
}
}
return default(ConsoleChannel);
}
set
{
for (int i = 0; i < this.Count; i++)
{
ConsoleChannel s = (ConsoleChannel)this[i];
if (s.Index == index)
{
this[i] = value;
}
}
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
public class ConsoleChannel
{
private string name;
private byte index;
private Color color;
public ConsoleChannel(byte index, string name, Color color)
{
this.name = name;
this.index = index;
this.color = color;
}
public virtual byte Index
{
get { return index; }
set { index = value; }
}
public virtual Color Color
{
get { return color; }
set { color = value; }
}
public virtual string Name
{
get { return name; }
set { name = value; }
}
}
[Flags]
public enum ConsoleMessageFormats
{
None = 0x00,
ChannelName = 0x01,
TimeStamp = 0x02,
Sender = 0x03,
All = Sender | ChannelName | TimeStamp
}
public class Console : Container
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private TextBox txtMain = null;
private ComboBox cmbMain;
private EventedList<ConsoleMessage> buffer = new EventedList<ConsoleMessage>();
private ChannelList channels = new ChannelList();
private List<byte> filter = new List<byte>();
private ConsoleMessageFormats messageFormat = ConsoleMessageFormats.None;
private bool channelsVisible = true;
private bool textBoxVisible = true;
private string sender;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
public string Sender
{
get { return sender; }
set { sender = value; }
}
////////////////////////////////////////////////////////////////////////////
public virtual EventedList<ConsoleMessage> MessageBuffer
{
get { return buffer; }
set
{
buffer.ItemAdded -= new EventHandler(buffer_ItemAdded);
buffer = value;
buffer.ItemAdded += new EventHandler(buffer_ItemAdded);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual ChannelList Channels
{
get { return channels; }
set
{
channels.ItemAdded -= new EventHandler(channels_ItemAdded);
channels = value;
channels.ItemAdded += new EventHandler(channels_ItemAdded);
channels_ItemAdded(null, null);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual List<byte> ChannelFilter
{
get { return filter; }
set { filter = value; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual byte SelectedChannel
{
set { cmbMain.Text = channels[value].Name; }
get { return channels[cmbMain.Text].Index; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual ConsoleMessageFormats MessageFormat
{
get { return messageFormat; }
set { messageFormat = value; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual bool ChannelsVisible
{
get { return channelsVisible; }
set
{
cmbMain.Visible = channelsVisible = value;
if (value && !textBoxVisible) TextBoxVisible = false;
PositionControls();
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual bool TextBoxVisible
{
get { return textBoxVisible; }
set
{
txtMain.Visible = textBoxVisible = value;
txtMain.Focused = true;
if (!value && channelsVisible) ChannelsVisible = false;
PositionControls();
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Events ////////////
////////////////////////////////////////////////////////////////////////////
public event ConsoleMessageEventHandler MessageSent;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Construstors //////
////////////////////////////////////////////////////////////////////////////
public Console(Manager manager)
: base(manager)
{
Width = 320;
Height = 160;
MinimumHeight = 64;
MinimumWidth = 64;
CanFocus = false;
Resizable = false;
Movable = false;
cmbMain = new ComboBox(manager);
cmbMain.Init();
cmbMain.Top = Height - cmbMain.Height;
cmbMain.Left = 0;
cmbMain.Width = 128;
cmbMain.Anchor = Anchors.Left | Anchors.Bottom;
cmbMain.Detached = false;
cmbMain.DrawSelection = false;
cmbMain.Visible = channelsVisible;
Add(cmbMain, false);
txtMain = new TextBox(manager);
txtMain.Init();
txtMain.Top = Height - txtMain.Height;
txtMain.Left = cmbMain.Width + 1;
txtMain.Anchor = Anchors.Left | Anchors.Bottom | Anchors.Right;
txtMain.Detached = false;
txtMain.Visible = textBoxVisible;
txtMain.KeyDown += new KeyEventHandler(txtMain_KeyDown);
txtMain.GamePadDown += new GamePadEventHandler(txtMain_GamePadDown);
txtMain.FocusGained += new EventHandler(txtMain_FocusGained);
Add(txtMain, false);
VerticalScrollBar.Top = 2;
VerticalScrollBar.Left = Width - 18;
VerticalScrollBar.Range = 1;
VerticalScrollBar.PageSize = 1;
VerticalScrollBar.ValueChanged += new EventHandler(VerticalScrollBar_ValueChanged);
VerticalScrollBar.Visible = true;
ClientArea.Draw += new DrawEventHandler(ClientArea_Draw);
buffer.ItemAdded += new EventHandler(buffer_ItemAdded);
channels.ItemAdded += new EventHandler(channels_ItemAdded);
channels.ItemRemoved += new EventHandler(channels_ItemRemoved);
PositionControls();
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
private void PositionControls()
{
if (txtMain != null)
{
txtMain.Left = channelsVisible ? cmbMain.Width + 1 : 0;
txtMain.Width = channelsVisible ? Width - cmbMain.Width - 1 : Width;
if (textBoxVisible)
{
ClientMargins = new Margins(Skin.ClientMargins.Left, Skin.ClientMargins.Top + 4, VerticalScrollBar.Width + 6, txtMain.Height + 4);
VerticalScrollBar.Height = Height - txtMain.Height - 5;
}
else
{
ClientMargins = new Margins(Skin.ClientMargins.Left, Skin.ClientMargins.Top + 4, VerticalScrollBar.Width + 6, 2);
VerticalScrollBar.Height = Height - 4;
}
Invalidate();
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void InitSkin()
{
base.InitSkin();
Skin = new SkinControl(Manager.Skin.Controls["Console"]);
PositionControls();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void ClientArea_Draw(object sender, DrawEventArgs e)
{
SpriteFont font = Skin.Layers[0].Text.Font.Resource;
Rectangle r = new Rectangle(e.Rectangle.Left, e.Rectangle.Top, e.Rectangle.Width, e.Rectangle.Height);
int pos = 0;
if (buffer.Count > 0)
{
EventedList<ConsoleMessage> b = GetFilteredBuffer(filter);
int c = b.Count;
int s = (VerticalScrollBar.Value + VerticalScrollBar.PageSize);
int f = s - VerticalScrollBar.PageSize;
if (b.Count > 0)
{
for (int i = s - 1; i >= f; i--)
{
{
int x = 4;
int y = r.Bottom - (pos + 1) * ((int)font.LineSpacing + 0);
string msg = ((ConsoleMessage)b[i]).Text;
string pre = "";
ConsoleChannel ch = (channels[((ConsoleMessage)b[i]).Channel] as ConsoleChannel);
if ((messageFormat & ConsoleMessageFormats.ChannelName) == ConsoleMessageFormats.ChannelName)
{
pre += string.Format("[{0}]", channels[((ConsoleMessage)b[i]).Channel].Name);
}
if ((messageFormat & ConsoleMessageFormats.Sender) == ConsoleMessageFormats.Sender)
{
pre += string.Format("[{0}]", ((ConsoleMessage)b[i]).Sender);
}
if ((messageFormat & ConsoleMessageFormats.TimeStamp) == ConsoleMessageFormats.TimeStamp)
{
pre = string.Format("[{0}]", ((ConsoleMessage)b[i]).Time.ToLongTimeString()) + pre;
}
if (pre != "") msg = pre + ": " + msg;
e.Renderer.DrawString(font,
msg,
x, y,
ch.Color);
pos += 1;
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
int h = txtMain.Visible ? (txtMain.Height + 1) : 0;
Rectangle r = new Rectangle(rect.Left, rect.Top, rect.Width, rect.Height - h);
base.DrawControl(renderer, r, gameTime);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void txtMain_FocusGained(object sender, EventArgs e)
{
ConsoleChannel ch = channels[cmbMain.Text];
if (ch != null) txtMain.TextColor = ch.Color;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void txtMain_KeyDown(object sender, KeyEventArgs e)
{
SendMessage(e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void txtMain_GamePadDown(object sender, GamePadEventArgs e)
{
SendMessage(e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void SendMessage(EventArgs x)
{
if (Manager.UseGuide && Guide.IsVisible) return;
KeyEventArgs k = new KeyEventArgs();
GamePadEventArgs g = new GamePadEventArgs(PlayerIndex.One);
if (x is KeyEventArgs) k = x as KeyEventArgs;
else if (x is GamePadEventArgs) g = x as GamePadEventArgs;
ConsoleChannel ch = channels[cmbMain.Text];
if (ch != null)
{
txtMain.TextColor = ch.Color;
string message = txtMain.Text;
if ((k.Key == Microsoft.Xna.Framework.Input.Keys.Enter || g.Button == GamePadActions.Press) && message != null && message != "")
{
x.Handled = true;
ConsoleMessageEventArgs me = new ConsoleMessageEventArgs(new ConsoleMessage(sender, message, ch.Index));
OnMessageSent(me);
buffer.Add(new ConsoleMessage(sender, me.Message.Text, me.Message.Channel));
txtMain.Text = "";
ClientArea.Invalidate();
CalcScrolling();
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected virtual void OnMessageSent(ConsoleMessageEventArgs e)
{
if (MessageSent != null) MessageSent.Invoke(this, e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void channels_ItemAdded(object sender, EventArgs e)
{
cmbMain.Items.Clear();
for (int i = 0; i < channels.Count; i++)
{
cmbMain.Items.Add((channels[i] as ConsoleChannel).Name);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void channels_ItemRemoved(object sender, EventArgs e)
{
cmbMain.Items.Clear();
for (int i = 0; i < channels.Count; i++)
{
cmbMain.Items.Add((channels[i] as ConsoleChannel).Name);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void buffer_ItemAdded(object sender, EventArgs e)
{
CalcScrolling();
ClientArea.Invalidate();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void CalcScrolling()
{
if (VerticalScrollBar != null)
{
int line = Skin.Layers[0].Text.Font.Resource.LineSpacing;
int c = GetFilteredBuffer(filter).Count;
int p = (int)Math.Ceiling(ClientArea.ClientHeight / (float)line);
VerticalScrollBar.Range = c == 0 ? 1 : c;
VerticalScrollBar.PageSize = c == 0 ? 1 : p;
VerticalScrollBar.Value = VerticalScrollBar.Range;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void VerticalScrollBar_ValueChanged(object sender, EventArgs e)
{
ClientArea.Invalidate();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnResize(ResizeEventArgs e)
{
CalcScrolling();
base.OnResize(e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private EventedList<ConsoleMessage> GetFilteredBuffer(List<byte> filter)
{
EventedList<ConsoleMessage> ret = new EventedList<ConsoleMessage>();
if (filter.Count > 0)
{
for (int i = 0; i < buffer.Count; i++)
{
if (filter.Contains(((ConsoleMessage)buffer[i]).Channel))
{
ret.Add(buffer[i]);
}
}
return ret;
}
else return buffer;
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
}

554
Neoforce/Container.cs Normal file
View File

@ -0,0 +1,554 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: Container.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
public struct ScrollBarValue
{
public int Vertical;
public int Horizontal;
}
public class Container: ClipControl
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private ScrollBar sbVert;
private ScrollBar sbHorz;
private MainMenu mainMenu;
private ToolBarPanel toolBarPanel;
private StatusBar statusBar;
private bool autoScroll = false;
private Control defaultControl = null;
/// <summary>
/// Scroll by PageSize (true) or StepSize (false)
/// </summary>
private bool scrollAlot = true;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
public virtual ScrollBarValue ScrollBarValue
{
get
{
ScrollBarValue scb = new ScrollBarValue();
scb.Vertical = (sbVert != null ? sbVert.Value : 0);
scb.Horizontal = (sbHorz != null ? sbHorz.Value : 0);
return scb;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override bool Visible
{
get
{
return base.Visible;
}
set
{
if (value)
{
if (DefaultControl != null)
{
DefaultControl.Focused = true;
}
}
base.Visible = value;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual Control DefaultControl
{
get { return defaultControl; }
set { defaultControl = value; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual bool AutoScroll
{
get { return autoScroll; }
set { autoScroll = value; }
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual MainMenu MainMenu
{
get { return mainMenu; }
set
{
if (mainMenu != null)
{
mainMenu.Resize -= Bars_Resize;
Remove(mainMenu);
}
mainMenu = value;
if (mainMenu != null)
{
Add(mainMenu, false);
mainMenu.Resize += Bars_Resize;
}
AdjustMargins();
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual ToolBarPanel ToolBarPanel
{
get
{
return toolBarPanel;
}
set
{
if (toolBarPanel != null)
{
toolBarPanel.Resize -= Bars_Resize;
Remove(toolBarPanel);
}
toolBarPanel = value;
if (toolBarPanel != null)
{
Add(toolBarPanel, false);
toolBarPanel.Resize += Bars_Resize;
}
AdjustMargins();
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual StatusBar StatusBar
{
get
{
return statusBar;
}
set
{
if (statusBar != null)
{
statusBar.Resize -= Bars_Resize;
Remove(statusBar);
}
statusBar = value;
if (statusBar != null)
{
Add(statusBar, false);
statusBar.Resize += Bars_Resize;
}
AdjustMargins();
}
}
////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Scroll by PageSize (true) or StepSize (false)
/// </summary>
public virtual bool ScrollAlot
{
get { return this.scrollAlot; }
set { this.scrollAlot = value; }
}
/// <summary>
/// Gets the container's vertical scroll bar.
/// </summary>
protected virtual ScrollBar VerticalScrollBar
{
get { return this.sbVert; }
}
/// <summary>
/// Gets the container's horizontal scroll bar.
/// </summary>
protected virtual ScrollBar HorizontalScrollBar
{
get { return this.sbHorz; }
}
#endregion
#region //// Constructors //////
////////////////////////////////////////////////////////////////////////////
public Container(Manager manager): base(manager)
{
sbVert = new ScrollBar(manager, Orientation.Vertical);
sbVert.Init();
sbVert.Detached = false;
sbVert.Anchor = Anchors.Top | Anchors.Right | Anchors.Bottom;
sbVert.ValueChanged += new EventHandler(ScrollBarValueChanged);
sbVert.Range = 0;
sbVert.PageSize = 0;
sbVert.Value = 0;
sbVert.Visible = false;
sbHorz = new ScrollBar(manager, Orientation.Horizontal);
sbHorz.Init();
sbHorz.Detached = false;
sbHorz.Anchor = Anchors.Right | Anchors.Left | Anchors.Bottom;
sbHorz.ValueChanged += new EventHandler(ScrollBarValueChanged);
sbHorz.Range = 0;
sbHorz.PageSize = 0;
sbHorz.Value = 0;
sbHorz.Visible = false;
Add(sbVert, false);
Add(sbHorz, false);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void InitSkin()
{
base.InitSkin();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void Bars_Resize(object sender, ResizeEventArgs e)
{
AdjustMargins();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void AdjustMargins()
{
Margins m = Skin.ClientMargins;
if (this.GetType() != typeof(Container))
{
m = ClientMargins;
}
if (mainMenu != null && mainMenu.Visible)
{
if (!mainMenu.Initialized) mainMenu.Init();
mainMenu.Left = m.Left;
mainMenu.Top = m.Top;
mainMenu.Width = Width - m.Horizontal;
mainMenu.Anchor = Anchors.Left | Anchors.Top | Anchors.Right;
m.Top += mainMenu.Height;
}
if (toolBarPanel != null && toolBarPanel.Visible)
{
if (!toolBarPanel.Initialized) toolBarPanel.Init();
toolBarPanel.Left = m.Left;
toolBarPanel.Top = m.Top;
toolBarPanel.Width = Width - m.Horizontal;
toolBarPanel.Anchor = Anchors.Left | Anchors.Top | Anchors.Right;
m.Top += toolBarPanel.Height;
}
if (statusBar != null && statusBar.Visible)
{
if (!statusBar.Initialized) statusBar.Init();
statusBar.Left = m.Left;
statusBar.Top = Height - m.Bottom - statusBar.Height;
statusBar.Width = Width - m.Horizontal;
statusBar.Anchor = Anchors.Left | Anchors.Bottom | Anchors.Right;
m.Bottom += statusBar.Height;
}
if (sbVert != null && sbVert.Visible)
{
m.Right += (sbVert.Width + 2);
}
if (sbHorz != null && sbHorz.Visible)
{
m.Bottom += (sbHorz.Height + 2);
}
ClientMargins = m;
PositionScrollBars();
base.AdjustMargins();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override void Add(Control control, bool client)
{
base.Add(control, client);
CalcScrolling();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
base.DrawControl(renderer, rect, gameTime);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void OnSkinChanged(EventArgs e)
{
base.OnSkinChanged(e);
if (sbVert != null && sbHorz != null)
{
sbVert.Visible = false;
sbHorz.Visible = false;
CalcScrolling();
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void PositionScrollBars()
{
if (sbVert != null)
{
sbVert.Left = ClientLeft + ClientWidth + 1;
sbVert.Top = ClientTop + 1;
int m = (sbHorz != null && sbHorz.Visible) ? 0 : 2;
sbVert.Height = ClientArea.Height - m;
sbVert.Range = ClientArea.VirtualHeight;
sbVert.PageSize = ClientArea.ClientHeight;
}
if (sbHorz != null)
{
sbHorz.Left = ClientLeft + 1;
sbHorz.Top = ClientTop + ClientHeight + 1;
int m = (sbVert != null && sbVert.Visible) ? 0 : 2;
sbHorz.Width = ClientArea.Width - m;
sbHorz.Range = ClientArea.VirtualWidth;
sbHorz.PageSize = ClientArea.ClientWidth;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void CalcScrolling()
{
if (sbVert != null && autoScroll)
{
bool vis = sbVert.Visible;
sbVert.Visible = ClientArea.VirtualHeight > ClientArea.ClientHeight;
if (ClientArea.VirtualHeight <= ClientArea.ClientHeight) sbVert.Value = 0;
if (vis != sbVert.Visible)
{
if (!sbVert.Visible)
{
foreach (Control c in ClientArea.Controls)
{
c.TopModifier = 0;
c.Invalidate();
}
}
AdjustMargins();
}
PositionScrollBars();
foreach (Control c in ClientArea.Controls)
{
c.TopModifier = -sbVert.Value;
c.Invalidate();
}
}
if (sbHorz != null && autoScroll)
{
bool vis = sbHorz.Visible;
sbHorz.Visible = ClientArea.VirtualWidth > ClientArea.ClientWidth;
if (ClientArea.VirtualWidth <= ClientArea.ClientWidth) sbHorz.Value = 0;
if (vis != sbHorz.Visible)
{
if (!sbHorz.Visible)
{
foreach (Control c in ClientArea.Controls)
{
c.LeftModifier = 0;
sbVert.Refresh();
c.Invalidate();
}
}
AdjustMargins();
}
PositionScrollBars();
foreach (Control c in ClientArea.Controls)
{
c.LeftModifier = -sbHorz.Value;
sbHorz.Refresh();
c.Invalidate();
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual void ScrollTo(int x, int y)
{
sbVert.Value = y;
sbHorz.Value = x;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual void ScrollTo(Control control)
{
if (control != null && ClientArea != null && ClientArea.Contains(control, true))
{
if (control.AbsoluteTop + control.Height > ClientArea.AbsoluteTop + ClientArea.Height)
{
sbVert.Value = sbVert.Value + control.AbsoluteTop - ClientArea.AbsoluteTop - sbVert.PageSize + control.Height;
}
else if (control.AbsoluteTop < ClientArea.AbsoluteTop)
{
sbVert.Value = sbVert.Value + control.AbsoluteTop - ClientArea.AbsoluteTop;
}
if (control.AbsoluteLeft + control.Width > ClientArea.AbsoluteLeft + ClientArea.Width)
{
sbHorz.Value = sbHorz.Value + control.AbsoluteLeft - ClientArea.AbsoluteLeft - sbHorz.PageSize + control.Width;
}
else if (control.AbsoluteLeft < ClientArea.AbsoluteLeft)
{
sbHorz.Value = sbHorz.Value + control.AbsoluteLeft - ClientArea.AbsoluteLeft;
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
void ScrollBarValueChanged(object sender, EventArgs e)
{
CalcScrolling();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
CalcScrolling();
// Crappy fix to certain scrolling issue
//if (sbVert != null) sbVert.Value -= 1;
//if (sbHorz != null) sbHorz.Value -= 1;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override void Invalidate()
{
base.Invalidate();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnClick(EventArgs e)
{
MouseEventArgs ex = e as MouseEventArgs;
ex.Position = new Point(ex.Position.X + sbHorz.Value, ex.Position.Y + sbVert.Value);
base.OnClick(e);
}
////////////////////////////////////////////////////////////////////////////
protected override void OnMouseScroll(MouseEventArgs e)
{
if (!ClientArea.Enabled)
return;
// If current control doesn't scroll, scroll the parent control
if (sbVert.Range - sbVert.PageSize < 1)
{
Control c = this;
while (c != null)
{
var p = c.Parent as Container;
if (p != null && p.Enabled)
{
p.OnMouseScroll(e);
break;
}
c = c.Parent;
}
return;
}
if (e.ScrollDirection == MouseScrollDirection.Down)
sbVert.ScrollDown(ScrollAlot);
else
sbVert.ScrollUp(ScrollAlot);
base.OnMouseScroll(e);
}
#endregion
}
}

155
Neoforce/ContentReaders.cs Normal file
View File

@ -0,0 +1,155 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: ContentReaders.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
//////////////////////////////////////////////////////////////////////////////
using System;
using System.IO;
using System.Xml;
using Microsoft.Xna.Framework.Content;
#if (!XBOX && !XBOX_FAKE)
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
#endif
//////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
////////////////////////////////////////////////////////////////////////////
public class LayoutXmlDocument : XmlDocument { }
public class SkinXmlDocument : XmlDocument { }
////////////////////////////////////////////////////////////////////////////
public class SkinReader : ContentTypeReader<SkinXmlDocument>
{
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
protected override SkinXmlDocument Read(ContentReader input, SkinXmlDocument existingInstance)
{
if (existingInstance == null)
{
SkinXmlDocument doc = new SkinXmlDocument();
doc.LoadXml(input.ReadString());
return doc;
}
else
{
existingInstance.LoadXml(input.ReadString());
}
return existingInstance;
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
public class LayoutReader : ContentTypeReader<LayoutXmlDocument>
{
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
protected override LayoutXmlDocument Read(ContentReader input, LayoutXmlDocument existingInstance)
{
if (existingInstance == null)
{
LayoutXmlDocument doc = new LayoutXmlDocument();
doc.LoadXml(input.ReadString());
return doc;
}
else
{
existingInstance.LoadXml(input.ReadString());
}
return existingInstance;
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
#if (!XBOX && !XBOX_FAKE)
public class CursorReader : ContentTypeReader<Cursor>
{
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
protected override Cursor Read(ContentReader input, Cursor existingInstance)
{
if (existingInstance == null)
{
int count = input.ReadInt32();
byte[] data = input.ReadBytes(count);
string path = Path.GetTempFileName();
File.WriteAllBytes(path, data);
string tPath = Path.GetTempFileName();
using(System.Drawing.Icon i = System.Drawing.Icon.ExtractAssociatedIcon(path))
{
using (System.Drawing.Bitmap b = i.ToBitmap())
{
b.Save(tPath, System.Drawing.Imaging.ImageFormat.Png);
b.Dispose();
}
i.Dispose();
}
//TODO: Replace with xml based solution for getting hotspot and size instead
IntPtr handle = NativeMethods.LoadCursor(path);
System.Windows.Forms.Cursor c = new System.Windows.Forms.Cursor(handle);
Vector2 hs = new Vector2(c.HotSpot.X, c.HotSpot.Y);
int w = c.Size.Width;
int h = c.Size.Height;
c.Dispose();
File.Delete(path);
return new Cursor(tPath, hs, w, h);
}
else
{
}
return existingInstance;
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
#endif
}

575
Neoforce/ContextMenu.cs Normal file
View File

@ -0,0 +1,575 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: ContextMenu.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
public class ContextMenu: MenuBase
{
#region //// Fields ////////////
////////////////////////////////////////////////////////////////////////////
private long timer = 0;
private Control sender = null;
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Properties ////////
////////////////////////////////////////////////////////////////////////////
protected internal Control Sender { get { return sender; } set { sender = value; } }
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Construstors //////
////////////////////////////////////////////////////////////////////////////
public ContextMenu(Manager manager): base(manager)
{
Visible = false;
Detached = true;
StayOnBack = true;
Manager.Input.MouseDown += new MouseEventHandler(Input_MouseDown);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Destructors ///////
////////////////////////////////////////////////////////////////////////////
protected override void Dispose(bool disposing)
{
if (disposing)
{
Manager.Input.MouseDown -= Input_MouseDown;
}
base.Dispose(disposing);
}
////////////////////////////////////////////////////////////////////////////
#endregion
#region //// Methods ///////////
////////////////////////////////////////////////////////////////////////////
public override void Init()
{
base.Init();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void InitSkin()
{
base.InitSkin();
Skin = new SkinControl(Manager.Skin.Controls["ContextMenu"]);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void DrawControl(Renderer renderer, Rectangle rect, GameTime gameTime)
{
base.DrawControl(renderer, rect, gameTime);
SkinLayer l1 = Skin.Layers["Control"];
SkinLayer l2 = Skin.Layers["Selection"];
int vsize = LineHeight();
Color col = Color.White;
for (int i = 0; i < Items.Count; i++)
{
int mod = i > 0 ? 2 : 0;
int left = rect.Left + l1.ContentMargins.Left + vsize;
int h = vsize - mod - (i < (Items.Count - 1) ? 1 : 0);
int top = rect.Top + l1.ContentMargins.Top + (i * vsize) + mod;
if (Items[i].Separated && i > 0)
{
Rectangle r = new Rectangle(left, rect.Top + l1.ContentMargins.Top + (i * vsize), LineWidth() - vsize + 4, 1);
renderer.Draw(Manager.Skin.Controls["Control"].Layers[0].Image.Resource, r, l1.Text.Colors.Enabled);
}
if (ItemIndex != i)
{
if (Items[i].Enabled)
{
Rectangle r = new Rectangle(left, top, LineWidth() - vsize, h);
renderer.DrawString(this, l1, Items[i].Text, r, false);
col = l1.Text.Colors.Enabled;
}
else
{
Rectangle r = new Rectangle(left + l1.Text.OffsetX,
top + l1.Text.OffsetY,
LineWidth() - vsize, h);
renderer.DrawString(l1.Text.Font.Resource, Items[i].Text, r, l1.Text.Colors.Disabled, l1.Text.Alignment);
col = l1.Text.Colors.Disabled;
}
}
else
{
if (Items[i].Enabled)
{
Rectangle rs = new Rectangle(rect.Left + l1.ContentMargins.Left,
top,
Width - (l1.ContentMargins.Horizontal - Skin.OriginMargins.Horizontal),
h);
renderer.DrawLayer(this, l2, rs);
Rectangle r = new Rectangle(left,
top, LineWidth() - vsize, h);
renderer.DrawString(this, l2, Items[i].Text, r, false);
col = l2.Text.Colors.Enabled;
}
else
{
Rectangle rs = new Rectangle(rect.Left + l1.ContentMargins.Left,
top,
Width - (l1.ContentMargins.Horizontal - Skin.OriginMargins.Horizontal),
vsize);
renderer.DrawLayer(l2, rs, l2.States.Disabled.Color, l2.States.Disabled.Index);
Rectangle r = new Rectangle(left + l1.Text.OffsetX,
top + l1.Text.OffsetY,
LineWidth() - vsize, h);
renderer.DrawString(l2.Text.Font.Resource, Items[i].Text, r, l2.Text.Colors.Disabled, l2.Text.Alignment);
col = l2.Text.Colors.Disabled;
}
}
if (Items[i].Image != null)
{
Rectangle r = new Rectangle(rect.Left + l1.ContentMargins.Left + 3,
rect.Top + top + 3,
LineHeight() - 6,
LineHeight() - 6);
renderer.Draw(Items[i].Image, r, Color.White);
}
if (Items[i].Items != null && Items[i].Items.Count > 0)
{
renderer.Draw(Manager.Skin.Images["Shared.ArrowRight"].Resource, rect.Left + LineWidth() - 4, rect.Top + l1.ContentMargins.Top + (i * vsize) + 8, col);
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private int LineHeight()
{
int h = 0;
if (Items.Count > 0)
{
SkinLayer l = Skin.Layers["Control"];
h = (int)l.Text.Font.Resource.LineSpacing + 9;
}
return h;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private int LineWidth()
{
int w = 0;
SkinFont font = Skin.Layers["Control"].Text.Font;
if (Items.Count > 0)
{
for (int i = 0; i < Items.Count; i++)
{
int wx = (int)font.Resource.MeasureString(Items[i].Text).X + 16;
if (wx > w) w = wx;
}
}
w += 4 + LineHeight();
return w;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void AutoSize()
{
SkinText font = Skin.Layers["Control"].Text;
if (Items != null && Items.Count > 0)
{
Height = (LineHeight() * Items.Count) + (Skin.Layers["Control"].ContentMargins.Vertical - Skin.OriginMargins.Vertical);
Width = LineWidth() + (Skin.Layers["Control"].ContentMargins.Horizontal - Skin.OriginMargins.Horizontal) + font.OffsetX;
}
else
{
Height = 16;
Width = 16;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void TrackItem(int x, int y)
{
if (Items != null && Items.Count > 0)
{
SkinText font = Skin.Layers["Control"].Text;
int h = LineHeight();
y -= Skin.Layers["Control"].ContentMargins.Top;
int i = (int)((float)y / h);
if (i < Items.Count)
{
if (i != ItemIndex && Items[i].Enabled)
{
if (ChildMenu != null)
{
this.HideMenu(false);
}
if (i >= 0 && i != ItemIndex)
{
Items[i].SelectedInvoke(new EventArgs());
}
Focused = true;
ItemIndex = i;
timer = (long)TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds;
}
else if (!Items[i].Enabled && ChildMenu == null)
{
ItemIndex = -1;
}
}
Invalidate();
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
TrackItem(e.Position.X, e.Position.Y);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected internal override void Update(GameTime gameTime)
{
base.Update(gameTime);
AutoSize();
long time = (long)TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds;
if (timer != 0 && time - timer >= Manager.MenuDelay && ItemIndex >= 0 && Items[ItemIndex].Items.Count > 0 && ChildMenu == null)
{
OnClick(new MouseEventArgs(new MouseState(), MouseButton.Left, Point.Zero));
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnMouseOut(MouseEventArgs e)
{
base.OnMouseOut(e);
if (!CheckArea(e.State.X, e.State.Y) && ChildMenu == null)
{
ItemIndex = -1;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnClick(EventArgs e)
{
if (sender != null && !(sender is MenuBase)) sender.Focused = true;
base.OnClick(e);
timer = 0;
MouseEventArgs ex = (e is MouseEventArgs) ? (MouseEventArgs)e : new MouseEventArgs();
if (ex.Button == MouseButton.Left || ex.Button == MouseButton.None)
{
if (ItemIndex >= 0 && Items[ItemIndex].Enabled)
{
if (ItemIndex >= 0 && Items[ItemIndex].Items != null && Items[ItemIndex].Items.Count > 0)
{
if (ChildMenu == null)
{
ChildMenu = new ContextMenu(Manager);
(ChildMenu as ContextMenu).RootMenu = this.RootMenu;
(ChildMenu as ContextMenu).ParentMenu = this;
(ChildMenu as ContextMenu).sender = sender;
ChildMenu.Items.AddRange(Items[ItemIndex].Items);
(ChildMenu as ContextMenu).AutoSize();
}
int y = AbsoluteTop + Skin.Layers["Control"].ContentMargins.Top + (ItemIndex * LineHeight());
(ChildMenu as ContextMenu).Show(sender, AbsoluteLeft + Width - 1, y);
if (ex.Button == MouseButton.None) (ChildMenu as ContextMenu).ItemIndex = 0;
}
else
{
if (ItemIndex >= 0)
{
Items[ItemIndex].ClickInvoke(ex);
}
if (RootMenu is ContextMenu) (RootMenu as ContextMenu).HideMenu(true);
else if (RootMenu is MainMenu)
{
(RootMenu as MainMenu).HideMenu();
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnKeyPress(KeyEventArgs e)
{
base.OnKeyPress(e);
timer = 0;
if (e.Key == Keys.Down || (e.Key == Keys.Tab && !e.Shift))
{
e.Handled = true;
ItemIndex += 1;
}
if (e.Key == Keys.Up || (e.Key == Keys.Tab && e.Shift))
{
e.Handled = true;
ItemIndex -=1;
}
if (ItemIndex > Items.Count - 1) ItemIndex = 0;
if (ItemIndex < 0) ItemIndex = Items.Count - 1;
if (e.Key == Keys.Right && Items[ItemIndex].Items.Count > 0)
{
e.Handled = true;
OnClick(new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero));
}
if (e.Key == Keys.Left)
{
e.Handled = true;
if (ParentMenu != null && ParentMenu is ContextMenu)
{
(ParentMenu as ContextMenu).Focused = true;
(ParentMenu as ContextMenu).HideMenu(false);
}
}
if (e.Key == Keys.Escape)
{
e.Handled = true;
if (ParentMenu != null) ParentMenu.Focused = true;
HideMenu(true);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
protected override void OnGamePadPress(GamePadEventArgs e)
{
timer = 0;
if (e.Button == GamePadButton.None) return;
if (e.Button == GamePadActions.Down || e.Button == GamePadActions.NextControl)
{
e.Handled = true;
ItemIndex += 1;
}
else if (e.Button == GamePadActions.Up || e.Button == GamePadActions.PrevControl)
{
e.Handled = true;
ItemIndex -= 1;
}
if (ItemIndex > Items.Count - 1) ItemIndex = 0;
if (ItemIndex < 0) ItemIndex = Items.Count - 1;
if (e.Button == GamePadActions.Right && Items[ItemIndex].Items.Count > 0)
{
e.Handled = true;
OnClick(new MouseEventArgs(new MouseState(), MouseButton.None, Point.Zero));
}
if (e.Button == GamePadActions.Left)
{
e.Handled = true;
if (ParentMenu != null && ParentMenu is ContextMenu)
{
(ParentMenu as ContextMenu).Focused = true;
(ParentMenu as ContextMenu).HideMenu(false);
}
}
base.OnGamePadPress(e);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual void HideMenu(bool hideCurrent)
{
if (hideCurrent)
{
Visible = false;
ItemIndex = -1;
}
if (ChildMenu != null)
{
(ChildMenu as ContextMenu).HideMenu(true);
ChildMenu.Dispose();
ChildMenu = null;
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public override void Show()
{
Show(null, Left, Top);
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public virtual void Show(Control sender, int x, int y)
{
AutoSize();
base.Show();
if (!Initialized) Init();
if (sender != null && sender.Root != null && sender.Root is Container)
{
(sender.Root as Container).Add(this, false);
}
else
{
Manager.Add(this);
}
this.sender = sender;
if (sender != null && sender.Root != null && sender.Root is Container)
{
Left = x - Root.AbsoluteLeft;
Top = y - Root.AbsoluteTop;
}
else
{
Left = x;
Top = y;
}
if (AbsoluteLeft + Width > Manager.TargetWidth)
{
Left = Left - Width;
if (ParentMenu != null && ParentMenu is ContextMenu)
{
Left = Left - ParentMenu.Width + 2;
}
else if (ParentMenu != null)
{
Left = Manager.TargetWidth - (Parent != null ? Parent.AbsoluteLeft : 0) - Width - 2;
}
}
if (AbsoluteTop + Height > Manager.TargetHeight)
{
Top = Top - Height;
if (ParentMenu != null && ParentMenu is ContextMenu)
{
Top = Top + LineHeight();
}
else if (ParentMenu != null)
{
Top = ParentMenu.Top - Height - 1;
}
}
Focused = true;
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private void Input_MouseDown(object sender, MouseEventArgs e)
{
if ((RootMenu is ContextMenu) && !(RootMenu as ContextMenu).CheckArea(e.Position.X, e.Position.Y) && Visible)
{
HideMenu(true);
}
else if ((RootMenu is MainMenu) && RootMenu.ChildMenu != null && !(RootMenu.ChildMenu as ContextMenu).CheckArea(e.Position.X, e.Position.Y) && Visible)
{
(RootMenu as MainMenu).HideMenu();
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private bool CheckArea(int x, int y)
{
if (Visible)
{
if (x <= AbsoluteLeft ||
x >= AbsoluteLeft + Width ||
y <= AbsoluteTop ||
y >= AbsoluteTop + Height)
{
bool ret = false;
if (ChildMenu != null)
{
ret = (ChildMenu as ContextMenu).CheckArea(x, y);
}
return ret;
}
else
{
return true;
}
}
else
{
return false;
}
}
////////////////////////////////////////////////////////////////////////////
#endregion
}
}

3045
Neoforce/Control.cs Normal file

File diff suppressed because it is too large Load Diff

56
Neoforce/Cursor.cs Normal file
View File

@ -0,0 +1,56 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TomShane.Neoforce.Controls
{
/// <summary>
/// Provides a basic Software cursor
/// </summary>
public class Cursor
{
private Texture2D cursorTexture;
public Texture2D CursorTexture
{
get { return cursorTexture;}
set { cursorTexture = value; }
}
internal string cursorPath;
private Vector2 hotspot;
private int width;
private int height;
public int Height
{
get { return height; }
set { height = value; }
}
public int Width
{
get { return width; }
set { width = value; }
}
public Vector2 HotSpot
{
get { return hotspot; }
set { hotspot = value; }
}
public Cursor(string path, Vector2 hotspot, int width, int height)
{
this.cursorPath = path;
this.hotspot = hotspot;
this.width = width;
this.height = height;
}
}
}

53
Neoforce/Delegates.cs Normal file
View File

@ -0,0 +1,53 @@
////////////////////////////////////////////////////////////////
// //
// Neoforce Controls //
// //
////////////////////////////////////////////////////////////////
// //
// File: Delegates.cs //
// //
// Version: 0.7 //
// //
// Date: 11/09/2010 //
// //
// Author: Tom Shane //
// //
////////////////////////////////////////////////////////////////
// //
// Copyright (c) by Tom Shane //
// //
////////////////////////////////////////////////////////////////
#region //// Using /////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
#endregion
namespace TomShane.Neoforce.Controls
{
#region //// Delegates /////////
////////////////////////////////////////////////////////////////////////////
public delegate void DeviceEventHandler(DeviceEventArgs e);
public delegate void SkinEventHandler(EventArgs e);
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
public delegate void EventHandler(object sender, EventArgs e);
public delegate void MouseEventHandler(object sender, MouseEventArgs e);
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public delegate void GamePadEventHandler(object sender, GamePadEventArgs e);
public delegate void DrawEventHandler(object sender, DrawEventArgs e);
public delegate void MoveEventHandler(object sender, MoveEventArgs e);
public delegate void ResizeEventHandler(object sender, ResizeEventArgs e);
public delegate void WindowClosingEventHandler(object sender, WindowClosingEventArgs e);
public delegate void WindowClosedEventHandler(object sender, WindowClosedEventArgs e);
public delegate void ConsoleMessageEventHandler(object sender, ConsoleMessageEventArgs e);
////////////////////////////////////////////////////////////////////////////
#endregion
}

Some files were not shown because too many files have changed in this diff Show More