mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
* bsdiff submodule * All the other stuff * Upgrade to 0.2 of SS14.Shared.BSdiff * Use streams. Even faster! * All rendering refactors should start with the killing of the SFML submodule. Trust me I've done this before! * And after that comes the removal of the references in the project files! Of course, neither of these compile. Meh. * IoC loads, now to port the entire client over. * More work * Trying to debug the null exception. * More messy WiP code * More WiP * Transform components now make Node2Ds inside the scene tree. * Work on the UI system. * Redo project configurations to work better. * More work on the Godot GUI binding. Added BaseButton and Button bindings, including events for them. More APIs on Controls like GetChild() and alike. Ability to instance a Godot PackedScene and have it automatically be wrapped by the SS14 GUI wrapper, thus allowing the usage of Godot's GUI editor while maintaining the good C# API. * Fix incorrect sRGB profile on bootsplash.png * LineEdit API. * Maybe commit the csproj too. * Exit button works now! * Change MainMenu root to a normal Control. * Some stuff messing with window popups. * Fix popup not scaling down again after housing large text. * Make popup dialog helper. * Auto wrap controls that spawn others when not instancing. * Nice typo me. * Work getting server connections working. * Server didn't start anymore due to still trying to load removed zip packs. * Made MainMenu dump UI elements in shutdown. * We now successfully connect to the server. * WE Ingame NOW! * Basic map rendering works now! * Camera & Input work. Though I'm not sure whether the input issues are this piss poor laptop or actual bugs. * Fix input issues. KeyHeld() was firing KeyUp() into States, which broke everything. * WiP Debug console. UI Works, command processing doesn't. * Remove some debug logs. * Fixing focusing issues with the Debug Console. * In which I copy paste in the DebugConsole code. * More WiP DebugConsole work. * Use RichTextLabel for DebugConsole. * Disable DebugConsole test text. Also disable context menu on the IP Box because of font size issues. * ITextureSource for texture wrapping. * Make resource loading not copy every build. It now loads from repo root. Release builds are TODO. * Bsdiff 0.3 for fixed targets. * Fix iCCP sRGB errors. * Give full texture paths to prototypes. * Sprite component old API restored. *shudder* * Finish sprite rendering. By hitting delete on basically everything. * Fixing camera delay with this one weird trick! The input lag was because the camera lagged, not because the input lagged. I didn't have visualization. * Use greyshirt as temporary human replacement. * FPS Counter. * Fix Windows, re-add space tile def. Windows broke because Mono on Windows can't use System.IO.Compression. The GZIP streams use by MsgFullState have been moved to ICSharpCode.SharpZipLib.GZip. Works fine. Also re-added the space tile def because everything blows up otherwise I guess. * Debug colliders works. * Highly WiP varied Window code. * Work on the quit button. Doesn't work yet. Needs state overhaul. * Do a better job of freeing Godot objects correctly. * Simplify game states. States are no longer cached. They're GC'd after shut down. Their creation has been simplified too due to IoCManager.InjectDependencies(). This makes returning to main menu work GUI wise. * Be less aggressive on resource freeing. Only dispose Mono handles now. * More work getting quit to work. * Grids get cleared upon disconnect correctly. * Disable rendering of the filler space tile. I'll leave a nice parallax background up to content. * Oops forgot to stage these. * Fix issues in the csproj file. * Make controls have own namespace. * Chatbox GUI, other stuff. Compiles but doesn't quite work yet. * Git fuck you * Chat works. * Clicking on a not-control removes focus now. * Fix an exception. * Update mono. Use enums instead of constants now! * Fix window dragging * Fix chat stealing focus from other LineEdits on MacOS I guess. * Correctly handle client shutdown & server disconnect. * Fix error spam on client shutdown probably. * Tiny amount of lighting code to have access to it from other computers * Lighting works mostly. * Godot.Texture.Flags -> FlagsEnum * More WiP lighting code * Turns out you can't control custom layers with cameras eh. * WiP lighting almost works * Lighting WORKS! * Lobby thing. * Some options menu work. I'm gonna try something * Options menu works. * More improvements. * In game options menu works. * Remove a debug log. * Fix Window Movement and Drag. * Huh Godot edited these scenes. * Forgot to commit projects * It compiles. * I never claimed that. * Update sandbox csprojs a bit. * Makes sandbox load. Client goes ingame but dies due to broken map manager networking code. * Fix grids. * Fix relogging duplicating entities inside Godot. * Eyes! * How about removing the TODO entry. * Auto fetch bsdiffwrap. * Update TODO. * Remove TODO list. * Tilemaps get cleaned on disconnect now. * Fix bsdiff submodule HOPEFULLY * Highly WiP and not compiling or working placement. Yes this is the best way for me to share code between my computers. * Fix bsdiff with spaces in path names bloody MSBuild + cmd. * PLACEMENT KINDA WORKS. NO RENDERING OR TILES YET. ALSO I NEED TO REBASE THIS. * Kill EngineContentPack.zip * Fix map code and remove sprite components from the server. * Ok entity placement mostly works. * Grid lines sorta work but SnapgridCenter is still broke. * Fix Center Grid Rendering. * Work getting tile spawns to work. Not quite there yet. * Fix placement and remove tiledef networking. It werks! * Remove SolutionSpecific.targets Didn't end up being needed. * Kill off wearableanimatedsprite component states. * Do not put binaries relative to the solution. * Remove shaders, reorganize prototypes for content. * Reimplement SpriteComponent color. * Correctly set __engine_human DrawDepth. * Round coordinates passed to Godot to pixels to prevent issues. * Remove some GUI log spam. * Resource cache now uses the VFS more-proper but still awfully. * Fix color reading code on SpriteComponent. See, e94bf4854b106da51c89eeeab9a609be369f9622 did work. The problem was all the code it NEEDED to work was broken. * Step one into trying to fix Travis and AppVeyor. * Auto download GodotSharp on Travis. * Let's not make dumb mistakes with the cache directories. * 2018 and requests still isn't in the stdlib. * This maybe? * This maybe? * AppVeyor time. * How 'bout we don't download sonarqube at all outside master. * Try to cache on AppVeyor. * Fix mac builds maybe. * Finishing up Godot: cleanup. * Eh this works too for SS14Loader. * Remove some dead files * Make Omnisharp not choke on buildchecker. * Controls for box containers. * Remove debug port from project.godot. * Control and drawing improvements. Controls are now properly capable of overriding Godot virtuals. Importantly minsize calculations and stuff like HasPoint. There is now a system for doing direct drawing on controls. This is done with a DrawingHandle system. TextureSource has been renamed to Texture. Also there's a wrapping for Godot's style boxes now. * Yeah don't insult omnisharp. * Stylebox fixes and margin API on controls. * Hey it compiles! * Fix things. * Fix null godot texture conversion. * Fix client transform parenting. * Fix movement sticking to north. * Some updates. * Work on exports. * Unstubs client/Godot timing code. It's mostly implemented now. * Client unit tests work. Jesus Christ. * Let's figure out why AppVeyor hates me. * Does the remie. * Update GodotSharp download URL. * Export templates for the builds server. * Remove mac export icon. * TO THE BUILDS SERVER. * Probably implement the effects system in Godot. * Fix mouse handling everywhere. * Fix some CollidableComponent exceptions. * Effects system works + unshaded option for LAZORS. * Let's not fuck with Angle yet. * Make file/line numbers show up on Windows Godot.
694 lines
20 KiB
C#
694 lines
20 KiB
C#
#if !__ANDROID__ && !IOS
|
|
#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 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 = new AutoResetEvent(false);
|
|
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)
|
|
{
|
|
if (SynchronizationContext.Current == 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>(SynchronizationContext.Current, 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;
|
|
m_receiveCallbacks.Remove(new NetTuple<SynchronizationContext, SendOrPostCallback>(SynchronizationContext.Current, callback));
|
|
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)
|
|
tuple.Item1.Post(tuple.Item2, this);
|
|
}
|
|
}
|
|
|
|
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
|
|
IPEndPoint iep = null;
|
|
|
|
iep = new IPEndPoint(m_configuration.LocalAddress, m_configuration.Port);
|
|
EndPoint ep = (EndPoint)iep;
|
|
|
|
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
|
m_socket.ReceiveBufferSize = m_configuration.ReceiveBufferSize;
|
|
m_socket.SendBufferSize = m_configuration.SendBufferSize;
|
|
m_socket.Blocking = false;
|
|
m_socket.Bind(ep);
|
|
|
|
var platform = Environment.OSVersion.Platform;
|
|
if (platform != PlatformID.MacOSX && platform != PlatformID.Unix)
|
|
{
|
|
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;
|
|
|
|
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];
|
|
NetRandom.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
|
|
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(SHA1.Create().ComputeHash(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 { }
|
|
m_socket.Close(2); // 2 seconds timeout
|
|
}
|
|
if (m_messageReceivedEvent != null)
|
|
{
|
|
try
|
|
{
|
|
m_messageReceivedEvent.Set();
|
|
m_messageReceivedEvent.Close();
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
// For some reason, inside Godot this seems to throw ObjectDisposedExceptions on client shutdown.
|
|
// If it's already disposed then I guess this is fine?
|
|
}
|
|
finally
|
|
{
|
|
m_messageReceivedEvent = null;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
m_socket = null;
|
|
m_status = NetPeerStatus.NotRunning;
|
|
LogDebug("Shutdown complete");
|
|
}
|
|
|
|
m_receiveBuffer = null;
|
|
m_sendBuffer = null;
|
|
m_unsentUnconnectedMessages.Clear();
|
|
m_connections.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;
|
|
|
|
do
|
|
{
|
|
int bytesReceived = 0;
|
|
try
|
|
{
|
|
bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
|
|
}
|
|
catch (SocketException sx)
|
|
{
|
|
if (sx.SocketErrorCode == 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;
|
|
}
|
|
|
|
LogWarning(sx.ToString());
|
|
return;
|
|
}
|
|
|
|
if (bytesReceived < NetConstants.HeaderByteSize)
|
|
return;
|
|
|
|
IPEndPoint ipsender = (IPEndPoint)m_senderRemote;
|
|
|
|
if (ipsender.Port == 1900)
|
|
{
|
|
// UPnP response
|
|
try
|
|
{
|
|
string resp = System.Text.Encoding.ASCII.GetString(m_receiveBuffer, 0, bytesReceived);
|
|
if (resp.Contains("upnp:rootdevice"))
|
|
{
|
|
resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
|
|
resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
|
|
m_upnp.ExtractServiceUrl(resp);
|
|
return;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
NetConnection sender = null;
|
|
m_connectionLookup.TryGetValue(ipsender, out sender);
|
|
|
|
double receiveTime = NetTime.Now;
|
|
//
|
|
// parse packet into messages
|
|
//
|
|
int numMessages = 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));
|
|
|
|
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;
|
|
}
|
|
|
|
try
|
|
{
|
|
NetException.Assert(tp < NetMessageType.Unused1 || tp > NetMessageType.Unused29);
|
|
|
|
if (tp >= NetMessageType.LibraryError)
|
|
{
|
|
if (sender != null)
|
|
sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength);
|
|
else
|
|
ReceivedUnconnectedLibraryMessage(receiveTime, 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 = receiveTime;
|
|
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);
|
|
if (sender != null)
|
|
sender.m_statistics.PacketReceived(bytesReceived, numMessages);
|
|
|
|
} 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:
|
|
HandleNatIntroduction(ptr);
|
|
return;
|
|
case NetMessageType.NatPunchMessage:
|
|
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:
|
|
// proceed
|
|
break;
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
internal void AcceptConnection(NetConnection 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;
|
|
}
|
|
}
|
|
}
|