소스 검색

修复读取配置错误,加上一个console插件

tanghai 8 년 전
부모
커밋
d2ce74c290
63개의 변경된 파일6553개의 추가작업 그리고 2개의 파일을 삭제
  1. 1 0
      .gitignore
  2. BIN
      Excel/UnitConfig.xlsx
  3. 9 0
      Unity/Assets/ConsolePro.meta
  4. 87 0
      Unity/Assets/ConsolePro/ConsoleProDebug.cs
  5. 12 0
      Unity/Assets/ConsolePro/ConsoleProDebug.cs.meta
  6. BIN
      Unity/Assets/ConsolePro/Editor Console Pro Documentation.pdf
  7. 8 0
      Unity/Assets/ConsolePro/Editor Console Pro Documentation.pdf.meta
  8. 9 0
      Unity/Assets/ConsolePro/Editor.meta
  9. BIN
      Unity/Assets/ConsolePro/Editor/ConsolePro.Editor.dll
  10. 34 0
      Unity/Assets/ConsolePro/Editor/ConsolePro.Editor.dll.meta
  11. 9 0
      Unity/Assets/ConsolePro/Remote.meta
  12. 233 0
      Unity/Assets/ConsolePro/Remote/ConsoleProRemoteServer.cs
  13. 12 0
      Unity/Assets/ConsolePro/Remote/ConsoleProRemoteServer.cs.meta
  14. 9 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib.meta
  15. 120 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/FastBitConverter.cs
  16. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/FastBitConverter.cs.meta
  17. 128 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/INetEventListener.cs
  18. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/INetEventListener.cs.meta
  19. 231 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NatPunchModule.cs
  20. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NatPunchModule.cs.meta
  21. 53 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetConstants.cs
  22. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetConstants.cs.meta
  23. 444 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDataReader.cs
  24. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDataReader.cs.meta
  25. 375 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDataWriter.cs
  26. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDataWriter.cs.meta
  27. 16 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDebug.cs
  28. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDebug.cs.meta
  29. 221 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetEndPoint.cs
  30. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetEndPoint.cs.meta
  31. 1039 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetManager.cs
  32. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetManager.cs.meta
  33. 163 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPacket.cs
  34. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPacket.cs.meta
  35. 101 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPacketPool.cs
  36. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPacketPool.cs.meta
  37. 857 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPeer.cs
  38. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPeer.cs.meta
  39. 81 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPeerCollection.cs
  40. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPeerCollection.cs.meta
  41. 709 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetSerializer.cs
  42. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetSerializer.cs.meta
  43. 455 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetSocket.cs
  44. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetSocket.cs.meta
  45. 97 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetThread.cs
  46. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetThread.cs.meta
  47. 219 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetUtils.cs
  48. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NetUtils.cs.meta
  49. 59 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NtpSyncModule.cs
  50. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/NtpSyncModule.cs.meta
  51. 375 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/ReliableChannel.cs
  52. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/ReliableChannel.cs.meta
  53. 4 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/RemoteFix
  54. 8 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/RemoteFix.meta
  55. 53 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/SequencedChannel.cs
  56. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/SequencedChannel.cs.meta
  57. 40 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/SimpleChannel.cs
  58. 12 0
      Unity/Assets/ConsolePro/Remote/LiteNetLib/SimpleChannel.cs.meta
  59. 1 1
      Unity/Assets/Editor/ExcelExporterEditor/ExcelExporterEditor.cs
  60. 1 1
      Unity/Assets/Res/Config/UnitConfig.txt
  61. 2 0
      Unity/Assets/Resources/Config.prefab
  62. 3 0
      Unity/Unity.Editor.csproj
  63. 23 0
      Unity/Unity.csproj

+ 1 - 0
.gitignore

@@ -38,3 +38,4 @@ _ReSharper.CSharp/
 /Unity/.vs/
 /Tools/MongoDB
 /Excel/md5.txt
+/Release

BIN
Excel/UnitConfig.xlsx


+ 9 - 0
Unity/Assets/ConsolePro.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 19dec742fd3e633478542409d5e30874
+folderAsset: yes
+timeCreated: 1505442607
+licenseType: Free
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 87 - 0
Unity/Assets/ConsolePro/ConsoleProDebug.cs

@@ -0,0 +1,87 @@
+using UnityEngine;
+using System;
+#if UNITY_EDITOR
+using System.Reflection;
+#endif
+
+public static class ConsoleProDebug
+{
+	// Clear the console and the native console
+	public static void Clear()
+	{
+		#if UNITY_EDITOR
+		if(ConsoleClearMethod != null)
+		{
+			ConsoleClearMethod.Invoke(null, null);
+		}
+		#endif
+	}
+
+	// Send a log to a specific filter regardless of contents
+	// Ex: ConsoleProDebug.LogToFilter("Hi", "CustomFilter");
+	public static void LogToFilter(string inLog, string inFilterName)
+	{
+		Debug.Log(inLog + "\nCPAPI:{\"cmd\":\"Filter\" \"name\":\"" + inFilterName + "\"}");
+	}
+
+	// Watch a variable. This will only produce one log entry regardless of how many times it is logged, allowing you to track variables without spam.
+	// Ex:
+	// void Update() {
+	// ConsoleProDebug.Watch("Player X Position", transform.position.x);
+	// }
+	public static void Watch(string inName, string inValue)
+	{
+		Debug.Log(inName + " : " + inValue + "\nCPAPI:{\"cmd\":\"Watch\" \"name\":\"" + inName + "\"}");
+	}
+
+	#if UNITY_EDITOR
+	// Reflection calls to access Console Pro from runtime
+	private static bool _checkedConsoleClearMethod = false;
+	private static MethodInfo _consoleClearMethod = null;
+	private static MethodInfo ConsoleClearMethod
+	{
+		get
+		{
+			if(_consoleClearMethod == null || !_checkedConsoleClearMethod)
+			{
+				_checkedConsoleClearMethod = true;
+				if(ConsoleWindowType == null)
+				{
+					return null;
+				}
+
+				_consoleClearMethod = ConsoleWindowType.GetMethod("ClearEntries", BindingFlags.Static | BindingFlags.Public);
+			}
+
+			return _consoleClearMethod;
+		}
+	}
+
+	private static bool _checkedConsoleWindowType = false;
+	private static Type _consoleWindowType = null;
+	private static Type ConsoleWindowType
+	{
+		get
+		{
+			if(_consoleWindowType == null || !_checkedConsoleWindowType)
+			{
+				_checkedConsoleWindowType = true;
+				Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
+				for(int iAssembly = 0; iAssembly < assemblies.Length; iAssembly++)
+				{
+					Type[] types = assemblies[iAssembly].GetTypes();
+					for(int iType = 0; iType < types.Length; iType++)
+					{
+						if(types[iType].Name == "ConsolePro3Window")
+						{
+							_consoleWindowType = types[iType];
+						}
+					}
+				}
+			}
+
+			return _consoleWindowType;
+		}
+	}
+	#endif
+}

+ 12 - 0
Unity/Assets/ConsolePro/ConsoleProDebug.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 30f42e8a12eb842acbe9a63057fb00e4
+timeCreated: 1469329295
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Unity/Assets/ConsolePro/Editor Console Pro Documentation.pdf


+ 8 - 0
Unity/Assets/ConsolePro/Editor Console Pro Documentation.pdf.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5de782a9528f04b41a8ba70afba32a61
+timeCreated: 1498113024
+licenseType: Store
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
Unity/Assets/ConsolePro/Editor.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: c8115764eb65749ffb9c91f29635cac7
+folderAsset: yes
+timeCreated: 1503191482
+licenseType: Store
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Unity/Assets/ConsolePro/Editor/ConsolePro.Editor.dll


+ 34 - 0
Unity/Assets/ConsolePro/Editor/ConsolePro.Editor.dll.meta

@@ -0,0 +1,34 @@
+fileFormatVersion: 2
+guid: c0d5bba9040f9408abc49f3d1b343326
+timeCreated: 1503191496
+licenseType: Store
+PluginImporter:
+  serializedVersion: 2
+  iconMap: {}
+  executionOrder: {}
+  isPreloaded: 0
+  isOverridable: 0
+  platformData:
+    data:
+      first:
+        Any: 
+      second:
+        enabled: 0
+        settings: {}
+    data:
+      first:
+        Editor: Editor
+      second:
+        enabled: 1
+        settings:
+          DefaultValueInitialized: true
+    data:
+      first:
+        Windows Store Apps: WindowsStoreApps
+      second:
+        enabled: 0
+        settings:
+          CPU: AnyCPU
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
Unity/Assets/ConsolePro/Remote.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 0863131177dd4429395aaa386c1ed336
+folderAsset: yes
+timeCreated: 1503191482
+licenseType: Store
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 233 - 0
Unity/Assets/ConsolePro/Remote/ConsoleProRemoteServer.cs

@@ -0,0 +1,233 @@
+// Uncomment to use in Editor
+// #define USECONSOLEPROREMOTESERVERINEDITOR
+
+#if (!UNITY_EDITOR && DEBUG) || (UNITY_EDITOR && USECONSOLEPROREMOTESERVERINEDITOR)
+	#define USECONSOLEPROREMOTESERVER
+#endif
+
+#if (UNITY_WP_8_1 || UNITY_WSA_8_1)
+	#define UNSUPPORTEDCONSOLEPROREMOTESERVER
+#endif
+
+using UnityEngine;
+using System;
+using System.Collections.Generic;
+
+#if USECONSOLEPROREMOTESERVER
+using FlyingWormConsole3.LiteNetLib;
+using FlyingWormConsole3.LiteNetLib.Utils;
+#endif
+
+namespace FlyingWormConsole3
+{
+	#if USECONSOLEPROREMOTESERVER
+public class ConsoleProRemoteServer : MonoBehaviour, INetEventListener
+	#else
+public class ConsoleProRemoteServer : MonoBehaviour
+	#endif
+{
+	public bool useNATPunch = false;
+	public int port = 51000;
+
+	#if UNITY_EDITOR && !USECONSOLEPROREMOTESERVER
+
+	#elif UNSUPPORTEDCONSOLEPROREMOTESERVER
+
+	public void Awake()
+	{
+		Debug.Log("Console Pro Remote Server is not supported on this platform");
+	}
+
+	#elif !USECONSOLEPROREMOTESERVER
+	
+	public void Awake()
+	{
+		Debug.Log("Console Pro Remote Server is disabled in release mode, please use a Development build or define DEBUG to use it");
+	}
+
+	#else
+
+	private NetManager _netServer;
+	private NetPeer _ourPeer;
+	private NetDataWriter _dataWriter;
+	private NetSerializer _serializer;
+
+	[System.SerializableAttribute]
+	public class QueuedLog
+	{
+		public string message;
+		public string stackTrace;
+		public LogType type;
+	}
+
+	
+	[NonSerializedAttribute]
+	public List<QueuedLog> logs = new List<QueuedLog>();
+
+	private static ConsoleProRemoteServer instance = null;
+
+	void Awake()
+	{
+		if(instance != null)
+		{
+			Destroy(gameObject);
+		}
+		
+		instance = this;
+		
+		DontDestroyOnLoad(gameObject);
+
+		Debug.Log("Starting Console Pro Server on port : " + port);
+
+		_dataWriter = new NetDataWriter();
+		_netServer = new NetManager(this, 100, "ConsolePro");
+		_netServer.Start(port);
+		_netServer.DiscoveryEnabled = true;
+		_netServer.UpdateTime = 15;
+		_netServer.MergeEnabled = true;
+		_netServer.NatPunchEnabled = useNATPunch;
+		_serializer = new NetSerializer();
+	}
+
+	void OnDestroy()
+	{
+		if(_netServer != null)
+		{
+			_netServer.Stop();
+		}
+	}
+
+	public void OnPeerConnected(NetPeer peer)
+	{
+		Debug.Log("Connected to " + peer.EndPoint);
+		_ourPeer = peer;
+	}
+
+	public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
+	{
+		Debug.Log("Disconnected from " + peer.EndPoint + ", info: " + disconnectInfo.Reason);
+		if (peer == _ourPeer)
+		{
+			_ourPeer = null;
+		}
+	}
+
+	public void OnNetworkReceive(NetPeer peer, NetDataReader reader)
+	{
+	
+	}
+
+	public void OnPeerDisconnected(NetPeer peer, DisconnectReason reason, int socketErrorCode)
+	{
+
+	}
+
+	public void OnNetworkError(NetEndPoint endPoint, int socketErrorCode)
+	{
+	}
+
+	public void OnNetworkReceiveUnconnected(NetEndPoint remoteEndPoint, NetDataReader reader, UnconnectedMessageType messageType)
+	{
+		if (messageType == UnconnectedMessageType.DiscoveryRequest)
+		{
+			// Debug.Log("[SERVER] Received discovery request. Send discovery response");
+			_netServer.SendDiscoveryResponse(new byte[] {1}, remoteEndPoint);
+		}
+	}
+
+	public void OnNetworkLatencyUpdate(NetPeer peer, int latency)
+	{
+		
+	}
+
+
+	#if UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_4_8 || UNITY_4_9
+
+	void OnEnable()
+	{
+		Application.RegisterLogCallback(LogCallback);
+	}
+
+	void Update()
+	{
+		Application.RegisterLogCallback(LogCallback);
+	}
+
+	void OnDisable()
+	{
+		Application.RegisterLogCallback(null);
+	}
+
+	#else
+
+	void OnEnable()
+	{
+		Application.logMessageReceived += LogCallback;
+	}
+
+	void OnDisable()
+	{
+		Application.logMessageReceived -= LogCallback;
+	}
+
+	#endif
+
+	public void LogCallback(string logString, string stackTrace, LogType type)
+	{
+		if(!logString.StartsWith("CPIGNORE"))
+		{
+			QueueLog(logString, stackTrace, type);
+		}
+	}
+
+	void QueueLog(string logString, string stackTrace, LogType type)
+	{
+		if(logs.Count > 200)
+		{
+			while(logs.Count > 200)
+			{
+				logs.RemoveAt(0);
+			}
+		}
+
+		logs.Add(new QueuedLog() { message = logString, stackTrace = stackTrace, type = type } );
+	}
+
+	void LateUpdate()
+	{
+		if(_netServer == null)
+		{
+			return;
+		}
+
+		_netServer.PollEvents();
+
+		if(_ourPeer == null)
+		{
+			return;
+		}
+
+		if(logs.Count <= 0)
+		{
+			return;
+		}
+
+		string cMessage = "";
+
+		for(int i = 0; i < logs.Count; i++)
+		{
+			cMessage = "";
+
+			QueuedLog cLog = logs[i];
+			cMessage = "::::" + cLog.type + "::::" + cLog.message + "\n" + cLog.stackTrace;
+			_dataWriter.Reset();
+			_dataWriter.Put(cMessage);
+			_ourPeer.Send(_dataWriter, SendOptions.ReliableOrdered);
+		}
+
+		logs.Clear();
+	}
+
+	#endif
+}
+}

+ 12 - 0
Unity/Assets/ConsolePro/Remote/ConsoleProRemoteServer.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: d6bcfaced529e418bb75980b297fda2a
+timeCreated: 1437614101
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 2c6bd635eeaa04c228b6d342c4758ad7
+folderAsset: yes
+timeCreated: 1494014730
+licenseType: Store
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 120 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/FastBitConverter.cs

@@ -0,0 +1,120 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System.Runtime.InteropServices;
+
+namespace FlyingWormConsole3.LiteNetLib.Utils
+{
+    public static class FastBitConverter
+    {
+        [StructLayout(LayoutKind.Explicit)]
+        private struct ConverterHelperDouble
+        {
+            [FieldOffset(0)]
+            public ulong Along;
+
+            [FieldOffset(0)]
+            public double Adouble;
+        }
+
+        [StructLayout(LayoutKind.Explicit)]
+        private struct ConverterHelperFloat
+        {
+            [FieldOffset(0)]
+            public int Aint;
+
+            [FieldOffset(0)]
+            public float Afloat;
+        }
+
+        private static void WriteLittleEndian(byte[] buffer, int offset, ulong data)
+        {
+#if BIGENDIAN
+            buffer[offset + 7] = (byte)(data);
+            buffer[offset + 6] = (byte)(data >> 8);
+            buffer[offset + 5] = (byte)(data >> 16);
+            buffer[offset + 4] = (byte)(data >> 24);
+            buffer[offset + 3] = (byte)(data >> 32);
+            buffer[offset + 2] = (byte)(data >> 40);
+            buffer[offset + 1] = (byte)(data >> 48);
+            buffer[offset    ] = (byte)(data >> 56);
+#else
+            buffer[offset] = (byte)(data);
+            buffer[offset + 1] = (byte)(data >> 8);
+            buffer[offset + 2] = (byte)(data >> 16);
+            buffer[offset + 3] = (byte)(data >> 24);
+            buffer[offset + 4] = (byte)(data >> 32);
+            buffer[offset + 5] = (byte)(data >> 40);
+            buffer[offset + 6] = (byte)(data >> 48);
+            buffer[offset + 7] = (byte)(data >> 56);
+#endif
+        }
+
+        private static void WriteLittleEndian(byte[] buffer, int offset, int data)
+        {
+#if BIGENDIAN
+            buffer[offset + 3] = (byte)(data);
+            buffer[offset + 2] = (byte)(data >> 8);
+            buffer[offset + 1] = (byte)(data >> 16);
+            buffer[offset    ] = (byte)(data >> 24);
+#else
+            buffer[offset] = (byte)(data);
+            buffer[offset + 1] = (byte)(data >> 8);
+            buffer[offset + 2] = (byte)(data >> 16);
+            buffer[offset + 3] = (byte)(data >> 24);
+#endif
+        }
+
+        public static void WriteLittleEndian(byte[] buffer, int offset, short data)
+        {
+#if BIGENDIAN
+            buffer[offset + 1] = (byte)(data);
+            buffer[offset    ] = (byte)(data >> 8);
+#else
+            buffer[offset] = (byte)(data);
+            buffer[offset + 1] = (byte)(data >> 8);
+#endif
+        }
+
+        public static void GetBytes(byte[] bytes, int startIndex, double value)
+        {
+            ConverterHelperDouble ch = new ConverterHelperDouble { Adouble = value };
+            WriteLittleEndian(bytes, startIndex, ch.Along);
+        }
+
+        public static void GetBytes(byte[] bytes, int startIndex, float value)
+        {
+            ConverterHelperFloat ch = new ConverterHelperFloat { Afloat = value };
+            WriteLittleEndian(bytes, startIndex, ch.Aint);
+        }
+
+        public static void GetBytes(byte[] bytes, int startIndex, short value)
+        {
+            WriteLittleEndian(bytes, startIndex, value);
+        }
+
+        public static void GetBytes(byte[] bytes, int startIndex, ushort value)
+        {
+            WriteLittleEndian(bytes, startIndex, (short)value);
+        }
+
+        public static void GetBytes(byte[] bytes, int startIndex, int value)
+        {
+            WriteLittleEndian(bytes, startIndex, value);
+        }
+
+        public static void GetBytes(byte[] bytes, int startIndex, uint value)
+        {
+            WriteLittleEndian(bytes, startIndex, (int)value);
+        }
+
+        public static void GetBytes(byte[] bytes, int startIndex, long value)
+        {
+            WriteLittleEndian(bytes, startIndex, (ulong)value);
+        }
+
+        public static void GetBytes(byte[] bytes, int startIndex, ulong value)
+        {
+            WriteLittleEndian(bytes, startIndex, value);
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/FastBitConverter.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 55df0de9a58e74c9395dfe5ffdab9a5a
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 128 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/INetEventListener.cs

@@ -0,0 +1,128 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using FlyingWormConsole3.LiteNetLib.Utils;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public enum UnconnectedMessageType
+    {
+        Default,
+        DiscoveryRequest,
+        DiscoveryResponse
+    }
+
+    public enum DisconnectReason
+    {
+        SocketReceiveError,
+        ConnectionFailed,
+        Timeout,
+        SocketSendError,
+        RemoteConnectionClose,
+        DisconnectPeerCalled
+    }
+
+    public struct DisconnectInfo
+    {
+        public DisconnectReason Reason;
+        public int SocketErrorCode;
+        public NetDataReader AdditionalData;
+    }
+
+    public interface INetEventListener
+    {
+        /// <summary>
+        /// New remote peer connected to host, or client connected to remote host
+        /// </summary>
+        /// <param name="peer">Connected peer object</param>
+        void OnPeerConnected(NetPeer peer);
+
+        /// <summary>
+        /// Peer disconnected
+        /// </summary>
+        /// <param name="peer">disconnected peer</param>
+        /// <param name="disconnectInfo">additional info about reason, errorCode or data received with disconnect message</param>
+        void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo);
+
+        /// <summary>
+        /// Network error (on send or receive)
+        /// </summary>
+        /// <param name="endPoint">From endPoint (can be null)</param>
+        /// <param name="socketErrorCode">Socket error code</param>
+        void OnNetworkError(NetEndPoint endPoint, int socketErrorCode);
+
+        /// <summary>
+        /// Received some data
+        /// </summary>
+        /// <param name="peer">From peer</param>
+        /// <param name="reader">DataReader containing all received data</param>
+        void OnNetworkReceive(NetPeer peer, NetDataReader reader);
+
+        /// <summary>
+        /// Received unconnected message
+        /// </summary>
+        /// <param name="remoteEndPoint">From address (IP and Port)</param>
+        /// <param name="reader">Message data</param>
+        /// <param name="messageType">Message type (simple, discovery request or responce)</param>
+        void OnNetworkReceiveUnconnected(NetEndPoint remoteEndPoint, NetDataReader reader, UnconnectedMessageType messageType);
+
+        /// <summary>
+        /// Latency information updated
+        /// </summary>
+        /// <param name="peer">Peer with updated latency</param>
+        /// <param name="latency">latency value in milliseconds</param>
+        void OnNetworkLatencyUpdate(NetPeer peer, int latency);
+    }
+
+    public class EventBasedNetListener : INetEventListener
+    {
+        public delegate void OnPeerConnected(NetPeer peer);
+        public delegate void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo);
+        public delegate void OnNetworkError(NetEndPoint endPoint, int socketErrorCode);
+        public delegate void OnNetworkReceive(NetPeer peer, NetDataReader reader);
+        public delegate void OnNetworkReceiveUnconnected(NetEndPoint remoteEndPoint, NetDataReader reader, UnconnectedMessageType messageType);
+        public delegate void OnNetworkLatencyUpdate(NetPeer peer, int latency);
+
+        public event OnPeerConnected PeerConnectedEvent;
+        public event OnPeerDisconnected PeerDisconnectedEvent;
+        public event OnNetworkError NetworkErrorEvent;
+        public event OnNetworkReceive NetworkReceiveEvent;
+        public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent;
+        public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent; 
+         
+        void INetEventListener.OnPeerConnected(NetPeer peer)
+        {
+            if (PeerConnectedEvent != null)
+                PeerConnectedEvent(peer);
+        }
+
+        void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
+        {
+            if (PeerDisconnectedEvent != null)
+                PeerDisconnectedEvent(peer, disconnectInfo);
+        }
+
+        void INetEventListener.OnNetworkError(NetEndPoint endPoint, int socketErrorCode)
+        {
+            if (NetworkErrorEvent != null)
+                NetworkErrorEvent(endPoint, socketErrorCode);
+        }
+
+        void INetEventListener.OnNetworkReceive(NetPeer peer, NetDataReader reader)
+        {
+            if (NetworkReceiveEvent != null)
+                NetworkReceiveEvent(peer, reader);
+        }
+
+        void INetEventListener.OnNetworkReceiveUnconnected(NetEndPoint remoteEndPoint, NetDataReader reader, UnconnectedMessageType messageType)
+        {
+            if (NetworkReceiveUnconnectedEvent != null)
+                NetworkReceiveUnconnectedEvent(remoteEndPoint, reader, messageType);
+        }
+
+        void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
+        {
+            if (NetworkLatencyUpdateEvent != null)
+                NetworkLatencyUpdateEvent(peer, latency);
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/INetEventListener.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: d391f9565d58e44a798d680ec5c11906
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 231 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NatPunchModule.cs

@@ -0,0 +1,231 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Collections.Generic;
+using FlyingWormConsole3.LiteNetLib.Utils;
+
+//Some code parts taked from lidgren-network-gen3
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public interface INatPunchListener
+    {
+        void OnNatIntroductionRequest(NetEndPoint localEndPoint, NetEndPoint remoteEndPoint, string token);
+        void OnNatIntroductionSuccess(NetEndPoint targetEndPoint, string token);
+    }
+
+    public class EventBasedNatPunchListener : INatPunchListener
+    {
+        public delegate void OnNatIntroductionRequest(NetEndPoint localEndPoint, NetEndPoint remoteEndPoint, string token);
+        public delegate void OnNatIntroductionSuccess(NetEndPoint targetEndPoint, string token);
+
+        public event OnNatIntroductionRequest NatIntroductionRequest;
+        public event OnNatIntroductionSuccess NatIntroductionSuccess;
+
+        void INatPunchListener.OnNatIntroductionRequest(NetEndPoint localEndPoint, NetEndPoint remoteEndPoint, string token)
+        {
+            if(NatIntroductionRequest != null)
+                NatIntroductionRequest(localEndPoint, remoteEndPoint, token);
+        }
+
+        void INatPunchListener.OnNatIntroductionSuccess(NetEndPoint targetEndPoint, string token)
+        {
+            if (NatIntroductionSuccess != null)
+                NatIntroductionSuccess(targetEndPoint, token);
+        }
+    }
+
+    public sealed class NatPunchModule
+    {
+        struct RequestEventData
+        {
+            public NetEndPoint LocalEndPoint;
+            public NetEndPoint RemoteEndPoint;
+            public string Token;
+        }
+
+        struct SuccessEventData
+        {
+            public NetEndPoint TargetEndPoint;
+            public string Token;
+        }
+
+        private readonly NetManager _netBase;
+        private readonly Queue<RequestEventData> _requestEvents;
+        private readonly Queue<SuccessEventData> _successEvents; 
+        private const byte HostByte = 1;
+        private const byte ClientByte = 0;
+        public const int MaxTokenLength = 256;
+
+        private INatPunchListener _natPunchListener;
+
+        internal NatPunchModule(NetManager netBase)
+        {
+            _netBase = netBase;
+            _requestEvents = new Queue<RequestEventData>();
+            _successEvents = new Queue<SuccessEventData>();
+        }
+
+        public void Init(INatPunchListener listener)
+        {
+            _natPunchListener = listener;
+        }
+
+        public void NatIntroduce(
+            NetEndPoint hostInternal,
+            NetEndPoint hostExternal,
+            NetEndPoint clientInternal,
+            NetEndPoint clientExternal,
+            string additionalInfo)
+        {
+            NetDataWriter dw = new NetDataWriter();
+
+            //First packet (server)
+            //send to client
+            dw.Put(ClientByte);
+            dw.Put(hostInternal);
+            dw.Put(hostExternal);
+            dw.Put(additionalInfo, MaxTokenLength);
+
+            var packet = _netBase.PacketPool.GetWithData(PacketProperty.NatIntroduction, dw);
+            _netBase.SendRawAndRecycle(packet, clientExternal);
+
+            //Second packet (client)
+            //send to server
+            dw.Reset();
+            dw.Put(HostByte);
+            dw.Put(clientInternal);
+            dw.Put(clientExternal);
+            dw.Put(additionalInfo, MaxTokenLength);
+
+            packet = _netBase.PacketPool.GetWithData(PacketProperty.NatIntroduction, dw);
+            _netBase.SendRawAndRecycle(packet, hostExternal);
+        }
+
+        public void PollEvents()
+        {
+            if (_natPunchListener == null)
+                return;
+            lock (_successEvents)
+            {
+                while (_successEvents.Count > 0)
+                {
+                    var evt = _successEvents.Dequeue();
+                    _natPunchListener.OnNatIntroductionSuccess(evt.TargetEndPoint, evt.Token);
+                }
+            }
+            lock (_requestEvents)
+            {
+                while (_requestEvents.Count > 0)
+                {
+                    var evt = _requestEvents.Dequeue();
+                    _natPunchListener.OnNatIntroductionRequest(evt.LocalEndPoint, evt.RemoteEndPoint, evt.Token);
+                }
+            }
+        }
+
+        public void SendNatIntroduceRequest(NetEndPoint masterServerEndPoint, string additionalInfo)
+        {
+            if (!_netBase.IsRunning)
+                return;
+
+            //prepare outgoing data
+            NetDataWriter dw = new NetDataWriter();
+            string networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv4);
+            if (string.IsNullOrEmpty(networkIp))
+            {
+                networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv6);
+            }
+            int networkPort = _netBase.LocalEndPoint.Port;
+            NetEndPoint localEndPoint = new NetEndPoint(networkIp, networkPort);
+            dw.Put(localEndPoint);
+            dw.Put(additionalInfo, MaxTokenLength);
+
+            //prepare packet
+            var packet = _netBase.PacketPool.GetWithData(PacketProperty.NatIntroductionRequest, dw);
+            _netBase.SendRawAndRecycle(packet, masterServerEndPoint);
+        }
+
+        private void HandleNatPunch(NetEndPoint senderEndPoint, NetDataReader dr)
+        {
+            byte fromHostByte = dr.GetByte();
+            if (fromHostByte != HostByte && fromHostByte != ClientByte)
+            {
+                //garbage
+                return;
+            }
+
+            //Read info
+            string additionalInfo = dr.GetString(MaxTokenLength);
+            NetUtils.DebugWrite(ConsoleColor.Green, "[NAT] punch received from {0} - additional info: {1}", senderEndPoint, additionalInfo);
+
+            //Release punch success to client; enabling him to Connect() to msg.Sender if token is ok
+            lock (_successEvents)
+            {
+                _successEvents.Enqueue(new SuccessEventData { TargetEndPoint = senderEndPoint, Token = additionalInfo });
+            }
+        }
+
+        private void HandleNatIntroduction(NetDataReader dr)
+        {
+            // read intro
+            byte hostByte = dr.GetByte();
+            NetEndPoint remoteInternal = dr.GetNetEndPoint();
+            NetEndPoint remoteExternal = dr.GetNetEndPoint();
+            string token = dr.GetString(MaxTokenLength);
+
+            NetUtils.DebugWrite(ConsoleColor.Cyan, "[NAT] introduction received; we are designated " + (hostByte == HostByte ? "host" : "client"));
+            NetDataWriter writer = new NetDataWriter();
+
+            // send internal punch
+            writer.Put(hostByte);
+            writer.Put(token);
+            var packet = _netBase.PacketPool.GetWithData(PacketProperty.NatPunchMessage, writer);
+            _netBase.SendRawAndRecycle(packet, remoteInternal);
+            NetUtils.DebugWrite(ConsoleColor.Cyan, "[NAT] internal punch sent to " + remoteInternal);
+
+            // send external punch
+            writer.Reset();
+            writer.Put(hostByte);
+            writer.Put(token);
+            packet = _netBase.PacketPool.GetWithData(PacketProperty.NatPunchMessage, writer);
+            _netBase.SendRawAndRecycle(packet, remoteExternal);
+            NetUtils.DebugWrite(ConsoleColor.Cyan, "[NAT] external punch sent to " + remoteExternal);
+        }
+
+        private void HandleNatIntroductionRequest(NetEndPoint senderEndPoint, NetDataReader dr)
+        {
+            NetEndPoint localEp = dr.GetNetEndPoint();
+            string token = dr.GetString(MaxTokenLength);
+            lock (_requestEvents)
+            {
+                _requestEvents.Enqueue(new RequestEventData
+                {
+                    LocalEndPoint = localEp,
+                    RemoteEndPoint = senderEndPoint,
+                    Token = token
+                });
+            }
+        }
+
+        internal void ProcessMessage(NetEndPoint senderEndPoint, NetPacket packet)
+        {
+            var dr = new NetDataReader(packet.RawData, NetConstants.HeaderSize, packet.Size);
+            switch (packet.Property)
+            {
+                case PacketProperty.NatIntroductionRequest:
+                    //We got request and must introduce
+                    HandleNatIntroductionRequest(senderEndPoint, dr);
+                    break;
+                case PacketProperty.NatIntroduction:
+                    //We got introduce and must punch
+                    HandleNatIntroduction(dr);
+                    break;
+                case PacketProperty.NatPunchMessage:
+                    //We got punch and can connect
+                    HandleNatPunch(senderEndPoint, dr);
+                    break;
+            }
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NatPunchModule.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 11f3508667cc14e3797a49d4695ffdd8
+timeCreated: 1497976517
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 53 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetConstants.cs

@@ -0,0 +1,53 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public enum SendOptions
+    {
+        Unreliable,
+        ReliableUnordered,
+        Sequenced,
+        ReliableOrdered
+    }
+
+    public static class NetConstants
+    {
+        public const int HeaderSize = 1;
+        public const int SequencedHeaderSize = 3;
+        public const int FragmentHeaderSize = 6;
+        public const int DefaultWindowSize = 64;
+        public const ushort MaxSequence = 32768;
+        public const ushort HalfMaxSequence = MaxSequence / 2;
+
+        //socket
+        public const string MulticastGroupIPv4 = "224.0.0.1";
+        public const string MulticastGroupIPv6 = "FF02:0:0:0:0:0:0:1";
+        public const int SocketBufferSize = 1024*1024; //2mb
+        public const int SocketTTL = 255;
+
+        //protocol
+        public const int ProtocolId = 1;
+        public const int MaxUdpHeaderSize = 68;
+        public const int PacketSizeLimit = ushort.MaxValue - MaxUdpHeaderSize;
+        public const int MinPacketSize = 576 - MaxUdpHeaderSize;
+        public const int MinPacketDataSize = MinPacketSize - HeaderSize;
+        public const int MinSequencedPacketDataSize = MinPacketSize - SequencedHeaderSize;
+
+        public static readonly int[] PossibleMtu =
+        {
+            576 - MaxUdpHeaderSize,  //Internet Path MTU for X.25 (RFC 879)
+            1492 - MaxUdpHeaderSize, //Ethernet with LLC and SNAP, PPPoE (RFC 1042)
+            1500 - MaxUdpHeaderSize, //Ethernet II (RFC 1191)
+            4352 - MaxUdpHeaderSize, //FDDI
+            4464 - MaxUdpHeaderSize, //Token ring
+            7981 - MaxUdpHeaderSize  //WLAN
+        };
+
+        public static int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1];
+
+        //peer specific
+        public const int FlowUpdateTime = 1000;
+        public const int FlowIncreaseThreshold = 4;
+        public const int DefaultPingInterval = 1000;
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetConstants.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 02abb4740bff94f28bdd538839339932
+timeCreated: 1497976517
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 444 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDataReader.cs

@@ -0,0 +1,444 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Text;
+
+namespace FlyingWormConsole3.LiteNetLib.Utils
+{
+    public class NetDataReader
+    {
+        protected byte[] _data;
+        protected int _position;
+        protected int _dataSize;
+
+        public byte[] Data
+        {
+            get { return _data; }
+        }
+
+        public int Position
+        {
+            get { return _position; }
+        }
+
+        public bool EndOfData
+        {
+            get { return _position == _dataSize; }
+        }
+
+        public int AvailableBytes
+        {
+            get { return _dataSize - _position; }
+        }
+
+        public void SetSource(NetDataWriter dataWriter)
+        {
+            _data = dataWriter.Data;
+            _position = 0;
+            _dataSize = dataWriter.Length;
+        }
+
+        public void SetSource(byte[] source)
+        {
+            _data = source;
+            _position = 0;
+            _dataSize = source.Length;
+        }
+
+        public void SetSource(byte[] source, int offset)
+        {
+            _data = source;
+            _position = offset;
+            _dataSize = source.Length;
+        }
+
+        public void SetSource(byte[] source, int offset, int dataSize)
+        {
+            _data = source;
+            _position = offset;
+            _dataSize = dataSize;
+        }
+
+        public NetDataReader()
+        {
+
+        }
+
+        public NetDataReader(byte[] source)
+        {
+            SetSource(source);
+        }
+
+        public NetDataReader(byte[] source, int offset)
+        {
+            SetSource(source, offset);
+        }
+
+        public NetDataReader(byte[] source, int offset, int maxSize)
+        {
+            SetSource(source, offset, maxSize);
+        }
+
+        #region GetMethods
+        public NetEndPoint GetNetEndPoint()
+        {
+            string host = GetString(1000);
+            int port = GetInt();
+            return new NetEndPoint(host, port);
+        }
+
+        public byte GetByte()
+        {
+            byte res = _data[_position];
+            _position += 1;
+            return res;
+        }
+
+        public sbyte GetSByte()
+        {
+            var b = (sbyte)_data[_position];
+            _position++;
+            return b;
+        }
+
+        public bool[] GetBoolArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new bool[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetBool();
+            }
+            return arr;
+        }
+
+        public ushort[] GetUShortArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new ushort[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetUShort();
+            }
+            return arr;
+        }
+
+        public short[] GetShortArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new short[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetShort();
+            }
+            return arr;
+        }
+
+        public long[] GetLongArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new long[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetLong();
+            }
+            return arr;
+        }
+
+        public ulong[] GetULongArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new ulong[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetULong();
+            }
+            return arr;
+        }
+
+        public int[] GetIntArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new int[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetInt();
+            }
+            return arr;
+        }
+
+        public uint[] GetUIntArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new uint[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetUInt();
+            }
+            return arr;
+        }
+
+        public float[] GetFloatArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new float[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetFloat();
+            }
+            return arr;
+        }
+
+        public double[] GetDoubleArray()
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new double[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetDouble();
+            }
+            return arr;
+        }
+
+        public string[] GetStringArray(int maxLength)
+        {
+            ushort size = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            var arr = new string[size];
+            for (int i = 0; i < size; i++)
+            {
+                arr[i] = GetString(maxLength);
+            }
+            return arr;
+        }
+
+        public bool GetBool()
+        {
+            bool res = _data[_position] > 0;
+            _position += 1;
+            return res;
+        }
+
+        public ushort GetUShort()
+        {
+            ushort result = BitConverter.ToUInt16(_data, _position);
+            _position += 2;
+            return result;
+        }
+
+        public short GetShort()
+        {
+            short result = BitConverter.ToInt16(_data, _position);
+            _position += 2;
+            return result;
+        }
+
+        public long GetLong()
+        {
+            long result = BitConverter.ToInt64(_data, _position);
+            _position += 8;
+            return result;
+        }
+
+        public ulong GetULong()
+        {
+            ulong result = BitConverter.ToUInt64(_data, _position);
+            _position += 8;
+            return result;
+        }
+
+        public int GetInt()
+        {
+            int result = BitConverter.ToInt32(_data, _position);
+            _position += 4;
+            return result;
+        }
+
+        public uint GetUInt()
+        {
+            uint result = BitConverter.ToUInt32(_data, _position);
+            _position += 4;
+            return result;
+        }
+
+        public float GetFloat()
+        {
+            float result = BitConverter.ToSingle(_data, _position);
+            _position += 4;
+            return result;
+        }
+
+        public double GetDouble()
+        {
+            double result = BitConverter.ToDouble(_data, _position);
+            _position += 8;
+            return result;
+        }
+
+        public string GetString(int maxLength)
+        {
+            int bytesCount = GetInt();
+            if (bytesCount <= 0 || bytesCount > maxLength*2)
+            {
+                return string.Empty;
+            }
+
+            int charCount = Encoding.UTF8.GetCharCount(_data, _position, bytesCount);
+            if (charCount > maxLength)
+            {
+                return string.Empty;
+            }
+
+            string result = Encoding.UTF8.GetString(_data, _position, bytesCount);
+            _position += bytesCount;
+            return result;
+        }
+
+        public string GetString()
+        {
+            int bytesCount = GetInt();
+            if (bytesCount <= 0)
+            {
+                return string.Empty;
+            }
+
+            string result = Encoding.UTF8.GetString(_data, _position, bytesCount);
+            _position += bytesCount;
+            return result;
+        }
+
+        public byte[] GetRemainingBytes()
+        {
+            byte[] outgoingData = new byte[AvailableBytes];
+            Buffer.BlockCopy(_data, _position, outgoingData, 0, AvailableBytes);
+            _position = _data.Length;
+            return outgoingData;
+        }
+
+        public void GetRemainingBytes(byte[] destination)
+        {
+            Buffer.BlockCopy(_data, _position, destination, 0, AvailableBytes);
+            _position = _data.Length;
+        }
+
+        public void GetBytes(byte[] destination, int lenght)
+        {
+            Buffer.BlockCopy(_data, _position, destination, 0, lenght);
+            _position += lenght;
+        }
+
+        public byte[] GetBytesWithLength()
+        {
+            int length = GetInt();
+            byte[] outgoingData = new byte[length];
+            Buffer.BlockCopy(_data, _position, outgoingData, 0, length);
+            _position += length;
+            return outgoingData;
+        }
+        #endregion
+
+        #region PeekMethods
+
+        public byte PeekByte()
+        {
+            return _data[_position];
+        }
+
+        public sbyte PeekSByte()
+        {
+            return (sbyte)_data[_position];
+        }
+
+        public bool PeekBool()
+        {
+            return _data[_position] > 0;
+        }
+
+        public ushort PeekUShort()
+        {
+            return BitConverter.ToUInt16(_data, _position);
+        }
+
+        public short PeekShort()
+        {
+            return BitConverter.ToInt16(_data, _position);
+        }
+
+        public long PeekLong()
+        {
+            return BitConverter.ToInt64(_data, _position);
+        }
+
+        public ulong PeekULong()
+        {
+            return BitConverter.ToUInt64(_data, _position);
+        }
+
+        public int PeekInt()
+        {
+            return BitConverter.ToInt32(_data, _position);
+        }
+
+        public uint PeekUInt()
+        {
+            return BitConverter.ToUInt32(_data, _position);
+        }
+
+        public float PeekFloat()
+        {
+            return BitConverter.ToSingle(_data, _position);
+        }
+
+        public double PeekDouble()
+        {
+            return BitConverter.ToDouble(_data, _position);
+        }
+
+        public string PeekString(int maxLength)
+        {
+            int bytesCount = BitConverter.ToInt32(_data, _position);
+            if (bytesCount <= 0 || bytesCount > maxLength * 2)
+            {
+                return string.Empty;
+            }
+
+            int charCount = Encoding.UTF8.GetCharCount(_data, _position + 4, bytesCount);
+            if (charCount > maxLength)
+            {
+                return string.Empty;
+            }
+
+            string result = Encoding.UTF8.GetString(_data, _position + 4, bytesCount);
+            return result;
+        }
+
+        public string PeekString()
+        {
+            int bytesCount = BitConverter.ToInt32(_data, _position);
+            if (bytesCount <= 0)
+            {
+                return string.Empty;
+            }
+
+            string result = Encoding.UTF8.GetString(_data, _position + 4, bytesCount);
+            return result;
+        }
+        #endregion
+
+        public void Clear()
+        {
+            _position = 0;
+            _dataSize = 0;
+            _data = null;
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDataReader.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 37bb0204fc22b499690c4032caf14811
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 375 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDataWriter.cs

@@ -0,0 +1,375 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Text;
+
+namespace FlyingWormConsole3.LiteNetLib.Utils
+{
+    public class NetDataWriter
+    {
+        protected byte[] _data;
+        protected int _position;
+
+        private int _maxLength;
+        private readonly bool _autoResize;
+
+        public NetDataWriter()
+        {
+            _maxLength = 64;
+            _data = new byte[_maxLength];
+            _autoResize = true;
+        }
+
+        public NetDataWriter(bool autoResize)
+        {
+            _maxLength = 64;
+            _data = new byte[_maxLength];
+            _autoResize = autoResize;
+        }
+
+        public NetDataWriter(bool autoResize, int initialSize)
+        {
+            _maxLength = initialSize;
+            _data = new byte[_maxLength];
+            _autoResize = autoResize;
+        }
+
+        public void ResizeIfNeed(int newSize)
+        {
+            if (_maxLength < newSize)
+            {
+                while (_maxLength < newSize)
+                {
+                    _maxLength *= 2;
+                }
+                Array.Resize(ref _data, _maxLength);
+            }
+        }
+
+        public void Reset(int size)
+        {
+            ResizeIfNeed(size);
+            _position = 0;
+        }
+
+        public void Reset()
+        {
+            _position = 0;
+        }
+
+        public byte[] CopyData()
+        {
+            byte[] resultData = new byte[_position];
+            Buffer.BlockCopy(_data, 0, resultData, 0, _position);
+            return resultData;
+        }
+
+        public byte[] Data
+        {
+            get { return _data; }
+        }
+
+        public int Length
+        {
+            get { return _position; }
+        }
+
+        public void Put(float value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 4);
+            FastBitConverter.GetBytes(_data, _position, value);
+            _position += 4;
+        }
+
+        public void Put(double value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 8);
+            FastBitConverter.GetBytes(_data, _position, value);
+            _position += 8;
+        }
+
+        public void Put(long value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 8);
+            FastBitConverter.GetBytes(_data, _position, value);
+            _position += 8;
+        }
+
+        public void Put(ulong value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 8);
+            FastBitConverter.GetBytes(_data, _position, value);
+            _position += 8;
+        }
+
+        public void Put(int value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 4);
+            FastBitConverter.GetBytes(_data, _position, value);
+            _position += 4;
+        }
+
+        public void Put(uint value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 4);
+            FastBitConverter.GetBytes(_data, _position, value);
+            _position += 4;
+        }
+
+        public void Put(ushort value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 2);
+            FastBitConverter.GetBytes(_data, _position, value);
+            _position += 2;
+        }
+
+        public void Put(short value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 2);
+            FastBitConverter.GetBytes(_data, _position, value);
+            _position += 2;
+        }
+
+        public void Put(sbyte value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 1);
+            _data[_position] = (byte)value;
+            _position++;
+        }
+
+        public void Put(byte value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 1);
+            _data[_position] = value;
+            _position++;
+        }
+
+        public void Put(byte[] data, int offset, int length)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + length);
+            Buffer.BlockCopy(data, offset, _data, _position, length);
+            _position += length;
+        }
+
+        public void Put(byte[] data)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + data.Length);
+            Buffer.BlockCopy(data, 0, _data, _position, data.Length);
+            _position += data.Length;
+        }
+
+        public void PutBytesWithLength(byte[] data, int offset, int length)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + length);
+            Put(length);
+            Buffer.BlockCopy(data, offset, _data, _position, length);
+            _position += length;
+        }
+
+        public void PutBytesWithLength(byte[] data)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + data.Length);
+            Put(data.Length);
+            Buffer.BlockCopy(data, 0, _data, _position, data.Length);
+            _position += data.Length;
+        }
+
+        public void Put(bool value)
+        {
+            if (_autoResize)
+                ResizeIfNeed(_position + 1);
+            _data[_position] = (byte)(value ? 1 : 0);
+            _position++;
+        }
+
+        public void PutArray(float[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len * 4 + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(double[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len * 8 + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(long[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len * 8 + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(ulong[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len * 8 + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(int[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len * 4 + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(uint[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len * 4 + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(ushort[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len * 2 + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(short[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len * 2 + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(bool[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            if (_autoResize)
+                ResizeIfNeed(_position + len + 2);
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(string[] value)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            Put(len);
+            for (int i = 0; i < value.Length; i++)
+            {
+                Put(value[i]);
+            }
+        }
+
+        public void PutArray(string[] value, int maxLength)
+        {
+            ushort len = value == null ? (ushort)0 : (ushort)value.Length;
+            Put(len);
+            for (int i = 0; i < len; i++)
+            {
+                Put(value[i], maxLength);
+            }
+        }
+
+        public void Put(NetEndPoint endPoint)
+        {
+            Put(endPoint.Host);
+            Put(endPoint.Port);
+        }
+
+        public void Put(string value)
+        {
+            if (string.IsNullOrEmpty(value))
+            {
+                Put(0);
+                return;
+            }
+
+            //put bytes count
+            int bytesCount = Encoding.UTF8.GetByteCount(value);
+            if (_autoResize)
+                ResizeIfNeed(_position + bytesCount + 4);
+            Put(bytesCount);
+
+            //put string
+            Encoding.UTF8.GetBytes(value, 0, value.Length, _data, _position);
+            _position += bytesCount;
+        }
+
+        public void Put(string value, int maxLength)
+        {
+            if (string.IsNullOrEmpty(value))
+            {
+                Put(0);
+                return;
+            }
+
+            int length = value.Length > maxLength ? maxLength : value.Length;
+            //calculate max count
+            int bytesCount = Encoding.UTF8.GetByteCount(value);
+            if (_autoResize)
+                ResizeIfNeed(_position + bytesCount + 4);
+
+            //put bytes count
+            Put(bytesCount);
+
+            //put string
+            Encoding.UTF8.GetBytes(value, 0, length, _data, _position);
+
+            _position += bytesCount;
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDataWriter.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 81ecf42c9cc394fc395942030e71bddd
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 16 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDebug.cs

@@ -0,0 +1,16 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public interface INetLogger
+    {
+        void WriteNet(ConsoleColor color, string str, params object[] args);
+    }
+
+    public static class NetDebug
+    {
+        public static INetLogger Logger = null;
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetDebug.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 78dc00ceb66ac4fdfa8c3957763522ba
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 221 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetEndPoint.cs

@@ -0,0 +1,221 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+#if !WINRT || UNITY_EDITOR
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public sealed class NetEndPoint
+    {
+        public string Host { get { return EndPoint.Address.ToString(); } }
+        public int Port { get { return EndPoint.Port; } }
+
+        internal readonly IPEndPoint EndPoint;
+
+        internal NetEndPoint(IPEndPoint ipEndPoint)
+        {
+            EndPoint = ipEndPoint;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is NetEndPoint))
+            {
+                return false;
+            }
+            return EndPoint.Equals(((NetEndPoint)obj).EndPoint);
+        }
+
+        public override string ToString()
+        {
+            return EndPoint.ToString();
+        }
+
+        public override int GetHashCode()
+        {
+            return EndPoint.GetHashCode();
+        }
+
+        public NetEndPoint(string hostStr, int port)
+        {
+            IPAddress ipAddress;
+            if (!IPAddress.TryParse(hostStr, out ipAddress))
+            {
+                if (Socket.OSSupportsIPv6)
+                {
+                    if (hostStr == "localhost")
+                    {
+                        ipAddress = IPAddress.IPv6Loopback;
+                    }
+                    else
+                    {
+                        ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetworkV6);
+                    }
+                }
+                if (ipAddress == null)
+                {
+                    ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetwork);
+                }
+            }
+            if (ipAddress == null)
+            {
+                throw new Exception("Invalid address: " + hostStr);
+            }
+            EndPoint = new IPEndPoint(ipAddress, port);
+        }
+
+        private IPAddress ResolveAddress(string hostStr, AddressFamily addressFamily)
+        {
+#if NETCORE
+            var hostTask = Dns.GetHostEntryAsync(hostStr);
+            hostTask.Wait();
+            var host = hostTask.Result;
+#else
+            var host = Dns.GetHostEntry(hostStr);
+#endif
+            foreach (IPAddress ip in host.AddressList)
+            {
+                if (ip.AddressFamily == addressFamily)
+                {
+                    return ip;
+                }
+            }
+            return null;
+        }
+
+        internal long GetId()
+        {
+            byte[] addr = EndPoint.Address.GetAddressBytes();
+            long id = 0;
+
+            if (addr.Length == 4) //IPv4
+            {
+                id = addr[0];
+                id |= (long)addr[1] << 8;
+                id |= (long)addr[2] << 16;
+                id |= (long)addr[3] << 24;
+                id |= (long)EndPoint.Port << 32;
+            }
+            else if (addr.Length == 16) //IPv6
+            {
+                id = addr[0] ^ addr[8];
+                id |= (long)(addr[1] ^ addr[9]) << 8;
+                id |= (long)(addr[2] ^ addr[10]) << 16;
+
+
+                id |= (long)(addr[3] ^ addr[11]) << 24;
+                id |= (long)(addr[4] ^ addr[12]) << 32;
+                id |= (long)(addr[5] ^ addr[13]) << 40;
+                id |= (long)(addr[6] ^ addr[14]) << 48;
+                id |= (long)(Port ^ addr[7] ^ addr[15]) << 56;
+            }
+
+            return id;
+        }
+    }
+}
+#else
+using System;
+using Windows.Networking;
+using Windows.Networking.Sockets;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public sealed class NetEndPoint
+    {
+        public string Host { get { return HostName.DisplayName; } }
+        public int Port { get; private set; }
+        internal readonly HostName HostName;
+        internal readonly string PortStr;
+
+        internal NetEndPoint(int port)
+        {
+            HostName = null;
+            PortStr = port.ToString();
+            Port = port;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is NetEndPoint))
+            {
+                return false;
+            }
+            NetEndPoint other = (NetEndPoint) obj;
+            return HostName.IsEqual(other.HostName) && PortStr.Equals(other.PortStr);
+        }
+
+        public override int GetHashCode()
+        {
+            return HostName.CanonicalName.GetHashCode() ^ PortStr.GetHashCode();
+        }
+
+        internal long GetId()
+        {
+            //Check locals
+            if (HostName == null)
+            {
+                return ParseIpToId("0.0.0.0");
+            }
+
+            if (HostName.DisplayName == "localhost")
+            {
+                return ParseIpToId("127.0.0.1");
+            }
+
+            //Check remote
+            string hostIp = string.Empty;
+            var task = DatagramSocket.GetEndpointPairsAsync(HostName, "0").AsTask();
+            task.Wait();
+
+            //IPv4
+            foreach (var endpointPair in task.Result)
+            {
+                hostIp = endpointPair.RemoteHostName.CanonicalName;
+                if (endpointPair.RemoteHostName.Type == HostNameType.Ipv4)
+                {
+                    return ParseIpToId(hostIp);
+                }
+            }
+
+            //Else
+            return hostIp.GetHashCode() ^ Port;
+        }
+
+        private long ParseIpToId(string hostIp)
+        {
+            long id = 0;
+            string[] ip = hostIp.Split('.');
+            id |= long.Parse(ip[0]);
+            id |= long.Parse(ip[1]) << 8;
+            id |= long.Parse(ip[2]) << 16;
+            id |= long.Parse(ip[3]) << 24;
+            id |= (long)Port << 32;
+            return id;
+        }
+
+        public override string ToString()
+        {
+            return HostName.CanonicalName + ":" + PortStr;
+        }
+
+        public NetEndPoint(string hostName, int port)
+        {
+            var task = DatagramSocket.GetEndpointPairsAsync(new HostName(hostName), port.ToString()).AsTask();
+            task.Wait();
+            HostName = task.Result[0].RemoteHostName;
+            Port = port;
+            PortStr = port.ToString();
+        }
+
+        internal NetEndPoint(HostName hostName, string port)
+        {
+            HostName = hostName;
+            Port = int.Parse(port);
+            PortStr = port;
+        }
+    }
+}
+#endif
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetEndPoint.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 33fb66c3ba5b8429fbbb0a2f5e7ceb57
+timeCreated: 1497976517
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 1039 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetManager.cs

@@ -0,0 +1,1039 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+#if DEBUG
+#define STATS_ENABLED
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using FlyingWormConsole3.LiteNetLib.Utils;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public sealed class NetManager
+    {
+        internal delegate void OnMessageReceived(byte[] data, int length, int errorCode, NetEndPoint remoteEndPoint);
+
+        private struct FlowMode
+        {
+            public int PacketsPerSecond;
+            public int StartRtt;
+        }
+
+        private enum NetEventType
+        {
+            Connect,
+            Disconnect,
+            Receive,
+            ReceiveUnconnected,
+            Error,
+            ConnectionLatencyUpdated,
+            DiscoveryRequest,
+            DiscoveryResponse
+        }
+
+        private sealed class NetEvent
+        {
+            public NetPeer Peer;
+            public readonly NetDataReader DataReader = new NetDataReader();
+            public NetEventType Type;
+            public NetEndPoint RemoteEndPoint;
+            public int AdditionalData;
+            public DisconnectReason DisconnectReason;
+        }
+
+#if DEBUG
+        private struct IncomingData
+        {
+            public byte[] Data;
+            public NetEndPoint EndPoint;
+            public DateTime TimeWhenGet;
+        }
+        private readonly List<IncomingData> _pingSimulationList = new List<IncomingData>(); 
+        private readonly Random _randomGenerator = new Random();
+        private const int MinLatencyTreshold = 5;
+#endif
+
+        private readonly NetSocket _socket;
+        private readonly List<FlowMode> _flowModes;
+
+        private readonly NetThread _logicThread;
+
+        private readonly Queue<NetEvent> _netEventsQueue;
+        private readonly Stack<NetEvent> _netEventsPool;
+        private readonly INetEventListener _netEventListener;
+
+        private readonly NetPeerCollection _peers;
+        private readonly int _maxConnections;
+        private readonly string _connectKey;
+
+        private readonly NetPacketPool _netPacketPool;
+
+        //config section
+        public bool UnconnectedMessagesEnabled = false;
+        public bool NatPunchEnabled = false;
+        public int UpdateTime { get { return _logicThread.SleepTime; } set { _logicThread.SleepTime = value; } }
+        public int PingInterval = NetConstants.DefaultPingInterval;
+        public long DisconnectTimeout = 5000;
+        public bool SimulatePacketLoss = false;
+        public bool SimulateLatency = false;
+        public int SimulationPacketLossChance = 10;
+        public int SimulationMinLatency = 30;
+        public int SimulationMaxLatency = 100;
+        public bool UnsyncedEvents = false;
+        public bool DiscoveryEnabled = false;
+        public bool MergeEnabled = false;
+        public int ReconnectDelay = 500;
+        public int MaxConnectAttempts = 10;
+        public bool ReuseAddress = false;
+
+        private const int DefaultUpdateTime = 15;
+
+        //stats
+        public ulong PacketsSent { get; private set; }
+        public ulong PacketsReceived { get; private set; }
+        public ulong BytesSent { get; private set; }
+        public ulong BytesReceived { get; private set; }
+
+        //modules
+        public readonly NatPunchModule NatPunchModule;
+
+        /// <summary>
+        /// Returns true if socket listening and update thread is running
+        /// </summary>
+        public bool IsRunning
+        {
+            get { return _logicThread.IsRunning; }
+        }
+
+        /// <summary>
+        /// Local EndPoint (host and port)
+        /// </summary>
+        public NetEndPoint LocalEndPoint
+        {
+            get { return _socket.LocalEndPoint; }
+        }
+
+        /// <summary>
+        /// Connected peers count
+        /// </summary>
+        public int PeersCount
+        {
+            get { return _peers.Count; }
+        }
+
+        public string ConnectKey
+        {
+            get { return _connectKey; }
+        }
+
+        //Flow
+        public void AddFlowMode(int startRtt, int packetsPerSecond)
+        {
+            var fm = new FlowMode {PacketsPerSecond = packetsPerSecond, StartRtt = startRtt};
+
+            if (_flowModes.Count > 0 && startRtt < _flowModes[0].StartRtt)
+            {
+                _flowModes.Insert(0, fm);
+            }
+            else
+            {
+                _flowModes.Add(fm);
+            }
+        }
+
+        internal int GetPacketsPerSecond(int flowMode)
+        {
+            if (flowMode < 0 || _flowModes.Count == 0)
+                return 0;
+            return _flowModes[flowMode].PacketsPerSecond;
+        }
+
+        internal int GetMaxFlowMode()
+        {
+            return _flowModes.Count - 1;
+        }
+
+        internal int GetStartRtt(int flowMode)
+        {
+            if (flowMode < 0 || _flowModes.Count == 0)
+                return 0;
+            return _flowModes[flowMode].StartRtt;
+        }
+
+        internal NetPacketPool PacketPool
+        {
+            get { return _netPacketPool; }
+        }
+
+        /// <summary>
+        /// NetManager constructor with maxConnections = 1 (usable for client)
+        /// </summary>
+        /// <param name="listener">Network events listener</param>
+        /// <param name="connectKey">Application key (must be same with remote host for establish connection)</param>
+        public NetManager(INetEventListener listener, string connectKey) : this(listener, 1, connectKey)
+        {
+            
+        }
+
+        /// <summary>
+        /// NetManager constructor
+        /// </summary>
+        /// <param name="listener">Network events listener</param>
+        /// <param name="maxConnections">Maximum connections (incoming and outcoming)</param>
+        /// <param name="connectKey">Application key (must be same with remote host for establish connection)</param>
+        public NetManager(INetEventListener listener, int maxConnections, string connectKey)
+        {
+            _logicThread = new NetThread("LogicThread", DefaultUpdateTime, UpdateLogic);
+            _socket = new NetSocket(ReceiveLogic);
+            _netEventListener = listener;
+            _flowModes = new List<FlowMode>();
+            _netEventsQueue = new Queue<NetEvent>();
+            _netEventsPool = new Stack<NetEvent>();
+            _netPacketPool = new NetPacketPool();
+            NatPunchModule = new NatPunchModule(this);
+
+            _connectKey = connectKey;
+            _peers = new NetPeerCollection(maxConnections);
+            _maxConnections = maxConnections;
+            _connectKey = connectKey;
+        }
+
+        internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency)
+        {
+            var evt = CreateEvent(NetEventType.ConnectionLatencyUpdated);
+            evt.Peer = fromPeer;
+            evt.AdditionalData = latency;
+            EnqueueEvent(evt);
+        }
+
+        internal bool SendRawAndRecycle(NetPacket packet, NetEndPoint remoteEndPoint)
+        {
+            var result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
+            _netPacketPool.Recycle(packet);
+            return result;
+        }
+
+        internal bool SendRaw(byte[] message, int start, int length, NetEndPoint remoteEndPoint)
+        {
+            if (!IsRunning)
+                return false;
+
+            int errorCode = 0;
+            bool result = _socket.SendTo(message, start, length, remoteEndPoint, ref errorCode) > 0;
+
+            //10040 message to long... need to check
+            //10065 no route to host
+            if (errorCode != 0 && errorCode != 10040 && errorCode != 10065)
+            {
+                //Send error
+                NetPeer fromPeer;
+                if (_peers.TryGetValue(remoteEndPoint, out fromPeer))
+                {
+                    DisconnectPeer(fromPeer, DisconnectReason.SocketSendError, errorCode, false, null, 0, 0);
+                }
+                var netEvent = CreateEvent(NetEventType.Error);
+                netEvent.RemoteEndPoint = remoteEndPoint;
+                netEvent.AdditionalData = errorCode;
+                EnqueueEvent(netEvent);
+                return false;
+            }
+            if (errorCode == 10040)
+            {
+                NetUtils.DebugWrite(ConsoleColor.Red, "[SRD] 10040, datalen: {0}", length);
+                return false;
+            }
+#if STATS_ENABLED
+            PacketsSent++;
+            BytesSent += (uint)length;
+#endif
+
+            return result;
+        }
+
+        private void DisconnectPeer(
+            NetPeer peer, 
+            DisconnectReason reason, 
+            int socketErrorCode, 
+            bool sendDisconnectPacket,
+            byte[] data,
+            int start,
+            int count)
+        {
+            if (sendDisconnectPacket)
+            {
+                if (count + 8 >= peer.Mtu)
+                {
+                    //Drop additional data
+                    data = null;
+                    count = 0;
+                    NetUtils.DebugWriteError("[NM] Disconnect additional data size more than MTU - 8!");
+                }
+
+                var disconnectPacket = _netPacketPool.Get(PacketProperty.Disconnect, 8 + count);
+                FastBitConverter.GetBytes(disconnectPacket.RawData, 1, peer.ConnectId);
+                if (data != null)
+                {
+                    Buffer.BlockCopy(data, start, disconnectPacket.RawData, 9, count);
+                }
+                SendRawAndRecycle(disconnectPacket, peer.EndPoint);
+            }
+            var netEvent = CreateEvent(NetEventType.Disconnect);
+            netEvent.Peer = peer;
+            netEvent.AdditionalData = socketErrorCode;
+            netEvent.DisconnectReason = reason;
+            EnqueueEvent(netEvent);
+            RemovePeer(peer.EndPoint);
+        }
+
+        private void ClearPeers()
+        {
+            lock (_peers)
+            {
+#if WINRT && !UNITY_EDITOR
+                _socket.ClearPeers();
+#endif
+                _peers.Clear();
+            }
+        }
+
+        private void RemovePeer(NetEndPoint endPoint)
+        {
+            _peers.Remove(endPoint);
+#if WINRT && !UNITY_EDITOR
+            _socket.RemovePeer(endPoint);
+#endif
+        }
+
+        private void RemovePeerAt(int idx)
+        {
+#if WINRT && !UNITY_EDITOR
+            var endPoint = _peers[idx].EndPoint;
+            _socket.RemovePeer(endPoint);
+#endif
+            _peers.RemoveAt(idx);
+        }
+
+        private NetEvent CreateEvent(NetEventType type)
+        {
+            NetEvent evt = null;
+
+            lock (_netEventsPool)
+            {
+                if (_netEventsPool.Count > 0)
+                {
+                    evt = _netEventsPool.Pop();
+                }
+            }
+            if(evt == null)
+            {
+                evt = new NetEvent();
+            }
+            evt.Type = type;
+            return evt;
+        }
+
+        private void EnqueueEvent(NetEvent evt)
+        {
+            if (UnsyncedEvents)
+            {
+                ProcessEvent(evt);
+            }
+            else
+            {
+                lock (_netEventsQueue)
+                {
+                    _netEventsQueue.Enqueue(evt);
+                }
+            }
+        }
+
+        private void ProcessEvent(NetEvent evt)
+        {
+            switch (evt.Type)
+            {
+                case NetEventType.Connect:
+                    _netEventListener.OnPeerConnected(evt.Peer);
+                    break;
+                case NetEventType.Disconnect:
+                    var info = new DisconnectInfo
+                    {
+                        Reason = evt.DisconnectReason,
+                        AdditionalData = evt.DataReader,
+                        SocketErrorCode = evt.AdditionalData
+                    };
+                    _netEventListener.OnPeerDisconnected(evt.Peer, info);
+                    break;
+                case NetEventType.Receive:
+                    _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader);
+                    break;
+                case NetEventType.ReceiveUnconnected:
+                    _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Default);
+                    break;
+                case NetEventType.DiscoveryRequest:
+                    _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.DiscoveryRequest);
+                    break;
+                case NetEventType.DiscoveryResponse:
+                    _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.DiscoveryResponse);
+                    break;
+                case NetEventType.Error:
+                    _netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.AdditionalData);
+                    break;
+                case NetEventType.ConnectionLatencyUpdated:
+                    _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.AdditionalData);
+                    break;
+            }
+
+            //Recycle
+            evt.DataReader.Clear();
+            evt.Peer = null;
+            evt.AdditionalData = 0;
+            evt.RemoteEndPoint = null;
+
+            lock (_netEventsPool)
+            {
+                _netEventsPool.Push(evt);
+            }
+        }
+
+        //Update function
+        private void UpdateLogic()
+        {
+#if DEBUG
+            if (SimulateLatency)
+            {
+                var time = DateTime.UtcNow;
+                lock (_pingSimulationList)
+                {
+                    for (int i = 0; i < _pingSimulationList.Count; i++)
+                    {
+                        var incomingData = _pingSimulationList[i];
+                        if (incomingData.TimeWhenGet <= time)
+                        {
+                            DataReceived(incomingData.Data, incomingData.Data.Length, incomingData.EndPoint);
+                            _pingSimulationList.RemoveAt(i);
+                            i--;
+                        }
+                    }
+                }
+            }
+#endif
+
+            //Process acks
+            lock (_peers)
+            {
+                int delta = _logicThread.SleepTime;
+                for(int i = 0; i < _peers.Count; i++)
+                {
+                    var netPeer = _peers[i];
+                    if (netPeer.ConnectionState == ConnectionState.Connected && netPeer.TimeSinceLastPacket > DisconnectTimeout)
+                    {
+                        NetUtils.DebugWrite("[NM] Disconnect by timeout: {0} > {1}", netPeer.TimeSinceLastPacket, DisconnectTimeout);
+                        var netEvent = CreateEvent(NetEventType.Disconnect);
+                        netEvent.Peer = netPeer;
+                        netEvent.DisconnectReason = DisconnectReason.Timeout;
+                        EnqueueEvent(netEvent);
+
+                        RemovePeerAt(i);
+                        i--;
+                    }
+                    else if(netPeer.ConnectionState == ConnectionState.Disconnected)
+                    {
+                        var netEvent = CreateEvent(NetEventType.Disconnect);
+                        netEvent.Peer = netPeer;
+                        netEvent.DisconnectReason = DisconnectReason.ConnectionFailed;
+                        EnqueueEvent(netEvent);
+
+                        RemovePeerAt(i);
+                        i--;
+                    }
+                    else
+                    {
+                        netPeer.Update(delta);
+                    }
+                }
+            }
+        }
+        
+        private void ReceiveLogic(byte[] data, int length, int errorCode, NetEndPoint remoteEndPoint)
+        {
+            //Receive some info
+            if (errorCode == 0)
+            {
+#if DEBUG
+                bool receivePacket = true;
+
+                if (SimulatePacketLoss && _randomGenerator.Next(100/SimulationPacketLossChance) == 0)
+                {
+                    receivePacket = false;
+                }
+                else if (SimulateLatency)
+                {
+                    int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
+                    if (latency > MinLatencyTreshold)
+                    {
+                        byte[] holdedData = new byte[length];
+                        Buffer.BlockCopy(data, 0, holdedData, 0, length);
+
+                        lock (_pingSimulationList)
+                        {
+                            _pingSimulationList.Add(new IncomingData
+                            {
+                                Data = holdedData,
+                                EndPoint = remoteEndPoint,
+                                TimeWhenGet = DateTime.UtcNow.AddMilliseconds(latency)
+                            });
+                        }
+                    
+                        receivePacket = false;
+                    }
+                }
+
+                if (receivePacket) //DataReceived
+#endif
+                    //ProcessEvents
+                    DataReceived(data, length, remoteEndPoint);
+            }
+            else //Error on receive
+            {
+                ClearPeers();
+                var netEvent = CreateEvent(NetEventType.Error);
+                netEvent.AdditionalData = errorCode;
+                EnqueueEvent(netEvent);
+            }
+        }
+
+        private void DataReceived(byte[] reusableBuffer, int count, NetEndPoint remoteEndPoint)
+        {
+#if STATS_ENABLED
+            PacketsReceived++;
+            BytesReceived += (uint) count;
+#endif
+
+            //Try read packet
+            NetPacket packet = _netPacketPool.GetAndRead(reusableBuffer, 0, count);
+            if (packet == null)
+            {
+                NetUtils.DebugWriteError("[NM] DataReceived: bad!");
+                return;
+            }
+
+            //Check unconnected
+            switch (packet.Property)
+            {
+                case PacketProperty.DiscoveryRequest:
+                    if(DiscoveryEnabled)
+                    {
+                        var netEvent = CreateEvent(NetEventType.DiscoveryRequest);
+                        netEvent.RemoteEndPoint = remoteEndPoint;
+                        netEvent.DataReader.SetSource(packet.RawData, NetConstants.HeaderSize);
+                        EnqueueEvent(netEvent);
+                    }
+                    return;
+                case PacketProperty.DiscoveryResponse:
+                    {
+                        var netEvent = CreateEvent(NetEventType.DiscoveryResponse);
+                        netEvent.RemoteEndPoint = remoteEndPoint;
+                        netEvent.DataReader.SetSource(packet.RawData, NetConstants.HeaderSize);
+                        EnqueueEvent(netEvent);
+                    }
+                    return;
+                case PacketProperty.UnconnectedMessage:
+                    if (UnconnectedMessagesEnabled)
+                    {
+                        var netEvent = CreateEvent(NetEventType.ReceiveUnconnected);
+                        netEvent.RemoteEndPoint = remoteEndPoint;
+                        netEvent.DataReader.SetSource(packet.RawData, NetConstants.HeaderSize);
+                        EnqueueEvent(netEvent);
+                    }
+                    return;
+                case PacketProperty.NatIntroduction:
+                case PacketProperty.NatIntroductionRequest:
+                case PacketProperty.NatPunchMessage:
+                    {
+                        if (NatPunchEnabled)
+                            NatPunchModule.ProcessMessage(remoteEndPoint, packet);
+                        return;
+                    }
+            }
+
+            //Check normal packets
+            NetPeer netPeer;
+
+            //Check peers
+            Monitor.Enter(_peers);
+            int peersCount = _peers.Count;
+
+            if (_peers.TryGetValue(remoteEndPoint, out netPeer))
+            {
+                Monitor.Exit(_peers);
+                //Send
+                if (packet.Property == PacketProperty.Disconnect)
+                {
+                    if (BitConverter.ToInt64(packet.RawData, 1) != netPeer.ConnectId)
+                    {
+                        //Old or incorrect disconnect
+                        _netPacketPool.Recycle(packet);
+                        return;
+                    }
+
+                    var netEvent = CreateEvent(NetEventType.Disconnect);
+                    netEvent.Peer = netPeer;
+                    netEvent.DataReader.SetSource(packet.RawData, 5, packet.Size - 5);
+                    netEvent.DisconnectReason = DisconnectReason.RemoteConnectionClose;
+                    EnqueueEvent(netEvent);
+
+                    _peers.Remove(netPeer.EndPoint);
+                    //do not recycle because no sense)
+                }
+                else if (packet.Property == PacketProperty.ConnectAccept)
+                {
+                    if (netPeer.ProcessConnectAccept(packet))
+                    {
+                        var connectEvent = CreateEvent(NetEventType.Connect);
+                        connectEvent.Peer = netPeer;
+                        EnqueueEvent(connectEvent);
+                    }
+                    _netPacketPool.Recycle(packet);
+                }
+                else
+                {
+                    netPeer.ProcessPacket(packet);
+                }
+                return;
+            }
+
+            try
+            {
+                if (peersCount < _maxConnections && packet.Property == PacketProperty.ConnectRequest)
+                {
+                    int protoId = BitConverter.ToInt32(packet.RawData, 1);
+                    if (protoId != NetConstants.ProtocolId)
+                    {
+                        NetUtils.DebugWrite(ConsoleColor.Cyan,
+                            "[NM] Peer connect reject. Invalid protocol ID: " + protoId);
+                        return;
+                    }
+
+                    string peerKey = Encoding.UTF8.GetString(packet.RawData, 13, packet.Size - 13);
+                    if (peerKey != _connectKey)
+                    {
+                        NetUtils.DebugWrite(ConsoleColor.Cyan, "[NM] Peer connect reject. Invalid key: " + peerKey);
+                        return;
+                    }
+
+                    //Getting new id for peer
+                    long connectionId = BitConverter.ToInt64(packet.RawData, 5);
+                    //response with id
+                    netPeer = new NetPeer(this, remoteEndPoint, connectionId);
+                    NetUtils.DebugWrite(ConsoleColor.Cyan, "[NM] Received peer connect request Id: {0}, EP: {1}",
+                        netPeer.ConnectId, remoteEndPoint);
+
+                    //clean incoming packet
+                    _netPacketPool.Recycle(packet);
+
+                    _peers.Add(remoteEndPoint, netPeer);
+
+                    var netEvent = CreateEvent(NetEventType.Connect);
+                    netEvent.Peer = netPeer;
+                    EnqueueEvent(netEvent);
+                }
+            }
+            finally
+            {
+                Monitor.Exit(_peers);
+            }
+        }
+
+        internal void ReceiveFromPeer(NetPacket packet, NetEndPoint remoteEndPoint)
+        {
+            NetPeer fromPeer;
+            if (_peers.TryGetValue(remoteEndPoint, out fromPeer))
+            {
+                NetUtils.DebugWrite(ConsoleColor.Cyan, "[NM] Received message");
+                var netEvent = CreateEvent(NetEventType.Receive);
+                netEvent.Peer = fromPeer;
+                netEvent.RemoteEndPoint = fromPeer.EndPoint;
+                netEvent.DataReader.SetSource(packet.GetPacketData());
+                EnqueueEvent(netEvent);
+            }
+        }
+
+        /// <summary>
+        /// Send data to all connected peers
+        /// </summary>
+        /// <param name="writer">DataWriter with data</param>
+        /// <param name="options">Send options (reliable, unreliable, etc.)</param>
+        public void SendToAll(NetDataWriter writer, SendOptions options)
+        {
+            SendToAll(writer.Data, 0, writer.Length, options);
+        }
+
+        /// <summary>
+        /// Send data to all connected peers
+        /// </summary>
+        /// <param name="data">Data</param>
+        /// <param name="options">Send options (reliable, unreliable, etc.)</param>
+        public void SendToAll(byte[] data, SendOptions options)
+        {
+            SendToAll(data, 0, data.Length, options);
+        }
+
+        /// <summary>
+        /// Send data to all connected peers
+        /// </summary>
+        /// <param name="data">Data</param>
+        /// <param name="start">Start of data</param>
+        /// <param name="length">Length of data</param>
+        /// <param name="options">Send options (reliable, unreliable, etc.)</param>
+        public void SendToAll(byte[] data, int start, int length, SendOptions options)
+        {
+            lock (_peers)
+            {
+                for(int i = 0; i < _peers.Count; i++)
+                {
+                    _peers[i].Send(data, start, length, options);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Send data to all connected peers
+        /// </summary>
+        /// <param name="writer">DataWriter with data</param>
+        /// <param name="options">Send options (reliable, unreliable, etc.)</param>
+        /// <param name="excludePeer">Excluded peer</param>
+        public void SendToAll(NetDataWriter writer, SendOptions options, NetPeer excludePeer)
+        {
+            SendToAll(writer.Data, 0, writer.Length, options, excludePeer);
+        }
+
+        /// <summary>
+        /// Send data to all connected peers
+        /// </summary>
+        /// <param name="data">Data</param>
+        /// <param name="options">Send options (reliable, unreliable, etc.)</param>
+        /// <param name="excludePeer">Excluded peer</param>
+        public void SendToAll(byte[] data, SendOptions options, NetPeer excludePeer)
+        {
+            SendToAll(data, 0, data.Length, options, excludePeer);
+        }
+
+        /// <summary>
+        /// Send data to all connected peers
+        /// </summary>
+        /// <param name="data">Data</param>
+        /// <param name="start">Start of data</param>
+        /// <param name="length">Length of data</param>
+        /// <param name="options">Send options (reliable, unreliable, etc.)</param>
+        /// <param name="excludePeer">Excluded peer</param>
+        public void SendToAll(byte[] data, int start, int length, SendOptions options, NetPeer excludePeer)
+        {
+            lock (_peers)
+            {
+                for (int i = 0; i < _peers.Count; i++)
+                {
+                    var netPeer = _peers[i];
+                    if (netPeer != excludePeer)
+                    {
+                        netPeer.Send(data, start, length, options);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Start logic thread and listening on available port
+        /// </summary>
+        public bool Start()
+        {
+            return Start(0);
+        }
+
+        /// <summary>
+        /// Start logic thread and listening on selected port
+        /// </summary>
+        /// <param name="port">port to listen</param>
+        public bool Start(int port)
+        {
+            if (IsRunning)
+            {
+                return false;
+            }
+
+            _netEventsQueue.Clear();
+            if (!_socket.Bind(port, ReuseAddress))
+                return false;
+
+            _logicThread.Start();
+            return true;
+        }
+
+        /// <summary>
+        /// Send message without connection
+        /// </summary>
+        /// <param name="message">Raw data</param>
+        /// <param name="remoteEndPoint">Packet destination</param>
+        /// <returns>Operation result</returns>
+        public bool SendUnconnectedMessage(byte[] message, NetEndPoint remoteEndPoint)
+        {
+            return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint);
+        }
+
+        /// <summary>
+        /// Send message without connection
+        /// </summary>
+        /// <param name="writer">Data serializer</param>
+        /// <param name="remoteEndPoint">Packet destination</param>
+        /// <returns>Operation result</returns>
+        public bool SendUnconnectedMessage(NetDataWriter writer, NetEndPoint remoteEndPoint)
+        {
+            return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
+        }
+
+        /// <summary>
+        /// Send message without connection
+        /// </summary>
+        /// <param name="message">Raw data</param>
+        /// <param name="start">data start</param>
+        /// <param name="length">data length</param>
+        /// <param name="remoteEndPoint">Packet destination</param>
+        /// <returns>Operation result</returns>
+        public bool SendUnconnectedMessage(byte[] message, int start, int length, NetEndPoint remoteEndPoint)
+        {
+            if (!IsRunning)
+                return false;
+            var packet = _netPacketPool.GetWithData(PacketProperty.UnconnectedMessage, message, start, length);
+            bool result = SendRawAndRecycle(packet, remoteEndPoint);
+            return result;
+        }
+
+        public bool SendDiscoveryRequest(NetDataWriter writer, int port)
+        {
+            return SendDiscoveryRequest(writer.Data, 0, writer.Length, port);
+        }
+
+        public bool SendDiscoveryRequest(byte[] data, int port)
+        {
+            return SendDiscoveryRequest(data, 0, data.Length, port);
+        }
+
+        public bool SendDiscoveryRequest(byte[] data, int start, int length, int port)
+        {
+            if (!IsRunning)
+                return false;
+            var packet = _netPacketPool.GetWithData(PacketProperty.DiscoveryRequest, data, start, length);
+            bool result = _socket.SendBroadcast(packet.RawData, 0, packet.Size, port);
+            _netPacketPool.Recycle(packet);
+            return result;
+        }
+
+        public bool SendDiscoveryResponse(NetDataWriter writer, NetEndPoint remoteEndPoint)
+        {
+            return SendDiscoveryResponse(writer.Data, 0, writer.Length, remoteEndPoint);
+        }
+
+        public bool SendDiscoveryResponse(byte[] data, NetEndPoint remoteEndPoint)
+        {
+            return SendDiscoveryResponse(data, 0, data.Length, remoteEndPoint);
+        }
+
+        public bool SendDiscoveryResponse(byte[] data, int start, int length, NetEndPoint remoteEndPoint)
+        {
+            if (!IsRunning)
+                return false;
+            var packet = _netPacketPool.GetWithData(PacketProperty.DiscoveryResponse, data, start, length);
+            bool result = SendRawAndRecycle(packet, remoteEndPoint);
+            return result;
+        }
+
+        /// <summary>
+        /// Flush all queued packets of all peers
+        /// </summary>
+        public void Flush()
+        {
+            lock (_peers)
+            {
+                for (int i = 0; i < _peers.Count; i++)
+                {
+                    _peers[i].Flush();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Receive all pending events. Call this in game update code
+        /// </summary>
+        public void PollEvents()
+        {
+            if (UnsyncedEvents)
+                return;
+
+            while (_netEventsQueue.Count > 0)
+            {
+                NetEvent evt;
+                lock (_netEventsQueue)
+                {
+                    evt = _netEventsQueue.Dequeue();
+                }
+                ProcessEvent(evt);
+            }
+        }
+
+        /// <summary>
+        /// Connect to remote host
+        /// </summary>
+        /// <param name="address">Server IP or hostname</param>
+        /// <param name="port">Server Port</param>
+        public void Connect(string address, int port)
+        {
+            //Create target endpoint
+            NetEndPoint ep = new NetEndPoint(address, port);
+            Connect(ep);
+        }
+
+        /// <summary>
+        /// Connect to remote host
+        /// </summary>
+        /// <param name="target">Server end point (ip and port)</param>
+        public void Connect(NetEndPoint target)
+        {
+            if (!IsRunning)
+            {
+                throw new Exception("Client is not running");
+            }
+            lock (_peers)
+            {
+                if (_peers.ContainsAddress(target) || _peers.Count >= _maxConnections)
+                {
+                    //Already connected
+                    return;
+                }
+
+                //Create reliable connection
+                //And request connection
+                var newPeer = new NetPeer(this, target, 0);
+                _peers.Add(target, newPeer);
+            }
+        }
+
+        /// <summary>
+        /// Force closes connection and stop all threads.
+        /// </summary>
+        public void Stop()
+        {
+            //Send disconnect packets
+            lock (_peers)
+            {
+                for (int i = 0; i < _peers.Count; i++)
+                {
+                    var disconnectPacket = _netPacketPool.Get(PacketProperty.Disconnect, 8);
+                    FastBitConverter.GetBytes(disconnectPacket.RawData, 1, _peers[i].ConnectId);
+                    SendRawAndRecycle(disconnectPacket, _peers[i].EndPoint);
+                }
+            }
+
+            //Clear
+            ClearPeers();
+
+            //Stop
+            if (IsRunning)
+            {
+                _logicThread.Stop();
+                _socket.Close();
+            }
+        }
+
+        /// <summary>
+        /// Get first peer. Usefull for Client mode
+        /// </summary>
+        /// <returns></returns>
+        public NetPeer GetFirstPeer()
+        {
+            lock (_peers)
+            {
+                if (_peers.Count > 0)
+                {
+                    return _peers[0];
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Get copy of current connected peers
+        /// </summary>
+        /// <returns>Array with connected peers</returns>
+        public NetPeer[] GetPeers()
+        {
+            NetPeer[] peers;
+            lock (_peers)
+            {
+                peers = _peers.ToArray();
+            }
+            return peers;
+        }
+
+        /// <summary>
+        /// Get copy of current connected peers (without allocations)
+        /// </summary>
+        /// <param name="peers">List that will contain result</param>
+        public void GetPeersNonAlloc(List<NetPeer> peers)
+        {
+            peers.Clear();
+            lock (_peers)
+            {
+                for(int i = 0; i < _peers.Count; i++)
+                {
+                    peers.Add(_peers[i]);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Disconnect peer from server
+        /// </summary>
+        /// <param name="peer">peer to disconnect</param>
+        public void DisconnectPeer(NetPeer peer)
+        {
+            DisconnectPeer(peer, null, 0, 0);
+        }
+
+        /// <summary>
+        /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
+        /// </summary>
+        /// <param name="peer">peer to disconnect</param>
+        /// <param name="data">additional data</param>
+        public void DisconnectPeer(NetPeer peer, byte[] data)
+        {
+            DisconnectPeer(peer, data, 0, data.Length);
+        }
+
+        /// <summary>
+        /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
+        /// </summary>
+        /// <param name="peer">peer to disconnect</param>
+        /// <param name="writer">additional data</param>
+        public void DisconnectPeer(NetPeer peer, NetDataWriter writer)
+        {
+            DisconnectPeer(peer, writer.Data, 0, writer.Length);
+        }
+
+        /// <summary>
+        /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
+        /// </summary>
+        /// <param name="peer">peer to disconnect</param>
+        /// <param name="data">additional data</param>
+        /// <param name="start">data start</param>
+        /// <param name="count">data length</param>
+        public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count)
+        {
+            if (peer != null && _peers.ContainsAddress(peer.EndPoint))
+            {
+                DisconnectPeer(peer, DisconnectReason.DisconnectPeerCalled, 0, true, data, start, count);
+            }
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetManager.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: a0197124da84847d8855d5f690b8c653
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 163 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPacket.cs

@@ -0,0 +1,163 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using FlyingWormConsole3.LiteNetLib.Utils;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    internal enum PacketProperty : byte
+    {
+        Unreliable,             //0
+        Reliable,               //1
+        Sequenced,              //2
+        ReliableOrdered,        //3
+        AckReliable,            //4
+        AckReliableOrdered,     //5
+        Ping,                   //6
+        Pong,                   //7
+        ConnectRequest,         //8
+        ConnectAccept,          //9
+        Disconnect,             //10
+        UnconnectedMessage,     //11
+        NatIntroductionRequest, //12
+        NatIntroduction,        //13
+        NatPunchMessage,        //14
+        MtuCheck,               //15
+        MtuOk,                  //16
+        DiscoveryRequest,       //17
+        DiscoveryResponse,      //18
+        Merged                  //19
+    }
+
+    internal sealed class NetPacket
+    {
+        private const int LastProperty = 19;
+
+        //Header
+        public PacketProperty Property
+        {
+            get { return (PacketProperty)(RawData[0] & 0x7F); }
+            set { RawData[0] = (byte)((RawData[0] & 0x80) | ((byte)value & 0x7F)); }
+        }
+
+        public ushort Sequence
+        {
+            get { return BitConverter.ToUInt16(RawData, 1); }
+            set { FastBitConverter.GetBytes(RawData, 1, value); }
+        }
+
+        public bool IsFragmented
+        {
+            get { return (RawData[0] & 0x80) != 0; }
+            set
+            {
+                if (value)
+                    RawData[0] |= 0x80; //set first bit
+                else
+                    RawData[0] &= 0x7F; //unset first bit
+            }
+        }
+
+        public ushort FragmentId
+        {
+            get { return BitConverter.ToUInt16(RawData, 3); }
+            set { FastBitConverter.GetBytes(RawData, 3, value); }
+        }
+
+        public ushort FragmentPart
+        {
+            get { return BitConverter.ToUInt16(RawData, 5); }
+            set { FastBitConverter.GetBytes(RawData, 5, value); }
+        }
+
+        public ushort FragmentsTotal
+        {
+            get { return BitConverter.ToUInt16(RawData, 7); }
+            set { FastBitConverter.GetBytes(RawData, 7, value); }
+        }
+
+        //Data
+        public readonly byte[] RawData;
+        public int Size;
+
+        public NetPacket(int size)
+        {
+            RawData = new byte[size];
+            Size = 0;
+        }
+
+        public static bool GetPacketProperty(byte[] data, out PacketProperty property)
+        {
+            byte properyByte = (byte)(data[0] & 0x7F);
+            if (properyByte > LastProperty)
+            {
+                property = PacketProperty.Unreliable;
+                return false;
+            }
+            property = (PacketProperty)properyByte;
+            return true;
+        }
+
+        public static int GetHeaderSize(PacketProperty property)
+        {
+            return IsSequenced(property)
+                ? NetConstants.SequencedHeaderSize
+                : NetConstants.HeaderSize;
+        }
+
+        public int GetHeaderSize()
+        {
+            return GetHeaderSize(Property);
+        }
+
+        public byte[] GetPacketData()
+        {
+            int headerSize = GetHeaderSize(Property);
+            int dataSize = Size - headerSize;
+            byte[] data = new byte[dataSize];
+            Buffer.BlockCopy(RawData, headerSize, data, 0, dataSize);
+            return data;
+        }
+
+        public bool IsClientData()
+        {
+            var property = Property;
+            return property == PacketProperty.Reliable ||
+                   property == PacketProperty.ReliableOrdered ||
+                   property == PacketProperty.Unreliable ||
+                   property == PacketProperty.Sequenced;
+        }
+
+        public static bool IsSequenced(PacketProperty property)
+        {
+            return property == PacketProperty.ReliableOrdered ||
+                property == PacketProperty.Reliable ||
+                property == PacketProperty.Sequenced ||
+                property == PacketProperty.Ping ||
+                property == PacketProperty.Pong ||
+                property == PacketProperty.AckReliable ||
+                property == PacketProperty.AckReliableOrdered;
+        }
+
+        //Packet contstructor from byte array
+        public bool FromBytes(byte[] data, int start, int packetSize)
+        {
+            //Reading property
+            byte property = (byte)(data[start] & 0x7F);
+            bool fragmented = (data[start] & 0x80) != 0;
+            int headerSize = GetHeaderSize((PacketProperty) property);
+
+            if (property > LastProperty ||
+                packetSize > NetConstants.PacketSizeLimit ||
+                packetSize < headerSize ||
+                (fragmented && packetSize < headerSize + NetConstants.FragmentHeaderSize))
+            {
+                return false;
+            }
+
+            Buffer.BlockCopy(data, start, RawData, 0, packetSize);
+            Size = packetSize;
+            return true;
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPacket.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 7aa9ed55f53fa48569ccd0963c50d8da
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 101 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPacketPool.cs

@@ -0,0 +1,101 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Collections.Generic;
+using FlyingWormConsole3.LiteNetLib.Utils;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    internal class NetPacketPool
+    {
+        private readonly Stack<NetPacket> _pool;
+
+        public NetPacketPool()
+        {
+            _pool = new Stack<NetPacket>();
+        }
+
+        public NetPacket GetWithData(PacketProperty property, NetDataWriter writer)
+        {
+            var packet = Get(property, writer.Length);
+            Buffer.BlockCopy(writer.Data, 0, packet.RawData, NetPacket.GetHeaderSize(property), writer.Length);
+            return packet;
+        }
+
+        public NetPacket GetWithData(PacketProperty property, byte[] data, int start, int length)
+        {
+            var packet = Get(property, length);
+            Buffer.BlockCopy(data, start, packet.RawData, NetPacket.GetHeaderSize(property), length);
+            return packet;
+        }
+
+        //Get packet just for read
+        public NetPacket GetAndRead(byte[] data, int start, int count)
+        {
+            NetPacket packet = null;
+            lock (_pool)
+            {
+                if (_pool.Count > 0)
+                {
+                    packet = _pool.Pop();
+                }
+            }
+            if (packet == null)
+            {
+                //allocate new packet of max size or bigger
+                packet = new NetPacket(NetConstants.MaxPacketSize);
+            }
+            if (!packet.FromBytes(data, start, count))
+            {
+                Recycle(packet);
+                return null;
+            }
+            return packet;
+        }
+
+        //Get packet with size
+        public NetPacket Get(PacketProperty property, int size)
+        {
+            NetPacket packet = null;
+            size += NetPacket.GetHeaderSize(property);
+            if (size <= NetConstants.MaxPacketSize)
+            {
+                lock (_pool)
+                {
+                    if (_pool.Count > 0)
+                    {
+                        packet = _pool.Pop();
+                    }
+                }
+            }
+            if (packet == null)
+            {
+                //allocate new packet of max size or bigger
+                packet = new NetPacket(size > NetConstants.MaxPacketSize ? size : NetConstants.MaxPacketSize);
+            }
+            else
+            {
+                Array.Clear(packet.RawData, 0, size);
+            }
+            packet.Property = property;
+            packet.Size = size;
+            return packet;
+        }
+
+        public void Recycle(NetPacket packet)
+        { 
+            if (packet.Size > NetConstants.MaxPacketSize)
+            {
+                //Dont pool big packets. Save memory
+                return;
+            }
+
+            //Clean fragmented flag
+            packet.IsFragmented = false;
+            lock (_pool)
+            {
+                _pool.Push(packet);
+            }
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPacketPool.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 3637381933a4745b996d4dd48cd71efe
+timeCreated: 1497976517
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 857 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPeer.cs

@@ -0,0 +1,857 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Collections.Generic;
+using System.Text;
+using FlyingWormConsole3.LiteNetLib.Utils;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public enum ConnectionState
+    {
+        InProgress,
+        Connected,
+        Disconnected
+    }
+
+    public sealed class NetPeer
+    {
+        //Flow control
+        private int _currentFlowMode;
+        private int _sendedPacketsCount;                    
+        private int _flowTimer;
+
+        //Ping and RTT
+        private int _ping;
+        private int _rtt;
+        private int _avgRtt;
+        private int _rttCount;
+        private int _goodRttCount;
+        private ushort _pingSequence;
+        private ushort _remotePingSequence;
+        private double _resendDelay = 27.0;
+
+        private int _pingSendTimer;
+        private const int RttResetDelay = 1000;
+        private int _rttResetTimer;
+
+        private DateTime _pingTimeStart;
+        private int _timeSinceLastPacket;
+
+        //Common            
+        private readonly NetEndPoint _remoteEndPoint;
+        private readonly NetManager _peerListener;
+        private readonly NetPacketPool _packetPool;
+        private readonly object _flushLock = new object();
+
+        //Channels
+        private readonly ReliableChannel _reliableOrderedChannel;
+        private readonly ReliableChannel _reliableUnorderedChannel;
+        private readonly SequencedChannel _sequencedChannel;
+        private readonly SimpleChannel _simpleChannel;
+
+        private int _windowSize = NetConstants.DefaultWindowSize;
+
+        //MTU
+        private int _mtu = NetConstants.PossibleMtu[0];
+        private int _mtuIdx;
+        private bool _finishMtu;
+        private int _mtuCheckTimer;
+        private int _mtuCheckAttempts;
+        private const int MtuCheckDelay = 1000;
+        private const int MaxMtuCheckAttempts = 4;
+        private readonly object _mtuMutex = new object();
+
+        //Fragment
+        private class IncomingFragments
+        {
+            public NetPacket[] Fragments;
+            public int ReceivedCount;
+            public int TotalSize;
+        }
+        private ushort _fragmentId;
+        private readonly Dictionary<ushort, IncomingFragments> _holdedFragments;
+
+        //Merging
+        private readonly NetPacket _mergeData;
+        private int _mergePos;
+        private int _mergeCount;
+
+        //Connection
+        private int _connectAttempts;
+        private int _connectTimer;
+        private long _connectId;
+        private ConnectionState _connectionState;
+
+        public ConnectionState ConnectionState
+        {
+            get { return _connectionState; }
+        }
+
+        public long ConnectId
+        {
+            get { return _connectId; }
+        }
+
+        public NetEndPoint EndPoint
+        {
+            get { return _remoteEndPoint; }
+        }
+
+        public int Ping
+        {
+            get { return _ping; }
+        }
+
+        public int CurrentFlowMode
+        {
+            get { return _currentFlowMode; }
+        }
+
+        public int Mtu
+        {
+            get { return _mtu; }
+        }
+
+        public int TimeSinceLastPacket
+        {
+            get { return _timeSinceLastPacket; }
+        }
+
+        public NetManager NetManager
+        {
+            get { return _peerListener; }
+        }
+
+        public int PacketsCountInReliableQueue
+        {
+            get { return _reliableUnorderedChannel.PacketsInQueue; }
+        }
+
+        public int PacketsCountInReliableOrderedQueue
+        {
+            get { return _reliableOrderedChannel.PacketsInQueue; }
+        }
+
+        internal double ResendDelay
+        {
+            get { return _resendDelay; }
+        }
+
+        /// <summary>
+		/// Application defined object containing data about the connection
+		/// </summary>
+        public object Tag;
+
+        internal NetPeer(NetManager peerListener, NetEndPoint remoteEndPoint, long connectId)
+        {
+            _packetPool = peerListener.PacketPool;
+            _peerListener = peerListener;
+            _remoteEndPoint = remoteEndPoint;
+
+            _avgRtt = 0;
+            _rtt = 0;
+            _pingSendTimer = 0;
+
+            _reliableOrderedChannel = new ReliableChannel(this, true, _windowSize);
+            _reliableUnorderedChannel = new ReliableChannel(this, false, _windowSize);
+            _sequencedChannel = new SequencedChannel(this);
+            _simpleChannel = new SimpleChannel(this);
+
+            _holdedFragments = new Dictionary<ushort, IncomingFragments>();
+
+            _mergeData = _packetPool.Get(PacketProperty.Merged, NetConstants.MaxPacketSize);
+
+            //if ID != 0 then we already connected
+            _connectAttempts = 0;
+            if (connectId == 0)
+            {
+                _connectId = DateTime.UtcNow.Ticks;
+                SendConnectRequest();
+            }
+            else
+            {
+                _connectId = connectId;
+                _connectionState = ConnectionState.Connected;
+                SendConnectAccept();
+            }
+
+            NetUtils.DebugWrite(ConsoleColor.Cyan, "[CC] ConnectId: {0}", _connectId);
+        }
+
+        private void SendConnectRequest()
+        {
+            //Get connect key bytes
+            byte[] keyData = Encoding.UTF8.GetBytes(_peerListener.ConnectKey);
+
+            //Make initial packet
+            var connectPacket = _packetPool.Get(PacketProperty.ConnectRequest, 12 + keyData.Length);
+
+            //Add data
+            FastBitConverter.GetBytes(connectPacket.RawData, 1, NetConstants.ProtocolId);
+            FastBitConverter.GetBytes(connectPacket.RawData, 5, _connectId);
+            Buffer.BlockCopy(keyData, 0, connectPacket.RawData, 13, keyData.Length);
+
+            //Send raw
+            _peerListener.SendRawAndRecycle(connectPacket, _remoteEndPoint);
+        }
+
+        private void SendConnectAccept()
+        {
+            //Reset connection timer
+            _timeSinceLastPacket = 0;
+
+            //Make initial packet
+            var connectPacket = _packetPool.Get(PacketProperty.ConnectAccept, 8);
+
+            //Add data
+            FastBitConverter.GetBytes(connectPacket.RawData, 1, _connectId);
+
+            //Send raw
+            _peerListener.SendRawAndRecycle(connectPacket, _remoteEndPoint);
+        }
+
+        internal bool ProcessConnectAccept(NetPacket packet)
+        {
+            if (_connectionState != ConnectionState.InProgress)
+                return false;
+
+            //check connection id
+            if (BitConverter.ToInt64(packet.RawData, 1) != _connectId)
+            {
+                return false;
+            }
+
+            NetUtils.DebugWrite(ConsoleColor.Cyan, "[NC] Received connection accept");
+            _timeSinceLastPacket = 0;
+            _connectionState = ConnectionState.Connected;
+            return true;
+        }
+
+        private static PacketProperty SendOptionsToProperty(SendOptions options)
+        {
+            switch (options)
+            {
+                case SendOptions.ReliableUnordered:
+                    return PacketProperty.Reliable;
+                case SendOptions.Sequenced:
+                    return PacketProperty.Sequenced;
+                case SendOptions.ReliableOrdered:
+                    return PacketProperty.ReliableOrdered;
+                default:
+                    return PacketProperty.Unreliable;
+            }
+        }
+
+        public int GetMaxSinglePacketSize(SendOptions options)
+        {
+            return _mtu - NetPacket.GetHeaderSize(SendOptionsToProperty(options));
+        }
+
+        public void Send(byte[] data, SendOptions options)
+        {
+            Send(data, 0, data.Length, options);
+        }
+
+        public void Send(NetDataWriter dataWriter, SendOptions options)
+        {
+            Send(dataWriter.Data, 0, dataWriter.Length, options);
+        }
+
+        public void Send(byte[] data, int start, int length, SendOptions options)
+        {
+            //Prepare
+            PacketProperty property = SendOptionsToProperty(options);
+            int headerSize = NetPacket.GetHeaderSize(property);
+
+            //Check fragmentation
+            if (length + headerSize > _mtu)
+            {
+                if (options == SendOptions.Sequenced || options == SendOptions.Unreliable)
+                {
+                    throw new Exception("Unreliable packet size > allowed (" + (_mtu - headerSize) + ")");
+                }
+                
+                int packetFullSize = _mtu - headerSize;
+                int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize;
+
+                int fullPacketsCount = length / packetDataSize;
+                int lastPacketSize = length % packetDataSize;
+                int totalPackets = fullPacketsCount + (lastPacketSize == 0 ? 0 : 1);
+
+                NetUtils.DebugWrite("FragmentSend:\n" +
+                           " MTU: {0}\n" +
+                           " headerSize: {1}\n" +
+                           " packetFullSize: {2}\n" +
+                           " packetDataSize: {3}\n" +
+                           " fullPacketsCount: {4}\n" +
+                           " lastPacketSize: {5}\n" +
+                           " totalPackets: {6}", 
+                    _mtu, headerSize, packetFullSize, packetDataSize, fullPacketsCount, lastPacketSize, totalPackets);
+
+                if (totalPackets > ushort.MaxValue)
+                {
+                    throw new Exception("Too many fragments: " + totalPackets + " > " + ushort.MaxValue);
+                }
+
+                int dataOffset = headerSize + NetConstants.FragmentHeaderSize;
+                for (ushort i = 0; i < fullPacketsCount; i++)
+                {
+                    NetPacket p = _packetPool.Get(property, packetFullSize);
+                    p.FragmentId = _fragmentId;
+                    p.FragmentPart = i;
+                    p.FragmentsTotal = (ushort)totalPackets;
+                    p.IsFragmented = true;
+                    Buffer.BlockCopy(data, i * packetDataSize, p.RawData, dataOffset, packetDataSize);
+                    SendPacket(p);
+                }
+                
+                if (lastPacketSize > 0)
+                {
+                    NetPacket p = _packetPool.Get(property, lastPacketSize + NetConstants.FragmentHeaderSize);
+                    p.FragmentId = _fragmentId;
+                    p.FragmentPart = (ushort)fullPacketsCount; //last
+                    p.FragmentsTotal = (ushort)totalPackets;
+                    p.IsFragmented = true;
+                    Buffer.BlockCopy(data, fullPacketsCount * packetDataSize, p.RawData, dataOffset, lastPacketSize);
+                    SendPacket(p);
+                }
+
+                _fragmentId++;             
+                return;
+            }
+
+            //Else just send
+            NetPacket packet = _packetPool.GetWithData(property, data, start, length);
+            SendPacket(packet);
+        }
+
+        private void CreateAndSend(PacketProperty property, ushort sequence)
+        {
+            NetPacket packet = _packetPool.Get(property, 0);
+            packet.Sequence = sequence;
+            SendPacket(packet);
+        }
+
+        //from user thread, our thread, or recv?
+        private void SendPacket(NetPacket packet)
+        {
+            NetUtils.DebugWrite("[RS]Packet: " + packet.Property);
+            switch (packet.Property)
+            {
+                case PacketProperty.Reliable:
+                    _reliableUnorderedChannel.AddToQueue(packet);
+                    break;
+                case PacketProperty.Sequenced:
+                    _sequencedChannel.AddToQueue(packet);
+                    break;
+                case PacketProperty.ReliableOrdered:
+                    _reliableOrderedChannel.AddToQueue(packet);
+                    break;
+                case PacketProperty.Unreliable:
+                    _simpleChannel.AddToQueue(packet);
+                    break;
+                case PacketProperty.MtuCheck:
+                    //Must check result for MTU fix
+                    if (!_peerListener.SendRawAndRecycle(packet, _remoteEndPoint))
+                    {
+                        _finishMtu = true;
+                    }
+                    break;
+                case PacketProperty.AckReliable:
+                case PacketProperty.AckReliableOrdered:
+                case PacketProperty.Ping:
+                case PacketProperty.Pong:
+                case PacketProperty.Disconnect:
+                case PacketProperty.MtuOk:
+                    SendRawData(packet);
+                    _packetPool.Recycle(packet);
+                    break;
+                default:
+                    throw new Exception("Unknown packet property: " + packet.Property);
+            }
+        }
+
+        private void UpdateRoundTripTime(int roundTripTime)
+        {
+            //Calc average round trip time
+            _rtt += roundTripTime;
+            _rttCount++;
+            _avgRtt = _rtt/_rttCount;
+
+            //flowmode 0 = fastest
+            //flowmode max = lowest
+
+            if (_avgRtt < _peerListener.GetStartRtt(_currentFlowMode - 1))
+            {
+                if (_currentFlowMode <= 0)
+                {
+                    //Already maxed
+                    return;
+                }
+
+                _goodRttCount++;
+                if (_goodRttCount > NetConstants.FlowIncreaseThreshold)
+                {
+                    _goodRttCount = 0;
+                    _currentFlowMode--;
+
+                    NetUtils.DebugWrite("[PA]Increased flow speed, RTT: {0}, PPS: {1}", _avgRtt, _peerListener.GetPacketsPerSecond(_currentFlowMode));
+                }
+            }
+            else if(_avgRtt > _peerListener.GetStartRtt(_currentFlowMode))
+            {
+                _goodRttCount = 0;
+                if (_currentFlowMode < _peerListener.GetMaxFlowMode())
+                {
+                    _currentFlowMode++;
+                    NetUtils.DebugWrite("[PA]Decreased flow speed, RTT: {0}, PPS: {1}", _avgRtt, _peerListener.GetPacketsPerSecond(_currentFlowMode));
+                }
+            }
+
+            //recalc resend delay
+            double avgRtt = _avgRtt;
+            if (avgRtt <= 0.0)
+                avgRtt = 0.1;
+            _resendDelay = 25 + (avgRtt * 2.1); // 25 ms + double rtt
+        }
+
+        internal void AddIncomingPacket(NetPacket p)
+        {
+            if (p.IsFragmented)
+            {
+                NetUtils.DebugWrite("Fragment. Id: {0}, Part: {1}, Total: {2}", p.FragmentId, p.FragmentPart, p.FragmentsTotal);
+                //Get needed array from dictionary
+                ushort packetFragId = p.FragmentId;
+                IncomingFragments incomingFragments;
+                if (!_holdedFragments.TryGetValue(packetFragId, out incomingFragments))
+                {
+                    incomingFragments = new IncomingFragments
+                    {
+                        Fragments = new NetPacket[p.FragmentsTotal]
+                    };
+                    _holdedFragments.Add(packetFragId, incomingFragments);
+                }
+
+                //Cache
+                var fragments = incomingFragments.Fragments;
+
+                //Error check
+                if (p.FragmentPart >= fragments.Length || fragments[p.FragmentPart] != null)
+                {
+                    _packetPool.Recycle(p);
+                    NetUtils.DebugWriteError("Invalid fragment packet");
+                    return;
+                }
+                //Fill array
+                fragments[p.FragmentPart] = p;
+
+                //Increase received fragments count
+                incomingFragments.ReceivedCount++;
+
+                //Increase total size
+                int dataOffset = p.GetHeaderSize() + NetConstants.FragmentHeaderSize;
+                incomingFragments.TotalSize += p.Size - dataOffset;
+
+                //Check for finish
+                if (incomingFragments.ReceivedCount != fragments.Length)
+                {
+                    return;
+                }
+
+                NetUtils.DebugWrite("Received all fragments!");
+                NetPacket resultingPacket = _packetPool.Get( p.Property, incomingFragments.TotalSize );
+
+                int resultingPacketOffset = resultingPacket.GetHeaderSize();
+                int firstFragmentSize = fragments[0].Size - dataOffset;
+                for (int i = 0; i < incomingFragments.ReceivedCount; i++)
+                {
+                    //Create resulting big packet
+                    int fragmentSize = fragments[i].Size - dataOffset;
+                    Buffer.BlockCopy(
+                        fragments[i].RawData,
+                        dataOffset,
+                        resultingPacket.RawData,
+                        resultingPacketOffset + firstFragmentSize * i,
+                        fragmentSize);
+
+                    //Free memory
+                    _packetPool.Recycle(fragments[i]);
+                    fragments[i] = null;
+                }
+
+                //Send to process
+                _peerListener.ReceiveFromPeer(resultingPacket, _remoteEndPoint);
+
+                //Clear memory
+                _packetPool.Recycle(resultingPacket);
+                _holdedFragments.Remove(packetFragId);
+            }
+            else //Just simple packet
+            {
+                _peerListener.ReceiveFromPeer(p, _remoteEndPoint);
+                _packetPool.Recycle(p);
+            }
+        }
+
+        private void ProcessMtuPacket(NetPacket packet)
+        {
+            if (packet.Size == 1 || 
+                packet.RawData[1] >= NetConstants.PossibleMtu.Length)
+                return;
+
+            //MTU auto increase
+            if (packet.Property == PacketProperty.MtuCheck)
+            {
+                if (packet.Size != NetConstants.PossibleMtu[packet.RawData[1]])
+                {
+                    return;
+                }
+                _mtuCheckAttempts = 0;
+                NetUtils.DebugWrite("MTU check. Resend: " + packet.RawData[1]);
+                var mtuOkPacket = _packetPool.Get(PacketProperty.MtuOk, 1);
+                mtuOkPacket.RawData[1] = packet.RawData[1];
+                SendPacket(mtuOkPacket);
+            }
+            else if(packet.RawData[1] > _mtuIdx) //MtuOk
+            {
+                lock (_mtuMutex)
+                {
+                    _mtuIdx = packet.RawData[1];
+                    _mtu = NetConstants.PossibleMtu[_mtuIdx];
+                }
+                //if maxed - finish.
+                if (_mtuIdx == NetConstants.PossibleMtu.Length - 1)
+                {
+                    _finishMtu = true;
+                }
+                NetUtils.DebugWrite("MTU ok. Increase to: " + _mtu);
+            }
+        }
+
+        //Process incoming packet
+        internal void ProcessPacket(NetPacket packet)
+        {
+            _timeSinceLastPacket = 0;
+
+            NetUtils.DebugWrite("[RR]PacketProperty: {0}", packet.Property);
+            switch (packet.Property)
+            {
+                case PacketProperty.ConnectRequest:
+                    //response with connect
+                    long newId = BitConverter.ToInt64(packet.RawData, 1);
+                    if (newId > _connectId)
+                    {
+                        _connectId = newId;
+                    }
+
+                    NetUtils.DebugWrite("ConnectRequest LastId: {0}, NewId: {1}, EP: {2}", ConnectId, newId, _remoteEndPoint);
+                    SendConnectAccept();
+                    _packetPool.Recycle(packet);
+                    break;
+
+                case PacketProperty.Merged:
+                    int pos = NetConstants.HeaderSize;
+                    while (pos < packet.Size)
+                    {
+                        ushort size = BitConverter.ToUInt16(packet.RawData, pos);
+                        pos += 2;
+                        NetPacket mergedPacket = _packetPool.GetAndRead(packet.RawData, pos, size);
+                        if (mergedPacket == null)
+                        {
+                            _packetPool.Recycle(packet);
+                            break;
+                        }
+                        pos += size;
+                        ProcessPacket(mergedPacket);
+                    }
+                    break;
+                //If we get ping, send pong
+                case PacketProperty.Ping:
+                    if (NetUtils.RelativeSequenceNumber(packet.Sequence, _remotePingSequence) < 0)
+                    {
+                        _packetPool.Recycle(packet);
+                        break;
+                    }
+                    NetUtils.DebugWrite("[PP]Ping receive, send pong");
+                    _remotePingSequence = packet.Sequence;
+                    _packetPool.Recycle(packet);
+
+                    //send
+                    CreateAndSend(PacketProperty.Pong, _remotePingSequence);
+                    break;
+
+                //If we get pong, calculate ping time and rtt
+                case PacketProperty.Pong:
+                    if (NetUtils.RelativeSequenceNumber(packet.Sequence, _pingSequence) < 0)
+                    {
+                        _packetPool.Recycle(packet);
+                        break;
+                    }
+                    _pingSequence = packet.Sequence;
+                    int rtt = (int)(DateTime.UtcNow - _pingTimeStart).TotalMilliseconds;
+                    UpdateRoundTripTime(rtt);
+                    NetUtils.DebugWrite("[PP]Ping: {0}", rtt);
+                    _packetPool.Recycle(packet);
+                    break;
+
+                //Process ack
+                case PacketProperty.AckReliable:
+                    _reliableUnorderedChannel.ProcessAck(packet);
+                    _packetPool.Recycle(packet);
+                    break;
+
+                case PacketProperty.AckReliableOrdered:
+                    _reliableOrderedChannel.ProcessAck(packet);
+                    _packetPool.Recycle(packet);
+                    break;
+
+                //Process in order packets
+                case PacketProperty.Sequenced:
+                    _sequencedChannel.ProcessPacket(packet);
+                    break;
+
+                case PacketProperty.Reliable:
+                    _reliableUnorderedChannel.ProcessPacket(packet);
+                    break;
+
+                case PacketProperty.ReliableOrdered:
+                    _reliableOrderedChannel.ProcessPacket(packet);
+                    break;
+
+                //Simple packet without acks
+                case PacketProperty.Unreliable:
+                    AddIncomingPacket(packet);
+                    return;
+
+                case PacketProperty.MtuCheck:
+                case PacketProperty.MtuOk:
+                    ProcessMtuPacket(packet);
+                    break;
+
+                default:
+                    NetUtils.DebugWriteError("Error! Unexpected packet type: " + packet.Property);
+                    break;
+            }
+        }
+
+        private static bool CanMerge(PacketProperty property)
+        {
+            switch (property)
+            {
+                case PacketProperty.ConnectAccept:
+                case PacketProperty.ConnectRequest:
+                case PacketProperty.MtuOk:
+                case PacketProperty.Pong:
+                case PacketProperty.Disconnect:
+                    return false;
+                default:
+                    return true;
+            }
+        }
+
+        internal void SendRawData(NetPacket packet)
+        {
+            //2 - merge byte + minimal packet size + datalen(ushort)
+            if (_peerListener.MergeEnabled &&
+                CanMerge(packet.Property) &&
+                _mergePos + packet.Size + NetConstants.HeaderSize*2 + 2 < _mtu)
+            {
+                FastBitConverter.GetBytes(_mergeData.RawData, _mergePos + NetConstants.HeaderSize, (ushort)packet.Size);
+                Buffer.BlockCopy(packet.RawData, 0, _mergeData.RawData, _mergePos + NetConstants.HeaderSize + 2, packet.Size);
+                _mergePos += packet.Size + 2;
+                _mergeCount++;
+
+                //DebugWriteForce("Merged: " + _mergePos + "/" + (_mtu - 2) + ", count: " + _mergeCount);
+                return;
+            }
+
+            NetUtils.DebugWrite(ConsoleColor.DarkYellow, "[P]SendingPacket: " + packet.Property);
+            _peerListener.SendRaw(packet.RawData, 0, packet.Size, _remoteEndPoint);
+        }
+
+        private void SendQueuedPackets(int currentMaxSend)
+        {
+            int currentSended = 0;
+            while (currentSended < currentMaxSend)
+            {
+                //Get one of packets
+                if (_reliableOrderedChannel.SendNextPacket() ||
+                    _reliableUnorderedChannel.SendNextPacket() ||
+                    _sequencedChannel.SendNextPacket() ||
+                    _simpleChannel.SendNextPacket())
+                {
+                    currentSended++;
+                }
+                else
+                {
+                    //no outgoing packets
+                    break;
+                }
+            }
+
+            //Increase counter
+            _sendedPacketsCount += currentSended;
+
+            //If merging enabled
+            if (_mergePos > 0)
+            {
+                if (_mergeCount > 1)
+                {
+                    NetUtils.DebugWrite("Send merged: " + _mergePos + ", count: " + _mergeCount);
+                    _peerListener.SendRaw(_mergeData.RawData, 0, NetConstants.HeaderSize + _mergePos, _remoteEndPoint);
+                }
+                else
+                {
+                    //Send without length information and merging
+                    _peerListener.SendRaw(_mergeData.RawData, NetConstants.HeaderSize + 2, _mergePos - 2, _remoteEndPoint);
+                }
+                _mergePos = 0;
+                _mergeCount = 0;
+            }
+        }
+
+        /// <summary>
+        /// Flush all queued packets
+        /// </summary>
+        public void Flush()
+        {
+            lock (_flushLock)
+            {
+                SendQueuedPackets(int.MaxValue);
+            }
+        }
+
+        internal void Update(int deltaTime)
+        {
+            if (_connectionState == ConnectionState.Disconnected)
+            {
+                return;
+            }
+
+            _timeSinceLastPacket += deltaTime;
+            if (_connectionState == ConnectionState.InProgress)
+            {
+                _connectTimer += deltaTime;
+                if (_connectTimer > _peerListener.ReconnectDelay)
+                {
+                    _connectTimer = 0;
+                    _connectAttempts++;
+                    if (_connectAttempts > _peerListener.MaxConnectAttempts)
+                    {
+                        _connectionState = ConnectionState.Disconnected;
+                        return;
+                    }
+
+                    //else send connect again
+                    SendConnectRequest();
+                }
+                return;
+            }
+
+            //Get current flow mode
+            int maxSendPacketsCount = _peerListener.GetPacketsPerSecond(_currentFlowMode);
+            int currentMaxSend;
+
+            if (maxSendPacketsCount > 0)
+            {
+                int availableSendPacketsCount = maxSendPacketsCount - _sendedPacketsCount;
+                currentMaxSend = Math.Min(availableSendPacketsCount, (maxSendPacketsCount*deltaTime)/NetConstants.FlowUpdateTime);
+            }
+            else
+            {
+                currentMaxSend = int.MaxValue;
+            }
+
+            //DebugWrite("[UPDATE]Delta: {0}ms, MaxSend: {1}", deltaTime, currentMaxSend);
+
+            //Pending acks
+            _reliableOrderedChannel.SendAcks();
+            _reliableUnorderedChannel.SendAcks();
+
+            //ResetFlowTimer
+            _flowTimer += deltaTime;
+            if (_flowTimer >= NetConstants.FlowUpdateTime)
+            {
+                NetUtils.DebugWrite("[UPDATE]Reset flow timer, _sendedPackets - {0}", _sendedPacketsCount);
+                _sendedPacketsCount = 0;
+                _flowTimer = 0;
+            }
+
+            //Send ping
+            _pingSendTimer += deltaTime;
+            if (_pingSendTimer >= _peerListener.PingInterval)
+            {
+                NetUtils.DebugWrite("[PP] Send ping...");
+
+                //reset timer
+                _pingSendTimer = 0;
+
+                //send ping
+                CreateAndSend(PacketProperty.Ping, _pingSequence);
+
+                //reset timer
+                _pingTimeStart = DateTime.UtcNow;
+            }
+
+            //RTT - round trip time
+            _rttResetTimer += deltaTime;
+            if (_rttResetTimer >= RttResetDelay)
+            {
+                _rttResetTimer = 0;
+                //Rtt update
+                _rtt = _avgRtt;
+                _ping = _avgRtt;
+                _peerListener.ConnectionLatencyUpdated(this, _ping);
+                _rttCount = 1;
+            }
+
+            //MTU - Maximum transmission unit
+            if (!_finishMtu)
+            {
+                _mtuCheckTimer += deltaTime;
+                if (_mtuCheckTimer >= MtuCheckDelay)
+                {
+                    _mtuCheckTimer = 0;
+                    _mtuCheckAttempts++;
+                    if (_mtuCheckAttempts >= MaxMtuCheckAttempts)
+                    {
+                        _finishMtu = true;
+                    }
+                    else
+                    {
+                        lock (_mtuMutex)
+                        {
+                            //Send increased packet
+                            if (_mtuIdx < NetConstants.PossibleMtu.Length - 1)
+                            {
+                                int newMtu = NetConstants.PossibleMtu[_mtuIdx + 1] - NetConstants.HeaderSize;
+                                var p = _packetPool.Get(PacketProperty.MtuCheck, newMtu);
+                                p.RawData[1] = (byte)(_mtuIdx + 1);
+                                SendPacket(p);
+                            }
+                        }
+                    }
+                }
+            }
+            //MTU - end
+
+            //Pending send
+            lock (_flushLock)
+            {
+                SendQueuedPackets(currentMaxSend);
+            }
+        }
+
+        //For channels
+        internal void Recycle(NetPacket packet)
+        {
+            _packetPool.Recycle(packet);
+        }
+
+        internal NetPacket GetPacketFromPool(PacketProperty property, int bytesCount)
+        {
+            return _packetPool.Get(property, bytesCount);
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPeer.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 39ca2e83856ea4ba0a0100a00089e695
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 81 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPeerCollection.cs

@@ -0,0 +1,81 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Collections.Generic;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    internal sealed class NetPeerCollection
+    {
+        private readonly Dictionary<NetEndPoint, NetPeer> _peersDict;
+        private readonly NetPeer[] _peersArray;
+        private int _count;
+
+        public int Count
+        {
+            get { return _count; }
+        }
+
+        public NetPeer this[int index]
+        {
+            get { return _peersArray[index]; }
+        }
+
+        public NetPeerCollection(int maxPeers)
+        {
+            _peersArray = new NetPeer[maxPeers];
+            _peersDict = new Dictionary<NetEndPoint, NetPeer>();
+        }
+
+        public bool TryGetValue(NetEndPoint endPoint, out NetPeer peer)
+        {
+            return _peersDict.TryGetValue(endPoint, out peer);
+        }
+
+        public void Clear()
+        {
+            Array.Clear(_peersArray, 0, _count);
+            _peersDict.Clear();
+            _count = 0;
+        }
+
+        public void Add(NetEndPoint endPoint, NetPeer peer)
+        {
+            _peersArray[_count] = peer;
+            _peersDict.Add(endPoint, peer);
+            _count++;
+        }
+
+        public bool ContainsAddress(NetEndPoint endPoint)
+        {
+            return _peersDict.ContainsKey(endPoint);
+        }
+
+        public NetPeer[] ToArray()
+        {
+            NetPeer[] result = new NetPeer[_count];
+            Array.Copy(_peersArray, 0, result, 0, _count);
+            return result;
+        }
+
+        public void RemoveAt(int idx)
+        {
+            _peersDict.Remove(_peersArray[idx].EndPoint);
+            _peersArray[idx] = _peersArray[_count - 1];
+            _peersArray[_count - 1] = null;
+            _count--;
+        }
+
+        public void Remove(NetEndPoint endPoint)
+        {
+            for (int i = 0; i < _count; i++)
+            {
+                if (_peersArray[i].EndPoint.Equals(endPoint))
+                {
+                    RemoveAt(i);
+                    break;
+                }
+            }
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetPeerCollection.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: ca9b271291848481a86c9b3cb7f07451
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 709 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetSerializer.cs

@@ -0,0 +1,709 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+#if WINRT || NETCORE
+using System.Linq;
+#endif
+
+namespace FlyingWormConsole3.LiteNetLib.Utils
+{
+    public interface INetSerializable
+    {
+        void Serialize(NetDataWriter writer);
+        void Desereialize(NetDataReader reader);
+    }
+
+    public abstract class NetSerializerHasher
+    {
+        public abstract ulong GetHash(string type);
+        public abstract void WriteHash(ulong hash, NetDataWriter writer);
+        public abstract ulong ReadHash(NetDataReader reader);
+    }
+
+    public sealed class FNVHasher : NetSerializerHasher
+    {
+        private readonly Dictionary<string, ulong> _hashCache = new Dictionary<string, ulong>();
+        private readonly char[] _hashBuffer = new char[1024];
+
+        public override ulong GetHash(string type)
+        {
+            ulong hash;
+            if (_hashCache.TryGetValue(type, out hash))
+            {
+                return hash;
+            }
+            hash = 14695981039346656037UL; //offset
+            int len = type.Length;
+            type.CopyTo(0, _hashBuffer, 0, len);
+            for (var i = 0; i < len; i++)
+            {
+                hash = hash ^ _hashBuffer[i];
+                hash *= 1099511628211UL; //prime
+            }
+            _hashCache.Add(type, hash);
+            return hash;
+        }
+
+        public override ulong ReadHash(NetDataReader reader)
+        {
+            return reader.GetULong();
+        }
+
+        public override void WriteHash(ulong hash, NetDataWriter writer)
+        {
+            writer.Put(hash);
+        }
+    }
+
+    public sealed class NetSerializer
+    {
+        private sealed class CustomType
+        {
+            public readonly CustomTypeWrite WriteDelegate;
+            public readonly CustomTypeRead ReadDelegate;
+
+            public CustomType(CustomTypeWrite writeDelegate, CustomTypeRead readDelegate)
+            {
+                WriteDelegate = writeDelegate;
+                ReadDelegate = readDelegate;
+            }
+        }
+
+        private delegate void CustomTypeWrite(NetDataWriter writer, object customObj);
+        private delegate object CustomTypeRead(NetDataReader reader);
+
+        private sealed class StructInfo
+        {
+            public readonly Action<NetDataWriter>[] WriteDelegate;
+            public readonly Action<NetDataReader>[] ReadDelegate;
+            public readonly Type[] FieldTypes;
+            public object Reference;
+            public Func<object> CreatorFunc;
+            public Action<object, object> OnReceive;
+            public readonly ulong Hash;
+            public readonly int MembersCount;
+
+            public StructInfo(ulong hash, int membersCount)
+            {
+                Hash = hash;
+                MembersCount = membersCount;
+                WriteDelegate = new Action<NetDataWriter>[membersCount];
+                ReadDelegate = new Action<NetDataReader>[membersCount];
+                FieldTypes = new Type[membersCount];
+            }
+
+            public void Write(NetDataWriter writer, object obj)
+            {
+                Reference = obj;
+                for (int i = 0; i < MembersCount; i++)
+                {
+                    WriteDelegate[i](writer);
+                }
+            }
+
+            public void Read(NetDataReader reader)
+            {
+                for (int i = 0; i < MembersCount; i++)
+                {
+                    ReadDelegate[i](reader);
+                }
+            }
+        }
+
+        private readonly Dictionary<ulong, StructInfo> _cache;
+        private readonly Dictionary<Type, CustomType> _registeredCustomTypes;
+
+        private static readonly HashSet<Type> BasicTypes = new HashSet<Type>
+        {
+            typeof(int),
+            typeof(uint),
+            typeof(byte),
+            typeof(sbyte),
+            typeof(short),
+            typeof(ushort),
+            typeof(long),
+            typeof(ulong),
+            typeof(string),
+            typeof(float),
+            typeof(double),
+            typeof(bool)
+        };
+
+        private readonly NetDataWriter _writer;
+        private readonly NetSerializerHasher _hasher;
+        private const int MaxStringLenght = 1024;
+
+        public NetSerializer() : this(new FNVHasher())
+        {
+        }
+
+        public NetSerializer(NetSerializerHasher hasher)
+        {
+            _hasher = hasher;
+            _cache = new Dictionary<ulong, StructInfo>();
+            _registeredCustomTypes = new Dictionary<Type, CustomType>();
+            _writer = new NetDataWriter();
+        }
+
+        private bool RegisterCustomTypeInternal<T>(Func<T> constructor) where T : INetSerializable
+        {
+            var t = typeof(T);
+            if (_registeredCustomTypes.ContainsKey(t))
+            {
+                return false;
+            }
+
+            var rwDelegates = new CustomType(
+                (writer, obj) =>
+                {
+                    ((T)obj).Serialize(writer);
+                },
+                reader =>
+                {
+                    var instance = constructor();
+                    instance.Desereialize(reader);
+                    return instance;
+                });
+            _registeredCustomTypes.Add(t, rwDelegates);
+            return true;
+        }
+
+        /// <summary>
+        /// Register custom property type
+        /// </summary>
+        /// <typeparam name="T">INetSerializable structure</typeparam>
+        /// <returns>True - if register successful, false - if type already registered</returns>
+        public bool RegisterCustomType<T>() where T : struct, INetSerializable
+        {
+            return RegisterCustomTypeInternal(() => new T());
+        }
+
+        /// <summary>
+        /// Register custom property type
+        /// </summary>
+        /// <typeparam name="T">INetSerializable class</typeparam>
+        /// <returns>True - if register successful, false - if type already registered</returns>
+        public bool RegisterCustomType<T>(Func<T> constructor) where T : class, INetSerializable
+        {
+            return RegisterCustomTypeInternal(constructor);
+        }
+
+        /// <summary>
+        /// Register custom property type
+        /// </summary>
+        /// <param name="writeDelegate"></param>
+        /// <param name="readDelegate"></param>
+        /// <returns>True - if register successful, false - if type already registered</returns>
+        public bool RegisterCustomType<T>(Action<NetDataWriter, T> writeDelegate, Func<NetDataReader, T> readDelegate)
+        {
+            var t = typeof(T);
+            if (BasicTypes.Contains(t) || _registeredCustomTypes.ContainsKey(t))
+            {
+                return false;
+            }
+
+            var rwDelegates = new CustomType(
+                (writer, obj) => writeDelegate(writer, (T)obj),
+                reader => readDelegate(reader));
+
+            _registeredCustomTypes.Add(t, rwDelegates);
+            return true;
+        }
+
+        private static Delegate CreateDelegate(Type type, MethodInfo info)
+        {
+#if WINRT || NETCORE
+            return info.CreateDelegate(type);
+#else
+            return Delegate.CreateDelegate(type, info);
+#endif
+        }
+
+        private static Func<TClass, TProperty> ExtractGetDelegate<TClass, TProperty>(MethodInfo info)
+        {
+            return (Func<TClass, TProperty>)CreateDelegate(typeof(Func<TClass, TProperty>), info);
+        }
+
+        private static Action<TClass, TProperty> ExtractSetDelegate<TClass, TProperty>(MethodInfo info)
+        {
+            return (Action<TClass, TProperty>)CreateDelegate(typeof(Action<TClass, TProperty>), info);
+        }
+
+        private StructInfo RegisterInternal<T>() where T : class
+        {
+            Type t = typeof(T);
+            ulong nameHash = _hasher.GetHash(t.Name);
+
+            StructInfo info;
+            if (_cache.TryGetValue(nameHash, out info))
+            {
+                return info;
+            }
+
+#if WINRT || NETCORE
+            var props = t.GetRuntimeProperties().ToArray();
+            int propsCount = props.Count();
+#else
+            var props = t.GetProperties(
+                BindingFlags.Instance |
+                BindingFlags.Public |
+                BindingFlags.GetProperty |
+                BindingFlags.SetProperty);
+            int propsCount = props.Length;
+#endif
+            if (props == null || propsCount < 0)
+            {
+                throw new ArgumentException("Type does not contain acceptable fields");
+            }
+
+            info = new StructInfo(nameHash, propsCount);
+            for (int i = 0; i < props.Length; i++)
+            {
+                var property = props[i];
+                var propertyType = property.PropertyType;
+
+                //Set field type
+                info.FieldTypes[i] = propertyType.IsArray ? propertyType.GetElementType() : propertyType;
+#if WINRT || NETCORE
+                bool isEnum = propertyType.GetTypeInfo().IsEnum;
+                var getMethod = property.GetMethod;
+                var setMethod = property.SetMethod;
+#else
+                bool isEnum = propertyType.IsEnum;
+                var getMethod = property.GetGetMethod();
+                var setMethod = property.GetSetMethod();
+#endif
+                if (isEnum)
+                {
+                    var underlyingType = Enum.GetUnderlyingType(propertyType);
+                    if (underlyingType == typeof(byte))
+                    {
+                        info.ReadDelegate[i] = reader =>
+                        {
+                            property.SetValue(info.Reference, Enum.ToObject(propertyType, reader.GetByte()), null);
+                        };
+                        info.WriteDelegate[i] = writer =>
+                        {
+                            writer.Put((byte)property.GetValue(info.Reference, null));
+                        };
+                    }
+                    else if (underlyingType == typeof(int))
+                    {
+                        info.ReadDelegate[i] = reader =>
+                        {
+                            property.SetValue(info.Reference, Enum.ToObject(propertyType, reader.GetInt()), null);
+                        };
+                        info.WriteDelegate[i] = writer =>
+                        {
+                            writer.Put((int)property.GetValue(info.Reference, null));
+                        };
+                    }
+                    else
+                    {
+                        throw new Exception("Not supported enum underlying type: " + underlyingType.Name);
+                    }
+                }
+                else if (propertyType == typeof(string))
+                {
+                    var setDelegate = ExtractSetDelegate<T, string>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, string>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetString(MaxStringLenght));
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference), MaxStringLenght);
+                }
+                else if (propertyType == typeof(bool))
+                {
+                    var setDelegate = ExtractSetDelegate<T, bool>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, bool>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetBool());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(byte))
+                {
+                    var setDelegate = ExtractSetDelegate<T, byte>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, byte>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetByte());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(sbyte))
+                {
+                    var setDelegate = ExtractSetDelegate<T, sbyte>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, sbyte>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetSByte());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(short))
+                {
+                    var setDelegate = ExtractSetDelegate<T, short>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, short>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetShort());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(ushort))
+                {
+                    var setDelegate = ExtractSetDelegate<T, ushort>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, ushort>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetUShort());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(int))
+                {
+                    var setDelegate = ExtractSetDelegate<T, int>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, int>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetInt());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(uint))
+                {
+                    var setDelegate = ExtractSetDelegate<T, uint>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, uint>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetUInt());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(long))
+                {
+                    var setDelegate = ExtractSetDelegate<T, long>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, long>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetLong());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(ulong))
+                {
+                    var setDelegate = ExtractSetDelegate<T, ulong>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, ulong>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetULong());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(float))
+                {
+                    var setDelegate = ExtractSetDelegate<T, float>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, float>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetFloat());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(double))
+                {
+                    var setDelegate = ExtractSetDelegate<T, double>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, double>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetDouble());
+                    info.WriteDelegate[i] = writer => writer.Put(getDelegate((T)info.Reference));
+                }
+                // Array types
+                else if (propertyType == typeof(string[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, string[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, string[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetStringArray(MaxStringLenght));
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference), MaxStringLenght);
+                }
+                else if (propertyType == typeof(byte[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, byte[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, byte[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetBytesWithLength());
+                    info.WriteDelegate[i] = writer => writer.PutBytesWithLength(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(short[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, short[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, short[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetShortArray());
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(ushort[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, ushort[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, ushort[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetUShortArray());
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(int[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, int[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, int[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetIntArray());
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(uint[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, uint[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, uint[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetUIntArray());
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(long[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, long[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, long[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetLongArray());
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(ulong[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, ulong[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, ulong[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetULongArray());
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(float[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, float[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, float[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetFloatArray());
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference));
+                }
+                else if (propertyType == typeof(double[]))
+                {
+                    var setDelegate = ExtractSetDelegate<T, double[]>(setMethod);
+                    var getDelegate = ExtractGetDelegate<T, double[]>(getMethod);
+                    info.ReadDelegate[i] = reader => setDelegate((T)info.Reference, reader.GetDoubleArray());
+                    info.WriteDelegate[i] = writer => writer.PutArray(getDelegate((T)info.Reference));
+                }
+                else
+                {
+                    CustomType registeredCustomType;
+                    bool array = false;
+
+                    if (propertyType.IsArray)
+                    {
+                        array = true;
+                        propertyType = propertyType.GetElementType();
+                    }
+
+                    if (_registeredCustomTypes.TryGetValue(propertyType, out registeredCustomType))
+                    {
+                        if (array) //Array type serialize/deserialize
+                        {
+                            info.ReadDelegate[i] = reader =>
+                            {
+                                ushort arrLength = reader.GetUShort();
+                                Array arr = Array.CreateInstance(propertyType, arrLength);
+                                for (int k = 0; k < arrLength; k++)
+                                {
+                                    arr.SetValue(registeredCustomType.ReadDelegate(reader), k);
+                                }
+
+                                property.SetValue(info.Reference, arr, null);
+                            };
+
+                            info.WriteDelegate[i] = writer =>
+                            {
+                                Array arr = (Array)property.GetValue(info.Reference, null);
+                                writer.Put((ushort)arr.Length);
+                                for (int k = 0; k < arr.Length; k++)
+                                {
+                                    registeredCustomType.WriteDelegate(writer, arr.GetValue(k));
+                                }
+                            };
+                        }
+                        else //Simple
+                        {
+                            info.ReadDelegate[i] = reader =>
+                            {
+                                property.SetValue(info.Reference, registeredCustomType.ReadDelegate(reader), null);
+                            };
+
+                            info.WriteDelegate[i] = writer =>
+                            {
+                                registeredCustomType.WriteDelegate(writer, property.GetValue(info.Reference, null));
+                            };
+                        }
+                    }
+                    else
+                    {
+                        throw new Exception("Unknown property type: " + propertyType.Name);
+                    }
+                }
+            }
+            _cache.Add(nameHash, info);
+
+            return info;
+        }
+
+        /// <summary>
+        /// Reads all available data from NetDataReader and calls OnReceive delegates
+        /// </summary>
+        /// <param name="reader">NetDataReader with packets data</param>
+        public void ReadAllPackets(NetDataReader reader)
+        {
+            while (reader.AvailableBytes > 0)
+            {
+                ReadPacket(reader);
+            }
+        }
+
+        /// <summary>
+        /// Reads all available data from NetDataReader and calls OnReceive delegates
+        /// </summary>
+        /// <param name="reader">NetDataReader with packets data</param>
+        /// <param name="userData">Argument that passed to OnReceivedEvent</param>
+        public void ReadAllPackets<T>(NetDataReader reader, T userData)
+        {
+            while (reader.AvailableBytes > 0)
+            {
+                ReadPacket(reader, userData);
+            }
+        }
+
+        /// <summary>
+        /// Reads one packet from NetDataReader and calls OnReceive delegate
+        /// </summary>
+        /// <param name="reader">NetDataReader with packet</param>
+        public void ReadPacket(NetDataReader reader)
+        {
+            ReadPacket(reader, null);
+        }
+
+        private StructInfo ReadInfo(NetDataReader reader)
+        {
+            ulong hash = _hasher.ReadHash(reader);
+            StructInfo info;
+            if (!_cache.TryGetValue(hash, out info))
+            {
+                throw new Exception("Undefined packet received");
+            }
+            return info;
+        }
+
+        /// <summary>
+        /// Reads packet with known type
+        /// </summary>
+        /// <param name="reader">NetDataReader with packet</param>
+        /// <returns>Returns packet if packet in reader is matched type</returns>
+        public T ReadKnownPacket<T>(NetDataReader reader) where T : class, new()
+        {
+            var info = ReadInfo(reader);
+            ulong typeHash = _hasher.GetHash(typeof(T).Name);
+            if (typeHash != info.Hash)
+            {
+                return null;
+            }
+            info.Reference = info.CreatorFunc != null ? info.CreatorFunc() : Activator.CreateInstance<T>();
+            info.Read(reader);
+            return (T)info.Reference;
+        }
+
+        /// <summary>
+        /// Reads packet with known type (non alloc variant)
+        /// </summary>
+        /// <param name="reader">NetDataReader with packet</param>
+        /// <param name="target">Deserialization target</param>
+        /// <returns>Returns true if packet in reader is matched type</returns>
+        public bool ReadKnownPacket<T>(NetDataReader reader, T target) where T : class, new()
+        {
+            var info = ReadInfo(reader);
+            ulong typeHash = _hasher.GetHash(typeof(T).Name);
+            if (typeHash != info.Hash)
+            {
+                return false;
+            }
+
+            info.Reference = target;
+            info.Read(reader);
+            return true;
+        }
+
+        /// <summary>
+        /// Reads one packet from NetDataReader and calls OnReceive delegate
+        /// </summary>
+        /// <param name="reader">NetDataReader with packet</param>
+        /// <param name="userData">Argument that passed to OnReceivedEvent</param>
+        public void ReadPacket(NetDataReader reader, object userData)
+        {
+            var info = ReadInfo(reader);
+            if (info.CreatorFunc != null)
+            {
+                info.Reference = info.CreatorFunc();
+            }
+            info.Read(reader);
+            info.OnReceive(info.Reference, userData);
+        }
+
+        /// <summary>
+        /// Register and subscribe to packet receive event
+        /// </summary>
+        /// <param name="onReceive">event that will be called when packet deserialized with ReadPacket method</param>
+        /// <param name="packetConstructor">Method that constructs packet intead of slow Activator.CreateInstance</param>
+        public void Subscribe<T>(Action<T> onReceive, Func<T> packetConstructor) where T : class, new()
+        {
+            var info = RegisterInternal<T>();
+            info.CreatorFunc = () => packetConstructor();
+            info.OnReceive = (o, userData) => { onReceive((T)o); };
+        }
+
+        /// <summary>
+        /// Register packet type for direct reading (ReadKnownPacket)
+        /// </summary>
+        /// <param name="packetConstructor">Method that constructs packet intead of slow Activator.CreateInstance</param>
+        public void Register<T>(Func<T> packetConstructor = null) where T : class, new()
+        {
+            var info = RegisterInternal<T>();
+            if (packetConstructor != null)
+            {
+                info.CreatorFunc = () => packetConstructor();
+            }
+            info.OnReceive = (o, userData) => { };
+        }
+
+        /// <summary>
+        /// Register and subscribe to packet receive event (with userData)
+        /// </summary>
+        /// <param name="onReceive">event that will be called when packet deserialized with ReadPacket method</param>
+        /// <param name="packetConstructor">Method that constructs packet intead of slow Activator.CreateInstance</param>
+        public void Subscribe<T, TUserData>(Action<T, TUserData> onReceive, Func<T> packetConstructor) where T : class, new()
+        {
+            var info = RegisterInternal<T>();
+            info.CreatorFunc = () => packetConstructor();
+            info.OnReceive = (o, userData) => { onReceive((T)o, (TUserData)userData); };
+        }
+
+        /// <summary>
+        /// Register and subscribe to packet receive event
+        /// This metod will overwrite last received packet class on receive (less garbage)
+        /// </summary>
+        /// <param name="onReceive">event that will be called when packet deserialized with ReadPacket method</param>
+        public void SubscribeReusable<T>(Action<T> onReceive) where T : class, new()
+        {
+            var info = RegisterInternal<T>();
+            info.Reference = new T();
+            info.OnReceive = (o, userData) => { onReceive((T)o); };
+        }
+
+        /// <summary>
+        /// Register and subscribe to packet receive event
+        /// This metod will overwrite last received packet class on receive (less garbage)
+        /// </summary>
+        /// <param name="onReceive">event that will be called when packet deserialized with ReadPacket method</param>
+        public void SubscribeReusable<T, TUserData>(Action<T, TUserData> onReceive) where T : class, new()
+        {
+            var info = RegisterInternal<T>();
+            info.Reference = new T();
+            info.OnReceive = (o, userData) => { onReceive((T)o, (TUserData)userData); };
+        }
+
+        /// <summary>
+        /// Serialize struct to NetDataWriter (fast)
+        /// </summary>
+        /// <param name="writer">Serialization target NetDataWriter</param>
+        /// <param name="obj">Struct to serialize</param>
+        public void Serialize<T>(NetDataWriter writer, T obj) where T : class, new()
+        {
+            var info = RegisterInternal<T>();
+            _hasher.WriteHash(info.Hash, writer);
+            info.Write(writer, obj);
+        }
+
+        /// <summary>
+        /// Serialize struct to byte array
+        /// </summary>
+        /// <param name="obj">Struct to serialize</param>
+        /// <returns>byte array with serialized data</returns>
+        public byte[] Serialize<T>(T obj) where T : class, new()
+        {
+            _writer.Reset();
+            Serialize(_writer, obj);
+            return _writer.CopyData();
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetSerializer.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: e26db3a8188754e07b42db31f32e20f7
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 455 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetSocket.cs

@@ -0,0 +1,455 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+#if !WINRT || UNITY_EDITOR
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    internal sealed class NetSocket
+    {
+        private Socket _udpSocketv4;
+        private Socket _udpSocketv6;
+        private NetEndPoint _localEndPoint;
+        private Thread _threadv4;
+        private Thread _threadv6;
+        private bool _running;
+        private readonly NetManager.OnMessageReceived _onMessageReceived;
+
+        private static readonly IPAddress MulticastAddressV6 = IPAddress.Parse (NetConstants.MulticastGroupIPv6);
+        private static readonly bool IPv6Support;
+        private const int SocketReceivePollTime = 100000;
+        private const int SocketSendPollTime = 5000;
+
+        public NetEndPoint LocalEndPoint
+        {
+            get { return _localEndPoint; }
+        }
+
+        static NetSocket()
+        {
+            try
+            {
+                //Unity3d .NET 2.0 throws exception.
+                // IPv6Support = Socket.OSSupportsIPv6;
+                IPv6Support = false;
+            }
+            catch 
+            {
+                IPv6Support = false;
+            }
+        }
+
+        public NetSocket(NetManager.OnMessageReceived onMessageReceived)
+        {
+            _onMessageReceived = onMessageReceived;
+        }
+
+        private void ReceiveLogic(object state)
+        {
+            Socket socket = (Socket)state;
+            EndPoint bufferEndPoint = new IPEndPoint(socket.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, 0);
+            NetEndPoint bufferNetEndPoint = new NetEndPoint((IPEndPoint)bufferEndPoint);
+            byte[] receiveBuffer = new byte[NetConstants.PacketSizeLimit];
+
+            while (_running)
+            {
+                //wait for data
+                if (!socket.Poll(SocketReceivePollTime, SelectMode.SelectRead))
+                {
+                    continue;
+                }
+
+                int result;
+
+                //Reading data
+                try
+                {
+                    result = socket.ReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, ref bufferEndPoint);
+                    if (!bufferNetEndPoint.EndPoint.Equals(bufferEndPoint))
+                    {
+                        bufferNetEndPoint = new NetEndPoint((IPEndPoint)bufferEndPoint);
+                    }
+                }
+                catch (SocketException ex)
+                {
+                    if (ex.SocketErrorCode == SocketError.ConnectionReset ||
+                        ex.SocketErrorCode == SocketError.MessageSize)
+                    {
+                        //10040 - message too long
+                        //10054 - remote close (not error)
+                        //Just UDP
+                        NetUtils.DebugWrite(ConsoleColor.DarkRed, "[R] Ingored error: {0} - {1}", (int)ex.SocketErrorCode, ex.ToString() );
+                        continue;
+                    }
+                    NetUtils.DebugWriteError("[R]Error code: {0} - {1}", (int)ex.SocketErrorCode, ex.ToString());
+                    _onMessageReceived(null, 0, (int)ex.SocketErrorCode, bufferNetEndPoint);
+                    continue;
+                }
+
+                //All ok!
+                NetUtils.DebugWrite(ConsoleColor.Blue, "[R]Recieved data from {0}, result: {1}", bufferNetEndPoint.ToString(), result);
+                _onMessageReceived(receiveBuffer, result, 0, bufferNetEndPoint);
+            }
+        }
+
+        public bool Bind(int port, bool reuseAddress)
+        {
+            _udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+            _udpSocketv4.Blocking = false;
+            _udpSocketv4.ReceiveBufferSize = NetConstants.SocketBufferSize;
+            _udpSocketv4.SendBufferSize = NetConstants.SocketBufferSize;
+            _udpSocketv4.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.IpTimeToLive, NetConstants.SocketTTL);
+            if(reuseAddress)
+                _udpSocketv4.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+#if !NETCORE
+            _udpSocketv4.DontFragment = true;
+#endif
+
+            try
+            {
+                _udpSocketv4.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
+            }
+            catch (SocketException e)
+            {
+                NetUtils.DebugWriteError("Broadcast error: {0}", e.ToString());
+            }
+
+            if (!BindSocket(_udpSocketv4, new IPEndPoint(IPAddress.Any, port)))
+            {
+                return false;
+            }
+            _localEndPoint = new NetEndPoint((IPEndPoint)_udpSocketv4.LocalEndPoint);
+
+            _running = true;
+            _threadv4 = new Thread(ReceiveLogic);
+            _threadv4.Name = "SocketThreadv4(" + port + ")";
+            _threadv4.IsBackground = true;
+            _threadv4.Start(_udpSocketv4);
+
+            //Check IPv6 support
+            if (!IPv6Support)
+                return true;
+
+            //Use one port for two sockets
+            port = _localEndPoint.Port;
+
+            _udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
+            _udpSocketv6.Blocking = false;
+            _udpSocketv6.ReceiveBufferSize = NetConstants.SocketBufferSize;
+            _udpSocketv6.SendBufferSize = NetConstants.SocketBufferSize;
+            if (reuseAddress)
+                _udpSocketv6.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+
+            if (BindSocket(_udpSocketv6, new IPEndPoint(IPAddress.IPv6Any, port)))
+            {
+                _localEndPoint = new NetEndPoint((IPEndPoint)_udpSocketv6.LocalEndPoint);
+
+                try
+                {
+                    _udpSocketv6.SetSocketOption(
+                        SocketOptionLevel.IPv6, 
+                        SocketOptionName.AddMembership,
+                        new IPv6MulticastOption(MulticastAddressV6));
+                }
+                catch(Exception)
+                {
+                    // Unity3d throws exception - ignored
+                }
+
+                _threadv6 = new Thread(ReceiveLogic);
+                _threadv6.Name = "SocketThreadv6(" + port + ")";
+                _threadv6.IsBackground = true;
+                _threadv6.Start(_udpSocketv6);
+            }
+
+            return true;
+        }
+
+        private bool BindSocket(Socket socket, IPEndPoint ep)
+        {
+            try
+            {
+                socket.Bind(ep);
+                NetUtils.DebugWrite(ConsoleColor.Blue, "[B]Succesfully binded to port: {0}", ((IPEndPoint)socket.LocalEndPoint).Port);
+            }
+            catch (SocketException ex)
+            {
+                NetUtils.DebugWriteError("[B]Bind exception: {0}", ex.ToString());
+                //TODO: very temporary hack for iOS (Unity3D)
+                if (ex.SocketErrorCode == SocketError.AddressFamilyNotSupported)
+                {
+                    return true;
+                }
+                return false;
+            }
+            return true;
+        }
+
+        public bool SendBroadcast(byte[] data, int offset, int size, int port)
+        {
+            try
+            {
+                int result = _udpSocketv4.SendTo(data, offset, size, SocketFlags.None, new IPEndPoint(IPAddress.Broadcast, port));
+                if (result <= 0)
+                    return false;
+                if (IPv6Support)
+                {
+                    result = _udpSocketv6.SendTo(data, offset, size, SocketFlags.None, new IPEndPoint(MulticastAddressV6, port));
+                    if (result <= 0)
+                        return false;
+                }
+            }
+            catch (Exception ex)
+            {
+                NetUtils.DebugWriteError("[S][MCAST]" + ex);
+                return false;
+            }
+            return true;
+        }
+
+        public int SendTo(byte[] data, int offset, int size, NetEndPoint remoteEndPoint, ref int errorCode)
+        {
+            try
+            {
+                int result = 0;
+                if (remoteEndPoint.EndPoint.AddressFamily == AddressFamily.InterNetwork)
+                {
+                    if (!_udpSocketv4.Poll(SocketSendPollTime, SelectMode.SelectWrite))
+                        return -1;
+                    result = _udpSocketv4.SendTo(data, offset, size, SocketFlags.None, remoteEndPoint.EndPoint);
+                }
+                else if(IPv6Support)
+                {
+                    if (!_udpSocketv6.Poll(SocketSendPollTime, SelectMode.SelectWrite))
+                        return -1;
+                    result = _udpSocketv6.SendTo(data, offset, size, SocketFlags.None, remoteEndPoint.EndPoint);
+                }
+
+                NetUtils.DebugWrite(ConsoleColor.Blue, "[S]Send packet to {0}, result: {1}", remoteEndPoint.EndPoint, result);
+                return result;
+            }
+            catch (SocketException ex)
+            {
+                if (ex.SocketErrorCode != SocketError.MessageSize)
+                {
+                    NetUtils.DebugWriteError("[S]" + ex);
+                }
+                
+                errorCode = (int)ex.SocketErrorCode;
+                return -1;
+            }
+            catch (Exception ex)
+            {
+                NetUtils.DebugWriteError("[S]" + ex);
+                return -1;
+            }
+        }
+
+        private void CloseSocket(Socket s)
+        {
+#if NETCORE
+            s.Dispose();
+#else
+            s.Close();
+#endif
+        }
+
+        public void Close()
+        {
+            _running = false;
+
+            //Close IPv4
+            if (Thread.CurrentThread != _threadv4)
+            {
+                _threadv4.Join();
+            }
+            _threadv4 = null;
+            if (_udpSocketv4 != null)
+            {
+                CloseSocket(_udpSocketv4);
+                _udpSocketv4 = null;
+            }
+
+            //No ipv6
+            if (_udpSocketv6 == null)
+                return;
+
+            //Close IPv6
+            if (Thread.CurrentThread != _threadv6)
+            {
+                _threadv6.Join();
+            }
+            _threadv6 = null;
+            if (_udpSocketv6 != null)
+            {
+                CloseSocket(_udpSocketv6);
+                _udpSocketv6 = null;
+            }
+        }
+    }
+}
+#else
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Networking;
+using Windows.Networking.Sockets;
+using Windows.Storage.Streams;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    internal sealed class NetSocket
+    {
+        private DatagramSocket _datagramSocket;
+        private readonly Dictionary<NetEndPoint, IOutputStream> _peers = new Dictionary<NetEndPoint, IOutputStream>();
+        private readonly NetManager.OnMessageReceived _onMessageReceived;
+        private readonly byte[] _byteBuffer = new byte[NetConstants.PacketSizeLimit];
+        private readonly IBuffer _buffer;
+        private NetEndPoint _bufferEndPoint;
+        private NetEndPoint _localEndPoint;
+        private static readonly HostName BroadcastAddress = new HostName("255.255.255.255");
+        private static readonly HostName MulticastAddressV6 = new HostName(NetConstants.MulticastGroupIPv6);
+
+        public NetEndPoint LocalEndPoint
+        {
+            get { return _localEndPoint; }
+        }
+
+        public NetSocket(NetManager.OnMessageReceived onMessageReceived)
+        {
+            _onMessageReceived = onMessageReceived;
+            _buffer = _byteBuffer.AsBuffer();
+        }
+        
+        private void OnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
+        {
+            var result = args.GetDataStream().ReadAsync(_buffer, _buffer.Capacity, InputStreamOptions.None).AsTask().Result;
+            int length = (int)result.Length;
+            if (length <= 0)
+                return;
+
+            if (_bufferEndPoint == null ||
+                !_bufferEndPoint.HostName.IsEqual(args.RemoteAddress) ||
+                !_bufferEndPoint.PortStr.Equals(args.RemotePort))
+            {
+                _bufferEndPoint = new NetEndPoint(args.RemoteAddress, args.RemotePort);
+            }
+            _onMessageReceived(_byteBuffer, length, 0, _bufferEndPoint);
+        }
+
+        public bool Bind(int port, bool reuseAddress)
+        {
+            _datagramSocket = new DatagramSocket();
+            _datagramSocket.Control.InboundBufferSizeInBytes = NetConstants.SocketBufferSize;
+            _datagramSocket.Control.DontFragment = true;
+            _datagramSocket.Control.OutboundUnicastHopLimit = NetConstants.SocketTTL;
+            _datagramSocket.MessageReceived += OnMessageReceived;
+
+            try
+            {
+                _datagramSocket.BindServiceNameAsync(port.ToString()).AsTask().Wait();
+                _datagramSocket.JoinMulticastGroup(MulticastAddressV6);
+                _localEndPoint = new NetEndPoint(_datagramSocket.Information.LocalAddress, _datagramSocket.Information.LocalPort);
+            }
+            catch (Exception ex)
+            {
+                NetUtils.DebugWriteError("[B]Bind exception: {0}", ex.ToString());
+                return false;
+            }
+            return true;
+        }
+
+        public bool SendBroadcast(byte[] data, int offset, int size, int port)
+        {
+            var portString = port.ToString();
+            try
+            {
+                var outputStream =
+                    _datagramSocket.GetOutputStreamAsync(BroadcastAddress, portString)
+                        .AsTask()
+                        .Result;
+                var writer = outputStream.AsStreamForWrite();
+                writer.Write(data, offset, size);
+                writer.Flush();
+
+                outputStream =
+                    _datagramSocket.GetOutputStreamAsync(MulticastAddressV6, portString)
+                        .AsTask()
+                        .Result;
+                writer = outputStream.AsStreamForWrite();
+                writer.Write(data, offset, size);
+                writer.Flush();
+            }
+            catch (Exception ex)
+            {
+                NetUtils.DebugWriteError("[S][MCAST]" + ex);
+                return false;
+            }
+            return true;
+        }
+
+        public int SendTo(byte[] data, int offset, int length, NetEndPoint remoteEndPoint, ref int errorCode)
+        {
+            Task<uint> task = null;
+            try
+            {
+                IOutputStream writer;
+                if (!_peers.TryGetValue(remoteEndPoint, out writer))
+                {
+                    writer =
+                        _datagramSocket.GetOutputStreamAsync(remoteEndPoint.HostName, remoteEndPoint.PortStr)
+                            .AsTask()
+                            .Result;
+                    _peers.Add(remoteEndPoint, writer);
+                }
+
+                task = writer.WriteAsync(data.AsBuffer(offset, length)).AsTask();
+                return (int)task.Result;
+            }
+            catch (Exception ex)
+            {
+                if (task?.Exception?.InnerExceptions != null)
+                {
+                    ex = task.Exception.InnerException;
+                }
+                var errorStatus = SocketError.GetStatus(ex.HResult);
+                switch (errorStatus)
+                {
+                    case SocketErrorStatus.MessageTooLong:
+                        errorCode = 10040;
+                        break;
+                    default:
+                        errorCode = (int)errorStatus;
+                        NetUtils.DebugWriteError("[S " + errorStatus + "(" + errorCode + ")]" + ex);
+                        break;
+                }
+                
+                return -1;
+            }
+        }
+
+        internal void RemovePeer(NetEndPoint ep)
+        {
+            _peers.Remove(ep);
+        }
+
+        public void Close()
+        {
+            _datagramSocket.Dispose();
+            _datagramSocket = null;
+            ClearPeers();
+        }
+
+        internal void ClearPeers()
+        {
+            _peers.Clear();
+        }
+    }
+}
+#endif
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetSocket.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: c517b909a8c704eae91d4eccf06bc8a1
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 97 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetThread.cs

@@ -0,0 +1,97 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+#if WINRT && !UNITY_EDITOR
+#define USE_WINRT
+#endif
+
+using System;
+using System.Threading;
+
+#if USE_WINRT
+using Windows.Foundation;
+using Windows.System.Threading;
+using Windows.System.Threading.Core;
+#endif
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public sealed class NetThread
+    {
+#if USE_WINRT
+        private readonly ManualResetEvent _updateWaiter = new ManualResetEvent(false);
+        private readonly ManualResetEvent _joinWaiter = new ManualResetEvent(false);
+#else
+        private Thread _thread;
+#endif
+
+        private readonly Action _callback;
+
+        public int SleepTime;
+        private bool _running;
+        private readonly string _name;
+
+        public bool IsRunning
+        {
+            get { return _running; }
+        }
+
+        public NetThread(string name, int sleepTime, Action callback)
+        {
+            _callback = callback;
+            SleepTime = sleepTime;
+            _name = name;
+        }
+
+        public void Start()
+        {
+            if (_running)
+                return;
+            _running = true;
+#if USE_WINRT
+            var thread = new PreallocatedWorkItem(ThreadLogic, WorkItemPriority.Normal, WorkItemOptions.TimeSliced);
+            thread.RunAsync().AsTask();
+#else
+            _thread = new Thread(ThreadLogic)
+            {
+                Name = _name,
+                IsBackground = true
+            };
+            _thread.Start();
+#endif
+        }
+
+        public void Stop()
+        {
+            if (!_running)
+                return;
+            _running = false;
+
+#if USE_WINRT
+            _joinWaiter.WaitOne();
+#else
+            _thread.Join();
+#endif
+        }
+
+#if USE_WINRT
+        private void ThreadLogic(IAsyncAction action)
+        {
+            while (_running)
+            {
+                _callback();
+                _updateWaiter.WaitOne(SleepTime);
+            }
+            _joinWaiter.Set();
+        }
+#else
+        private void ThreadLogic()
+        {
+            while (_running)
+            {
+                _callback();
+                Thread.Sleep(SleepTime);
+            }
+        }
+#endif
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetThread.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: d1928476aac6242d29fab6849f102494
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 219 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetUtils.cs

@@ -0,0 +1,219 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+#define UNITY
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+#if WINRT && !UNITY_EDITOR
+using Windows.Networking;
+using Windows.Networking.Connectivity;
+#else
+using System.Net;
+using System.Net.Sockets;
+using System.Net.NetworkInformation;
+#endif
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+#if WINRT && !UNITY_EDITOR
+    public enum ConsoleColor
+    {
+        Gray,
+        Yellow,
+        Cyan,
+        DarkCyan,
+        DarkGreen,
+        Blue,
+        DarkRed,
+        Red,
+        Green,
+        DarkYellow
+    }
+#endif
+
+    [Flags]
+    public enum LocalAddrType
+    {
+        IPv4 = 1,
+        IPv6 = 2,
+        All = 3
+    }
+
+    public static class NetUtils
+    {
+        internal static int RelativeSequenceNumber(int number, int expected)
+        {
+            return (number - expected + NetConstants.MaxSequence + NetConstants.HalfMaxSequence) % NetConstants.MaxSequence - NetConstants.HalfMaxSequence;
+        }
+
+        internal static int GetDividedPacketsCount(int size, int mtu)
+        {
+            return (size / mtu) + (size % mtu == 0 ? 0 : 1);
+        }
+
+        public static void PrintInterfaceInfos()
+        {
+#if !WINRT || UNITY_EDITOR
+            DebugWriteForce(ConsoleColor.Green, "IPv6Support: {0}", Socket.OSSupportsIPv6);
+            try
+            {
+                foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
+                {
+                    foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
+                    {
+                        if (ip.Address.AddressFamily == AddressFamily.InterNetwork ||
+                            ip.Address.AddressFamily == AddressFamily.InterNetworkV6)
+                        {
+                            DebugWriteForce(
+                                ConsoleColor.Green,
+                                "Interface: {0}, Type: {1}, Ip: {2}, OpStatus: {3}",
+                                ni.Name,
+                                ni.NetworkInterfaceType.ToString(),
+                                ip.Address.ToString(),
+                                ni.OperationalStatus.ToString());
+                        }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                DebugWriteForce(ConsoleColor.Red, "Error while getting interface infos: {0}", e.ToString());
+            }
+#endif
+        }
+
+        public static void GetLocalIpList(List<string> targetList, LocalAddrType addrType)
+        {
+            bool ipv4 = (addrType & LocalAddrType.IPv4) == LocalAddrType.IPv4;
+            bool ipv6 = (addrType & LocalAddrType.IPv6) == LocalAddrType.IPv6;
+#if WINRT && !UNITY_EDITOR
+            foreach (HostName localHostName in NetworkInformation.GetHostNames())
+            {
+                if (localHostName.IPInformation != null && 
+                    ((ipv4 && localHostName.Type == HostNameType.Ipv4) ||
+                     (ipv6 && localHostName.Type == HostNameType.Ipv6)))
+                {
+                    targetList.Add(localHostName.ToString());
+                }
+            }
+#else
+            try
+            {
+                foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
+                {
+                    //Skip loopback
+                    if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
+                        continue;
+
+                    var ipProps = ni.GetIPProperties();
+
+                    //Skip address without gateway
+                    if (ipProps.GatewayAddresses.Count == 0)
+                        continue;
+
+                    foreach (UnicastIPAddressInformation ip in ipProps.UnicastAddresses)
+                    {
+                        var address = ip.Address;
+                        if ((ipv4 && address.AddressFamily == AddressFamily.InterNetwork) ||
+                            (ipv6 && address.AddressFamily == AddressFamily.InterNetworkV6))
+                            targetList.Add(address.ToString());
+                    }
+                }
+            }
+            catch
+            {
+                //ignored
+            }
+
+            //Fallback mode (unity android)
+            if (targetList.Count == 0)
+            {
+#if NETCORE
+                var hostTask = Dns.GetHostEntryAsync(Dns.GetHostName());
+                hostTask.Wait();
+                var host = hostTask.Result;
+#else
+                var host = Dns.GetHostEntry(Dns.GetHostName());
+#endif
+                foreach (IPAddress ip in host.AddressList)
+                {
+                    if((ipv4 && ip.AddressFamily == AddressFamily.InterNetwork) ||
+                       (ipv6 && ip.AddressFamily == AddressFamily.InterNetworkV6))
+                        targetList.Add(ip.ToString());
+                }
+            }
+#endif
+            if (targetList.Count == 0)
+            {
+                if(ipv4)
+                    targetList.Add("127.0.0.1");
+                if(ipv6)
+                    targetList.Add("::1");
+            }
+        }
+
+        private static readonly List<string> IpList = new List<string>();
+        public static string GetLocalIp(LocalAddrType addrType)
+        {
+            lock (IpList)
+            {
+                IpList.Clear();
+                GetLocalIpList(IpList, addrType);
+                return IpList.Count == 0 ? string.Empty : IpList[0];
+            }
+        }
+
+        private static readonly object DebugLogLock = new object();
+
+        private static void DebugWriteLogic(ConsoleColor color, string str, params object[] args)
+        {
+            lock (DebugLogLock)
+            {
+
+                if (NetDebug.Logger == null)
+                {
+#if UNITY
+#if !UNITY_4_0
+                    UnityEngine.Debug.LogFormat(str, args);
+#endif
+#elif WINRT
+                    Debug.WriteLine(str, args);
+#else
+                    Console.ForegroundColor = color;
+                    Console.WriteLine(str, args);
+                    Console.ForegroundColor = ConsoleColor.Gray;
+#endif
+                }
+                else
+                {
+                    NetDebug.Logger.WriteNet(color, str, args);
+                }
+            }
+        }
+
+        [Conditional("DEBUG_MESSAGES")]
+        internal static void DebugWrite(string str, params object[] args)
+        {
+            DebugWriteLogic(ConsoleColor.DarkGreen, str, args);
+        }
+
+        [Conditional("DEBUG_MESSAGES")]
+        internal static void DebugWrite(ConsoleColor color, string str, params object[] args)
+        {
+            DebugWriteLogic(color, str, args);
+        }
+
+        [Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")]
+        internal static void DebugWriteForce(ConsoleColor color, string str, params object[] args)
+        {
+            DebugWriteLogic(color, str, args);
+        }
+
+        [Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")]
+        internal static void DebugWriteError(string str, params object[] args)
+        {
+            DebugWriteLogic(ConsoleColor.Red, str, args);
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NetUtils.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 0b27974b2b4714973a8f5a3ea4036677
+timeCreated: 1497976517
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 59 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NtpSyncModule.cs

@@ -0,0 +1,59 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Threading;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    public class NtpSyncModule
+    {
+        public DateTime? SyncedTime { get; private set; }
+        private readonly NetSocket _socket;
+        private readonly NetEndPoint _ntpEndPoint;
+        private readonly ManualResetEvent _waiter = new ManualResetEvent(false);
+
+        public NtpSyncModule(string ntpServer)
+        {
+            _ntpEndPoint = new NetEndPoint(ntpServer, 123);
+            _socket = new NetSocket(OnMessageReceived);
+            _socket.Bind(0, false);
+            SyncedTime = null;
+        }
+
+        private void OnMessageReceived(byte[] data, int length, int errorCode, NetEndPoint remoteEndPoint)
+        {
+            if (errorCode != 0)
+            {
+                _waiter.Set();
+                return;
+            }
+
+            ulong intPart = (ulong)data[40] << 24 | (ulong)data[41] << 16 | (ulong)data[42] << 8 | (ulong)data[43];
+            ulong fractPart = (ulong)data[44] << 24 | (ulong)data[45] << 16 | (ulong)data[46] << 8 | (ulong)data[47];
+
+            var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
+            SyncedTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);
+
+            _waiter.Set();
+        }
+
+        public void GetNetworkTime()
+        {
+            if (SyncedTime != null)
+                return;
+
+            var ntpData = new byte[48];
+            //LeapIndicator = 0 (no warning)
+            //VersionNum = 3
+            //Mode = 3 (Client Mode)
+            ntpData[0] = 0x1B;
+
+            //send
+            int errorCode = 0;
+            _socket.SendTo(ntpData, 0, ntpData.Length, _ntpEndPoint, ref errorCode);
+
+            if(errorCode == 0)
+                _waiter.WaitOne(1000);
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/NtpSyncModule.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 91e481bf657774228854ba5923470b26
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 375 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/ReliableChannel.cs

@@ -0,0 +1,375 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    internal sealed class ReliableChannel
+    {
+        private class PendingPacket
+        {
+            public NetPacket Packet;
+            public DateTime? TimeStamp;
+
+            public NetPacket GetAndClear()
+            {
+                var packet = Packet;
+                Packet = null;
+                TimeStamp = null;
+                return packet;
+            }
+        }
+
+        private readonly Queue<NetPacket> _outgoingPackets;
+        private readonly bool[] _outgoingAcks;               //for send acks
+        private readonly PendingPacket[] _pendingPackets;    //for unacked packets and duplicates
+        private readonly NetPacket[] _receivedPackets;       //for order
+        private readonly bool[] _earlyReceived;              //for unordered
+
+        private int _localSeqence;
+        private int _remoteSequence;
+        private int _localWindowStart;
+        private int _remoteWindowStart;
+
+        private readonly NetPeer _peer;
+        private bool _mustSendAcks;
+
+        private readonly bool _ordered;
+        private readonly int _windowSize;
+        private const int BitsInByte = 8;
+
+        private int _queueIndex;
+
+        public int PacketsInQueue
+        {
+            get { return _outgoingPackets.Count; }
+        }
+
+        public ReliableChannel(NetPeer peer, bool ordered, int windowSize)
+        {
+            _windowSize = windowSize;
+            _peer = peer;
+            _ordered = ordered;
+
+            _outgoingPackets = new Queue<NetPacket>(_windowSize);
+
+            _outgoingAcks = new bool[_windowSize];
+            _pendingPackets = new PendingPacket[_windowSize];
+            for (int i = 0; i < _pendingPackets.Length; i++)
+            {
+                _pendingPackets[i] = new PendingPacket();
+            }
+
+            if (_ordered)
+                _receivedPackets = new NetPacket[_windowSize];
+            else
+                _earlyReceived = new bool[_windowSize];
+
+            _localWindowStart = 0;
+            _localSeqence = 0;
+            _remoteSequence = 0;
+            _remoteWindowStart = 0;
+        }
+
+        //ProcessAck in packet
+        public void ProcessAck(NetPacket packet)
+        {
+            int validPacketSize = (_windowSize - 1) / BitsInByte + 1 + NetConstants.SequencedHeaderSize;
+            if (packet.Size != validPacketSize)
+            {
+                NetUtils.DebugWrite("[PA]Invalid acks packet size");
+                return;
+            }
+
+            ushort ackWindowStart = packet.Sequence;
+            if (ackWindowStart > NetConstants.MaxSequence)
+            {
+                NetUtils.DebugWrite("[PA]Bad window start");
+                return;
+            }
+
+            //check relevance
+            if (NetUtils.RelativeSequenceNumber(ackWindowStart, _localWindowStart) <= -_windowSize)
+            {
+                NetUtils.DebugWrite("[PA]Old acks");
+                return;
+            }
+
+            byte[] acksData = packet.RawData;
+            NetUtils.DebugWrite("[PA]AcksStart: {0}", ackWindowStart);
+            int startByte = NetConstants.SequencedHeaderSize;
+
+            Monitor.Enter(_pendingPackets);
+            for (int i = 0; i < _windowSize; i++)
+            {
+                int ackSequence = (ackWindowStart + i) % NetConstants.MaxSequence;
+                if (NetUtils.RelativeSequenceNumber(ackSequence, _localWindowStart) < 0)
+                {
+                    //NetUtils.DebugWrite(ConsoleColor.Cyan, "[PA] SKIP OLD: " + ackSequence);
+                    //Skip old ack
+                    continue;
+                }
+
+                int currentByte = startByte + i / BitsInByte;
+                int currentBit = i % BitsInByte;
+
+                if ((acksData[currentByte] & (1 << currentBit)) == 0)
+                {
+                    //NetUtils.DebugWrite(ConsoleColor.Cyan, "[PA] SKIP FALSE: " + ackSequence);
+                    //Skip false ack
+                    continue;
+                }
+
+                if (ackSequence == _localWindowStart)
+                {
+                    //Move window
+                    _localWindowStart = (_localWindowStart + 1) % NetConstants.MaxSequence;
+                }
+
+                NetPacket removed = _pendingPackets[ackSequence % _windowSize].GetAndClear();
+                if (removed != null)
+                {
+                    _peer.Recycle(removed);
+
+                    NetUtils.DebugWrite("[PA]Removing reliableInOrder ack: {0} - true", ackSequence);
+                }
+                else
+                {
+                    NetUtils.DebugWrite("[PA]Removing reliableInOrder ack: {0} - false", ackSequence);
+                }
+            }
+            Monitor.Exit(_pendingPackets);
+        }
+
+        public void AddToQueue(NetPacket packet)
+        {
+            lock (_outgoingPackets)
+            {
+                _outgoingPackets.Enqueue(packet);
+            }
+        }
+
+        private void ProcessQueuedPackets()
+        {
+            //get packets from queue
+            while (_outgoingPackets.Count > 0)
+            {
+                int relate = NetUtils.RelativeSequenceNumber(_localSeqence, _localWindowStart);
+                if (relate < _windowSize)
+                {
+                    NetPacket packet;
+                    lock (_outgoingPackets)
+                    {
+                        packet = _outgoingPackets.Dequeue();
+                    }
+                    packet.Sequence = (ushort)_localSeqence;
+                    _pendingPackets[_localSeqence % _windowSize].Packet = packet;
+                    _localSeqence = (_localSeqence + 1) % NetConstants.MaxSequence;
+                }
+                else //Queue filled
+                {
+                    break;
+                }
+            }
+        }
+
+        public bool SendNextPacket()
+        {
+            //check sending acks
+            DateTime currentTime = DateTime.UtcNow;
+
+            Monitor.Enter(_pendingPackets);
+            ProcessQueuedPackets();
+
+            //send
+            PendingPacket currentPacket;
+            bool packetFound = false;
+            int startQueueIndex = _queueIndex;
+            do
+            {
+                currentPacket = _pendingPackets[_queueIndex];
+                if (currentPacket.Packet != null)
+                {
+                    //check send time
+                    if(currentPacket.TimeStamp.HasValue)
+                    {
+                        double packetHoldTime = (currentTime - currentPacket.TimeStamp.Value).TotalMilliseconds;
+                        if (packetHoldTime > _peer.ResendDelay)
+                        {
+                            NetUtils.DebugWrite("[RC]Resend: {0} > {1}", (int)packetHoldTime, _peer.ResendDelay);
+                            packetFound = true;
+                        }
+                    }
+                    else //Never sended
+                    {
+                        packetFound = true;
+                    }
+                }
+
+                _queueIndex = (_queueIndex + 1) % _windowSize;
+            } while (!packetFound && _queueIndex != startQueueIndex);
+
+            if (packetFound)
+            {
+                currentPacket.TimeStamp = DateTime.UtcNow;
+                _peer.SendRawData(currentPacket.Packet);
+                NetUtils.DebugWrite("[RR]Sended");
+            }
+            Monitor.Exit(_pendingPackets);
+            return packetFound;
+        }
+
+        public void SendAcks()
+        {
+            if (!_mustSendAcks)
+                return;
+            _mustSendAcks = false;
+
+            NetUtils.DebugWrite("[RR]SendAcks");
+
+            //Init packet
+            int bytesCount = (_windowSize - 1) / BitsInByte + 1;
+            PacketProperty property = _ordered ? PacketProperty.AckReliableOrdered : PacketProperty.AckReliable;
+            var acksPacket = _peer.GetPacketFromPool(property, bytesCount);
+
+            //For quick access
+            byte[] data = acksPacket.RawData; //window start + acks size
+
+            //Put window start
+            Monitor.Enter(_outgoingAcks);
+            acksPacket.Sequence = (ushort)_remoteWindowStart;
+
+            //Put acks
+            int startAckIndex = _remoteWindowStart % _windowSize;
+            int currentAckIndex = startAckIndex;
+            int currentBit = 0;
+            int currentByte = NetConstants.SequencedHeaderSize;
+            do 
+            {
+                if (_outgoingAcks[currentAckIndex])
+                {
+                    data[currentByte] |= (byte)(1 << currentBit);
+                }
+
+                currentBit++;
+                if (currentBit == BitsInByte)
+                {
+                    currentByte++;
+                    currentBit = 0;
+                }
+                currentAckIndex = (currentAckIndex + 1) % _windowSize;
+            } while (currentAckIndex != startAckIndex);
+            Monitor.Exit(_outgoingAcks);
+
+            _peer.SendRawData(acksPacket);
+            _peer.Recycle(acksPacket);
+        }
+
+        //Process incoming packet
+        public void ProcessPacket(NetPacket packet)
+        {
+            if (packet.Sequence >= NetConstants.MaxSequence)
+            {
+                NetUtils.DebugWrite("[RR]Bad sequence");
+                return;
+            }
+
+            int relate = NetUtils.RelativeSequenceNumber(packet.Sequence, _remoteWindowStart);
+            int relateSeq = NetUtils.RelativeSequenceNumber(packet.Sequence, _remoteSequence);
+
+            if (relateSeq > _windowSize)
+            {
+                NetUtils.DebugWrite("[RR]Bad sequence");
+                return;
+            }
+
+            //Drop bad packets
+            if(relate < 0)
+            {
+                //Too old packet doesn't ack
+                NetUtils.DebugWrite("[RR]ReliableInOrder too old");
+                return;
+            }
+            if (relate >= _windowSize * 2)
+            {
+                //Some very new packet
+                NetUtils.DebugWrite("[RR]ReliableInOrder too new");
+                return;
+            }
+
+            //If very new - move window
+            Monitor.Enter(_outgoingAcks);
+            if (relate >= _windowSize)
+            {
+                //New window position
+                int newWindowStart = (_remoteWindowStart + relate - _windowSize + 1) % NetConstants.MaxSequence;
+
+                //Clean old data
+                while (_remoteWindowStart != newWindowStart)
+                {
+                    _outgoingAcks[_remoteWindowStart % _windowSize] = false;
+                    _remoteWindowStart = (_remoteWindowStart + 1) % NetConstants.MaxSequence;
+                }
+            }
+
+            //Final stage - process valid packet
+            //trigger acks send
+            _mustSendAcks = true;
+
+            if (_outgoingAcks[packet.Sequence % _windowSize])
+            {
+                NetUtils.DebugWrite("[RR]ReliableInOrder duplicate");
+                Monitor.Exit(_outgoingAcks);
+                return;
+            }
+
+            //save ack
+            _outgoingAcks[packet.Sequence % _windowSize] = true;
+            Monitor.Exit(_outgoingAcks);
+
+            //detailed check
+            if (packet.Sequence == _remoteSequence)
+            {
+                NetUtils.DebugWrite("[RR]ReliableInOrder packet succes");
+                _peer.AddIncomingPacket(packet);
+                _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence;
+
+                if (_ordered)
+                {
+                    NetPacket p;
+                    while ( (p = _receivedPackets[_remoteSequence % _windowSize]) != null)
+                    {
+                        //process holded packet
+                        _receivedPackets[_remoteSequence % _windowSize] = null;
+                        _peer.AddIncomingPacket(p);
+                        _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence;
+                    }
+                }
+                else
+                {
+                    while (_earlyReceived[_remoteSequence % _windowSize])
+                    {
+                        //process early packet
+                        _earlyReceived[_remoteSequence % _windowSize] = false;
+                        _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence;
+                    }
+                }
+
+                return;
+            }
+
+            //holded packet
+            if (_ordered)
+            {
+                _receivedPackets[packet.Sequence % _windowSize] = packet;
+            }
+            else
+            {
+                _earlyReceived[packet.Sequence % _windowSize] = true;
+                _peer.AddIncomingPacket(packet);
+            }
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/ReliableChannel.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 516bd901eb62f4bb391ef541f9effdec
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 4 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/RemoteFix

@@ -0,0 +1,4 @@
+for file in *.cs; do
+  echo "#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1" | cat - $file > tempfile && mv tempfile $file
+  echo '#endif' >> "$file"
+done

+ 8 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/RemoteFix.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a9f1f24482ff84a25a1188dd49ec4d86
+timeCreated: 1498001160
+licenseType: Store
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 53 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/SequencedChannel.cs

@@ -0,0 +1,53 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System.Collections.Generic;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    internal sealed class SequencedChannel
+    {
+        private ushort _localSequence;
+        private ushort _remoteSequence;
+        private readonly Queue<NetPacket> _outgoingPackets;
+        private readonly NetPeer _peer;
+
+        public SequencedChannel(NetPeer peer)
+        {
+            _outgoingPackets = new Queue<NetPacket>();
+            _peer = peer;
+        }
+
+        public void AddToQueue(NetPacket packet)
+        {
+            lock (_outgoingPackets)
+            {
+                _outgoingPackets.Enqueue(packet);
+            }
+        }
+
+        public bool SendNextPacket()
+        {
+            NetPacket packet;
+            lock (_outgoingPackets)
+            {
+                if (_outgoingPackets.Count == 0)
+                    return false;
+                packet = _outgoingPackets.Dequeue();
+            }
+            _localSequence++;
+            packet.Sequence = _localSequence;
+            _peer.SendRawData(packet);
+            _peer.Recycle(packet);
+            return true;
+        }
+
+        public void ProcessPacket(NetPacket packet)
+        {
+            if (NetUtils.RelativeSequenceNumber(packet.Sequence, _remoteSequence) > 0)
+            {
+                _remoteSequence = packet.Sequence;
+                _peer.AddIncomingPacket(packet);
+            }
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/SequencedChannel.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 84bbbc6c6e45c4287917fc944650e4af
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 40 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/SimpleChannel.cs

@@ -0,0 +1,40 @@
+#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA_8_1
+using System.Collections.Generic;
+
+namespace FlyingWormConsole3.LiteNetLib
+{
+    internal sealed class SimpleChannel
+    {
+        private readonly Queue<NetPacket> _outgoingPackets;
+        private readonly NetPeer _peer;
+
+        public SimpleChannel(NetPeer peer)
+        {
+            _outgoingPackets = new Queue<NetPacket>();
+            _peer = peer;
+        }
+
+        public void AddToQueue(NetPacket packet)
+        {
+            lock (_outgoingPackets)
+            {
+                _outgoingPackets.Enqueue(packet);
+            }
+        }
+
+        public bool SendNextPacket()
+        {
+            NetPacket packet;
+            lock (_outgoingPackets)
+            {
+                if (_outgoingPackets.Count == 0)
+                    return false;
+                packet = _outgoingPackets.Dequeue();
+            }
+            _peer.SendRawData(packet);
+            _peer.Recycle(packet);
+            return true;
+        }
+    }
+}
+#endif

+ 12 - 0
Unity/Assets/ConsolePro/Remote/LiteNetLib/SimpleChannel.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 6b4cdb673a0094d438a802844a8cc794
+timeCreated: 1497976518
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 1 - 1
Unity/Assets/Editor/ExcelExporterEditor/ExcelExporterEditor.cs

@@ -149,7 +149,7 @@ public class ExcelExporterEditor : EditorWindow
 				
 				string fieldName = GetCellString(sheet, 3, i);
 
-				if (fieldName == "Id")
+				if (fieldName == "Id" || fieldName == "_id")
 				{
 					continue;
 				}

+ 1 - 1
Unity/Assets/Res/Config/UnitConfig.txt

@@ -1 +1 @@
-{"Id":1001,"Name":"米克尔","Desc":"带有强力攻击技能","Position":1,"Height":178,"Weight":68}
+{"_id":1001,"Name":"米克尔","Desc":"带有强力攻击技能","Position":1,"Height":178,"Weight":68}

+ 2 - 0
Unity/Assets/Resources/Config.prefab

@@ -54,3 +54,5 @@ MonoBehaviour:
   data:
   - key: BuffConfig
     gameObject: {fileID: 4900000, guid: 0f3540faabf2b7e4c83e81cb7ba6a096, type: 3}
+  - key: UnitConfig
+    gameObject: {fileID: 4900000, guid: c245c7dda13b3ad43b2278925e922899, type: 3}

+ 3 - 0
Unity/Unity.Editor.csproj

@@ -134,6 +134,9 @@
     <Reference Include="NPOI.OOXML">
       <HintPath>Assets\Plugins\npoi\NPOI.OOXML.dll</HintPath>
     </Reference>
+    <Reference Include="ConsolePro.Editor">
+      <HintPath>Assets\ConsolePro\Editor\ConsolePro.Editor.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="Unity.Plugins.csproj">

+ 23 - 0
Unity/Unity.csproj

@@ -100,6 +100,29 @@
     </ProjectReference>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Assets\ConsolePro\ConsoleProDebug.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\ConsoleProRemoteServer.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\FastBitConverter.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\INetEventListener.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NatPunchModule.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetConstants.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetDataReader.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetDataWriter.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetDebug.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetEndPoint.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetManager.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetPacket.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetPacketPool.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetPeer.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetPeerCollection.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetSerializer.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetSocket.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetThread.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NetUtils.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\NtpSyncModule.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\ReliableChannel.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\SequencedChannel.cs" />
+    <Compile Include="Assets\ConsolePro\Remote\LiteNetLib\SimpleChannel.cs" />
     <Compile Include="Assets\ILRuntime\ILRuntime\CLR\Method\CLRMethod.cs" />
     <Compile Include="Assets\ILRuntime\ILRuntime\CLR\Method\ExceptionHandler.cs" />
     <Compile Include="Assets\ILRuntime\ILRuntime\CLR\Method\ILMethod.cs" />