summaryrefslogtreecommitdiff
path: root/Runtime/Network
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Network')
-rw-r--r--Runtime/Network/BitStreamPacker.cpp556
-rw-r--r--Runtime/Network/BitStreamPacker.h76
-rw-r--r--Runtime/Network/DummyNetwork.cpp736
-rw-r--r--Runtime/Network/MasterServerInterface.cpp692
-rw-r--r--Runtime/Network/MasterServerInterface.h88
-rw-r--r--Runtime/Network/MulticastSocket.cpp224
-rw-r--r--Runtime/Network/MulticastSocket.h59
-rw-r--r--Runtime/Network/NetworkEnums.h196
-rw-r--r--Runtime/Network/NetworkManager.cpp2825
-rw-r--r--Runtime/Network/NetworkManager.h358
-rw-r--r--Runtime/Network/NetworkStubs.h77
-rw-r--r--Runtime/Network/NetworkUtility.cpp1076
-rw-r--r--Runtime/Network/NetworkUtility.h74
-rw-r--r--Runtime/Network/NetworkView.cpp733
-rw-r--r--Runtime/Network/NetworkView.h116
-rw-r--r--Runtime/Network/NetworkViewID.cpp193
-rw-r--r--Runtime/Network/NetworkViewID.h56
-rw-r--r--Runtime/Network/NetworkViewIDAllocator.cpp130
-rw-r--r--Runtime/Network/NetworkViewIDAllocator.h58
-rw-r--r--Runtime/Network/OnlineServices.h10
-rw-r--r--Runtime/Network/PackMonoRPC.cpp488
-rw-r--r--Runtime/Network/PackMonoRPC.h19
-rw-r--r--Runtime/Network/PackStateSpecialized.cpp124
-rw-r--r--Runtime/Network/PackStateSpecialized.h23
-rw-r--r--Runtime/Network/PlayerCommunicator/EditorConnection.cpp335
-rw-r--r--Runtime/Network/PlayerCommunicator/EditorConnection.h77
-rw-r--r--Runtime/Network/PlayerCommunicator/GeneralConnection.cpp666
-rw-r--r--Runtime/Network/PlayerCommunicator/GeneralConnection.h266
-rw-r--r--Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h56
-rw-r--r--Runtime/Network/PlayerCommunicator/PlayerConnection.cpp535
-rw-r--r--Runtime/Network/PlayerCommunicator/PlayerConnection.h111
-rw-r--r--Runtime/Network/ServerSocket.cpp117
-rw-r--r--Runtime/Network/ServerSocket.h31
-rw-r--r--Runtime/Network/SocketConsts.h89
-rw-r--r--Runtime/Network/SocketStreams.cpp411
-rw-r--r--Runtime/Network/SocketStreams.h94
-rw-r--r--Runtime/Network/SocketUtils.h56
-rw-r--r--Runtime/Network/Sockets.cpp384
-rw-r--r--Runtime/Network/Sockets.h92
39 files changed, 12307 insertions, 0 deletions
diff --git a/Runtime/Network/BitStreamPacker.cpp b/Runtime/Network/BitStreamPacker.cpp
new file mode 100644
index 0000000..766158e
--- /dev/null
+++ b/Runtime/Network/BitStreamPacker.cpp
@@ -0,0 +1,556 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+#include "BitStreamPacker.h"
+
+#if ENABLE_NETWORK
+#include "Runtime/Math/Quaternion.h"
+#include "Runtime/Math/Vector3.h"
+#include "External/RakNet/builds/include/StringCompressor.h"
+#include "NetworkViewID.h"
+
+using namespace std;
+
+// WARNING: current impl did quite a few unaligned accesses, so we change it to use memcpy (no align restrictions)
+
+template <typename T> static inline void ReadValueUnaligned(const UInt8* data, T* val)
+{
+ ::memcpy(val, data, sizeof(T));
+}
+template <typename T> static inline void WriteValueUnaligned(UInt8* data, const T& val)
+{
+ ::memcpy(data, &val, sizeof(T));
+}
+
+
+void BitstreamPacker::ReadPackState (Quaternionf& t)
+{
+ if (m_DeltaReadPos + sizeof(Quaternionf) <= m_DeltaReadSize)
+ {
+ ReadValueUnaligned(m_ReadDeltaData + m_DeltaReadPos, &t);
+ m_DeltaReadPos += sizeof(Quaternionf);
+ }
+ else
+ {
+ t = Quaternionf (0,0,0,1);
+ m_DeltaReadPos += sizeof(Quaternionf);
+ }
+}
+
+void BitstreamPacker::ReadPackState (Vector3f& t)
+{
+ if (m_DeltaReadPos + sizeof(Vector3f) <= m_DeltaReadSize)
+ {
+ ReadValueUnaligned(m_ReadDeltaData + m_DeltaReadPos, &t);
+ m_DeltaReadPos += sizeof(Vector3f);
+ }
+ else
+ {
+ t = Vector3f(0,0,0);
+ m_DeltaReadPos += sizeof(Vector3f);
+ }
+}
+
+void BitstreamPacker::WritePackState (Vector3f& t)
+{
+ std::vector<UInt8>& data = *m_WriteDeltaData;
+ if (m_DeltaWritePos + sizeof(Vector3f) > data.size())
+ data.resize(m_DeltaWritePos + sizeof(Vector3f));
+
+ WriteValueUnaligned(&data[m_DeltaWritePos], t);
+ m_DeltaWritePos += sizeof(Vector3f);
+}
+
+void BitstreamPacker::WritePackState (Quaternionf& t)
+{
+ std::vector<UInt8>& data = *m_WriteDeltaData;
+ if (m_DeltaWritePos + sizeof(Quaternionf) > data.size())
+ data.resize(m_DeltaWritePos + sizeof(Quaternionf));
+
+ WriteValueUnaligned(&data[m_DeltaWritePos], t);
+ m_DeltaWritePos += sizeof(Quaternionf);
+}
+
+void BitstreamPacker::WritePackState (string& t)
+{
+ std::vector<UInt8>& data = *m_WriteDeltaData;
+ if (m_DeltaWritePos + t.size() > data.size())
+ data.resize(m_DeltaWritePos + t.size() + sizeof(SInt32));
+
+ WriteValueUnaligned<SInt32>(&data[m_DeltaWritePos], t.size());
+ m_DeltaWritePos += sizeof(SInt32);
+
+ memcpy(&data[m_DeltaWritePos], t.c_str(), t.size());
+ m_DeltaWritePos += t.size();
+}
+
+void BitstreamPacker::ReadPackState (string& t)
+{
+ t = string();
+ if (m_DeltaReadPos + sizeof(SInt32) <= m_DeltaReadSize)
+ {
+ SInt32 size = 0; ReadValueUnaligned<SInt32>(m_ReadDeltaData + m_DeltaReadPos, &size);
+ m_DeltaReadPos += sizeof(SInt32);
+
+ if (m_DeltaReadPos + size <= m_DeltaReadSize)
+ {
+ t.assign((char*)m_ReadDeltaData + m_DeltaReadPos, (char*)m_ReadDeltaData + m_DeltaReadPos + size);
+ }
+ m_DeltaReadPos += size;
+ }
+}
+
+void BitstreamPacker::WritePackState (char* t, int& length)
+{
+ std::vector<UInt8>& data = *m_WriteDeltaData;
+ if (m_DeltaWritePos + length > data.size())
+ data.resize(m_DeltaWritePos + length + sizeof(SInt32));
+
+ WriteValueUnaligned<SInt32>(&data[m_DeltaWritePos], length);
+ m_DeltaWritePos += sizeof(SInt32);
+ memcpy(&data[m_DeltaWritePos], t, length);
+ m_DeltaWritePos += length;
+}
+
+void BitstreamPacker::ReadPackState (char*& t, int& length)
+{
+ if (m_DeltaReadPos + sizeof(SInt32) <= m_DeltaReadSize)
+ {
+ SInt32 size = 0; ReadValueUnaligned<SInt32>(m_ReadDeltaData + m_DeltaReadPos, &size);
+ m_DeltaReadPos += sizeof(SInt32);
+
+ // Allocate new char array with correct size
+ t = new char[size];
+ length = size;
+
+ // Again check data bounds
+ if (m_DeltaReadPos + size <= m_DeltaReadSize)
+ {
+ // Assign char pointer to the correct location in memory
+
+ // WhatAFuckingHell is that
+ // should be memcpy probably ;-)
+ t = reinterpret_cast<char*>(m_ReadDeltaData + m_DeltaReadPos);
+ }
+ m_DeltaReadPos += size;
+ }
+}
+
+void BitstreamPacker::ReadPackState (NetworkViewID& t)
+{
+ if (m_DeltaReadPos + sizeof(NetworkViewID) <= m_DeltaReadSize) \
+ {
+ ReadValueUnaligned(m_ReadDeltaData + m_DeltaReadPos, &t);
+ m_DeltaReadPos += sizeof(NetworkViewID);
+ }
+ else
+ {
+ t = NetworkViewID::GetUnassignedViewID();
+ m_DeltaReadPos += sizeof(NetworkViewID);
+ }
+}
+
+void BitstreamPacker::WritePackState (NetworkViewID& t)
+{
+ std::vector<UInt8>& data = *m_WriteDeltaData;
+ if (m_DeltaWritePos + sizeof(NetworkViewID) > data.size())
+ data.resize(m_DeltaWritePos + sizeof(NetworkViewID));
+ WriteValueUnaligned(&data[m_DeltaWritePos], t);
+ m_DeltaWritePos += sizeof(NetworkViewID);
+}
+
+#define READ_WRITE_PACKSTATE(TYPE) \
+void BitstreamPacker::ReadPackState (TYPE& t) \
+{ \
+ if (m_DeltaReadPos + sizeof(TYPE) <= m_DeltaReadSize) \
+ { \
+ ReadValueUnaligned<TYPE>(m_ReadDeltaData + m_DeltaReadPos, &t); \
+ m_DeltaReadPos += sizeof(TYPE); \
+ } \
+ else \
+ { \
+ t = TYPE(); \
+ m_DeltaReadPos += sizeof(TYPE); \
+ } \
+} \
+void BitstreamPacker::WritePackState (TYPE t) \
+{ \
+ std::vector<UInt8>& data = *m_WriteDeltaData; \
+ if (m_DeltaWritePos + sizeof(TYPE) > data.size()) \
+ data.resize(m_DeltaWritePos + sizeof(TYPE)); \
+ WriteValueUnaligned<TYPE>(&data[m_DeltaWritePos], t); \
+ m_DeltaWritePos += sizeof(TYPE); \
+}
+
+READ_WRITE_PACKSTATE(UInt32)
+READ_WRITE_PACKSTATE(float)
+READ_WRITE_PACKSTATE(short)
+READ_WRITE_PACKSTATE(UInt8)
+READ_WRITE_PACKSTATE(bool)
+
+void BitstreamPacker::Serialize (NetworkViewID& value)
+{
+ if (m_IsReading)
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ NetworkViewID oldData;
+ ReadPackState(oldData);
+
+ bool readValue = false;
+ m_NoOutOfBounds &= m_BitStream->Read(readValue);
+ if (readValue)
+ m_NoOutOfBounds &= value.Read(*m_BitStream);
+ else
+ value = oldData;
+
+ WritePackState (value);
+ }
+ else
+ {
+ m_NoOutOfBounds &= value.Read(*m_BitStream);
+ }
+ }
+ else
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ NetworkViewID oldData;
+ ReadPackState(oldData);
+
+ if (value != oldData)
+ {
+ m_BitStream->Write1();
+ value.Write(*m_BitStream);
+ WritePackState (value);
+ m_IsDifferent |= true;
+ }
+ else
+ {
+ m_BitStream->Write0();
+ WritePackState (oldData);
+ }
+
+ }
+ else
+ {
+ value.Write(*m_BitStream);
+ m_IsDifferent |= true;
+ }
+ }
+
+}
+
+
+void BitstreamPacker::Serialize (float& value, float maxDelta)
+{
+ if (m_IsReading)
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ float oldData;
+ ReadPackState(oldData);
+
+ bool readValue = false;
+ m_NoOutOfBounds &= m_BitStream->Read(readValue);
+ if (readValue)
+ m_NoOutOfBounds &= m_BitStream->Read(value);
+ else
+ value = oldData;
+
+ WritePackState (value);
+ }
+ else
+ {
+ m_NoOutOfBounds &= m_BitStream->Read(value);
+ }
+ }
+ else
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ float oldData;
+ ReadPackState(oldData);
+
+ if (!CompareApproximately(value, oldData, maxDelta))
+ {
+ m_BitStream->Write1();
+ m_BitStream->Write(value);
+ WritePackState (value);
+ m_IsDifferent |= true;
+ }
+ else
+ {
+ m_BitStream->Write0();
+ WritePackState (oldData);
+ }
+
+ }
+ else
+ {
+ m_BitStream->Write(value);
+ m_IsDifferent |= true;
+ }
+ }
+}
+
+void BitstreamPacker::Serialize (bool& value)
+{
+ if (m_IsReading)
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ bool oldData;
+ ReadPackState(oldData);
+ m_NoOutOfBounds &= m_BitStream->Read(value);
+ WritePackState (value);
+ }
+ else
+ {
+ m_NoOutOfBounds &= m_BitStream->Read(value);
+ }
+ }
+ else
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ bool oldData;
+ ReadPackState(oldData);
+
+ if (value != oldData)
+ {
+ m_BitStream->Write(value);
+ WritePackState (value);
+ m_IsDifferent |= true;
+ }
+ else
+ {
+ m_BitStream->Write(value);
+ WritePackState (oldData);
+ }
+ }
+ else
+ {
+ m_BitStream->Write(value);
+ m_IsDifferent |= true;
+ }
+ }
+}
+
+#define SERIALIZE(TYPE) void BitstreamPacker::Serialize (TYPE& value) {\
+ if (m_IsReading)\
+ {\
+ if (m_WriteDeltaData != NULL)\
+ {\
+ TYPE oldData;\
+ ReadPackState(oldData);\
+ \
+ bool readValue = false;\
+ m_NoOutOfBounds &= m_BitStream->Read(readValue);\
+ if (readValue)\
+ m_NoOutOfBounds &= m_BitStream->Read(value);\
+ else\
+ value = oldData;\
+ \
+ WritePackState (value);\
+ }\
+ else\
+ {\
+ m_NoOutOfBounds &= m_BitStream->Read(value);\
+ }\
+ }\
+ else\
+ {\
+ if (m_WriteDeltaData != NULL)\
+ {\
+ TYPE oldData;\
+ ReadPackState(oldData);\
+ \
+ if (value != oldData)\
+ {\
+ m_BitStream->Write1();\
+ m_BitStream->Write(value);\
+ WritePackState (value);\
+ m_IsDifferent |= true;\
+ }\
+ else\
+ {\
+ m_BitStream->Write0();\
+ WritePackState (oldData);\
+ }\
+ }\
+ else\
+ {\
+ m_BitStream->Write(value);\
+ m_IsDifferent |= true;\
+ }\
+ }\
+}
+
+SERIALIZE(UInt32)
+SERIALIZE(short)
+SERIALIZE(UInt8)
+
+
+void BitstreamPacker::Serialize (Vector3f& value, float maxDelta)
+{
+ Serialize(value.x, maxDelta);
+ Serialize(value.y, maxDelta);
+ Serialize(value.z, maxDelta);
+}
+
+void BitstreamPacker::Serialize (Quaternionf& value, float maxDelta)
+{
+ Serialize(value.x, maxDelta);
+ Serialize(value.y, maxDelta);
+ Serialize(value.z, maxDelta);
+ Serialize(value.w, maxDelta);
+}
+
+void BitstreamPacker::Serialize (std::string& value)
+{
+ if (m_IsReading)
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ std::string oldData;
+ ReadPackState(oldData);
+
+ bool readValue = false;
+ m_BitStream->Read(readValue);
+ if (readValue)
+ {
+ char rawOut[4096];
+ if (StringCompressor::Instance()->DecodeString(rawOut, 4096, m_BitStream))
+ {
+ value = rawOut;
+ }
+ else
+ {
+ value = oldData;
+ }
+ }
+ else
+ value = oldData;
+
+ WritePackState (value);
+ }
+ else
+ {
+ char rawOut[4096];
+ if (StringCompressor::Instance()->DecodeString(rawOut, 4096, m_BitStream))
+ {
+ value = rawOut;
+ }
+ else
+ value = string();
+ }
+ }
+ else
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ string oldData;
+ ReadPackState(oldData);
+
+ if (value != oldData)
+ {
+ m_BitStream->Write1();
+ StringCompressor::Instance()->EncodeString(value.c_str(), 4096, m_BitStream);
+ WritePackState (value);
+ m_IsDifferent |= true;
+ }
+ else
+ {
+ m_BitStream->Write0();
+ }
+ WritePackState (value);
+ }
+ else
+ {
+ m_BitStream->Write(value);
+ m_IsDifferent |= true;
+ }
+ }
+}
+
+void BitstreamPacker::Serialize (char* value, int& valueLength)
+{
+ if (m_IsReading)
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ char* oldData = NULL;
+ ReadPackState(oldData, valueLength);
+
+ // Check if this contains a new state or if we should reuse the old one
+ bool readValue = false;
+ m_BitStream->Read(readValue);
+ if (readValue)
+ m_NoOutOfBounds &= m_BitStream->Read(value, valueLength);
+ else
+ value = oldData;
+ WritePackState (value, valueLength);
+ if (oldData != NULL)
+ delete[] oldData;
+ }
+ else
+ {
+ m_NoOutOfBounds &= m_BitStream->Read(value, valueLength);
+ }
+ }
+ else
+ {
+ if (m_WriteDeltaData != NULL)
+ {
+ char* oldData = NULL;
+ ReadPackState(oldData, valueLength);
+
+ // If the state has changed, then pack it, if not then reuse old state
+ if (strcmp(value,oldData) != 0)
+ {
+ m_BitStream->Write1(); // New state
+ m_BitStream->Write(value, valueLength);
+ WritePackState (value, valueLength);
+ m_IsDifferent |= true;
+ }
+ else
+ {
+ m_BitStream->Write0(); // No change
+ WritePackState (oldData, valueLength);
+ }
+ if (oldData != NULL)
+ delete[] oldData;
+ }
+ else
+ {
+ m_BitStream->Write(value, valueLength);
+ m_IsDifferent |= true;
+ }
+ }
+}
+
+
+BitstreamPacker::BitstreamPacker (RakNet::BitStream& stream, std::vector<UInt8>* delta, UInt8* readData, int readSize, bool reading)
+{
+ Init(stream, delta, readData, readSize, reading);
+}
+
+BitstreamPacker::BitstreamPacker (RakNet::BitStream& stream, bool reading)
+{
+ Init(stream, NULL, NULL, 0, reading);
+}
+
+void BitstreamPacker::Init(RakNet::BitStream& stream, std::vector<UInt8>* delta, UInt8* readData, int readSize, bool reading)
+{
+ m_BitStream = &stream;
+ m_DeltaReadPos = 0;
+ m_DeltaWritePos = 0;
+ m_WriteDeltaData = delta;
+ m_DeltaReadSize = readSize;
+ m_ReadDeltaData = readData;
+ m_IsDifferent = false;
+ m_IsReading = reading;
+ m_NoOutOfBounds = true;
+}
+
+#endif
diff --git a/Runtime/Network/BitStreamPacker.h b/Runtime/Network/BitStreamPacker.h
new file mode 100644
index 0000000..a437d88
--- /dev/null
+++ b/Runtime/Network/BitStreamPacker.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#if ENABLE_NETWORK
+#include "External/RakNet/builds/include/BitStream.h"
+class Vector3f;
+class Quaternionf;
+struct NetworkViewID;
+
+
+class BitstreamPacker
+{
+ RakNet::BitStream* m_BitStream;
+
+ int m_DeltaReadPos;
+ UInt8* m_ReadDeltaData;
+ int m_DeltaReadSize;
+
+ std::vector<UInt8>* m_WriteDeltaData;
+ int m_DeltaWritePos;
+
+ bool m_IsDifferent;
+ bool m_IsReading;
+ bool m_NoOutOfBounds;
+
+ void Init(RakNet::BitStream& stream, std::vector<UInt8>* delta, UInt8* readDeltaData, int readDeltaSize, bool reading);
+
+ public:
+
+ BitstreamPacker (RakNet::BitStream& stream, std::vector<UInt8>* delta, UInt8* readDeltaData, int readDeltaSize, bool reading);
+ BitstreamPacker (RakNet::BitStream& stream, bool reading);
+
+ bool IsWriting () { return !m_IsReading; }
+ bool IsReading () { return m_IsReading; }
+ bool HasChanged () { return m_IsDifferent; }
+ bool HasReadOutOfBounds () { return !m_NoOutOfBounds; }
+
+ void Serialize (float& value, float maxDelta = -1.0F);
+ void Serialize (SInt32& value) { Serialize((UInt32&)value); }
+ void Serialize (UInt32& value);
+ void Serialize (short& value);
+ void Serialize (char& value) { Serialize((unsigned char&)value); }
+ void Serialize (unsigned char& value);
+ void Serialize (bool& value);
+ void Serialize (NetworkViewID& value);
+ void Serialize (std::string& value);
+ void Serialize (Vector3f& value, float maxDelta = -1.0F);
+ void Serialize (Quaternionf& value, float maxDelta = -1.0F);
+ void Serialize (char* value, int& valueLength);
+
+ private:
+
+ void ReadPackState (Quaternionf& t);
+ void ReadPackState (Vector3f& t);
+ void ReadPackState (float& t);
+ void ReadPackState (UInt32& t);
+ void ReadPackState (short& t);
+ void ReadPackState (unsigned char& t);
+ void ReadPackState (bool& t);
+ void ReadPackState (std::string& t);
+ void ReadPackState (char*& t, int& length);
+ void ReadPackState (NetworkViewID& t);
+
+ void WritePackState (Vector3f& t);
+ void WritePackState (Quaternionf& t);
+ void WritePackState (float t);
+ void WritePackState (UInt32 t);
+ void WritePackState (short t);
+ void WritePackState (unsigned char t);
+ void WritePackState (bool t);
+ void WritePackState (std::string& t);
+ void WritePackState (char* t, int& length);
+ void WritePackState (NetworkViewID& t);
+
+};
+
+#endif
diff --git a/Runtime/Network/DummyNetwork.cpp b/Runtime/Network/DummyNetwork.cpp
new file mode 100644
index 0000000..7d3945e
--- /dev/null
+++ b/Runtime/Network/DummyNetwork.cpp
@@ -0,0 +1,736 @@
+
+#include "NetworkManager.h"
+
+#if ENABLE_NETWORK
+#include "MasterServerInterface.h"
+#include "External/RakNet/builds/include/RakNetworkFactory.h"
+#include "External/RakNet/builds/include/RakPeerInterface.h"
+#include "External/RakNet/builds/include/RakNetStatistics.h"
+#include "External/RakNet/builds/include/RakSleep.h"
+#include "External/RakNet/builds/include/SocketLayer.h"
+#endif
+#include "Runtime/GameCode/CloneObject.h"
+#include "BitStreamPacker.h"
+#include "Runtime/Utilities/Utility.h"
+
+
+NetworkManager::NetworkManager(BaseAllocator* baseAllocator, ObjectCreationMode mode)
+: Super(baseAllocator, mode) { }
+
+NetworkManager::~NetworkManager() { }
+
+void NetworkManager::AddNetworkView (ListNode_& s) { }
+
+void NetworkManager::AddAllNetworkView (ListNode_& s) { }
+
+void NetworkManager::AddNonSyncNetworkView (ListNode_& s) { }
+
+void NetworkManager::AwakeFromLoad (AwakeFromLoadMode awakeMode) { }
+
+void NetworkManager::SetAssetToPrefab (const std::map<UnityGUID, PPtr<GameObject> >& mapping) { }
+
+void NetworkManager::NetworkOnApplicationQuit() { }
+
+void NetworkManager::InitializeSecurity() { }
+
+int NetworkManager::InitializeServer(int connections, int listenPort, bool useNat)
+{
+ return 0;
+}
+
+int NetworkManager::Connect(std::vector<string> IPs, int remotePort, int listenPort, const std::string& password)
+{
+ return 0;
+}
+
+int NetworkManager::Connect(std::string IP, int remotePort, const std::string& password)
+{
+ return 0;
+}
+
+int NetworkManager::Connect(std::string IP, int remotePort, int listenPort, const std::string& password)
+{
+ return 0;
+}
+
+void NetworkManager::Disconnect(int timeout, bool resetParams) { }
+
+void NetworkManager::CloseConnection(int target, bool sendDisconnect) { }
+
+void NetworkManager::NetworkUpdate() { }
+
+string NetworkManager::GetStats(int i)
+{
+ return string();
+}
+
+void NetworkManager::ClientConnectionDisconnected(int msgType) { }
+
+
+void NetworkManager::MsgNewConnection(SystemAddress clientAddress) { }
+
+void NetworkManager::MsgClientInit() { }
+
+void NetworkManager::RPCReceiveViewIDBatch (RPCParameters *rpcParameters) { }
+
+void NetworkManager::RPCRequestViewIDBatch (RPCParameters *rpcParameters) { }
+
+void NetworkManager::SendRPCBuffer (PlayerTable &player) { }
+
+bool NetworkManager::MayReceiveFromPlayer( SystemAddress adress, int group )
+{
+ return false;
+}
+
+bool NetworkManager::MaySendToPlayer( SystemAddress address, int group )
+{
+ return false;
+}
+
+
+void NetworkManager::SetReceivingGroupEnabled (int playerIndex, int group, bool enabled) { }
+
+void NetworkManager::SetSendingGroupEnabled (int group, bool enabled) { }
+
+void NetworkManager::SetSendingGroupEnabled (int playerIndex, int group, bool enabled) { }
+
+void NetworkManager::MsgStateUpdate(SystemAddress senderAddress) { }
+
+void NetworkManager::DestroyPlayerObjects(NetworkPlayer playerID) { }
+
+void NetworkManager::DestroyDelayed(NetworkViewID viewID) { }
+
+void NetworkManager::RPCNetworkDestroy(RPCParameters *rpcParameters) { }
+
+void NetworkManager::SetMessageQueueRunning(bool run) { }
+
+void NetworkManager::RegisterRPC(const char* reg, void ( *functionPointer ) ( RPCParameters *rpcParms )) { }
+
+void NetworkManager::PerformRPC(const std::string &function, int mode, RakNet::BitStream& parameters, NetworkViewID viewID, UInt32 group) { }
+
+void NetworkManager::BroadcastRPC(const char* name, const RakNet::BitStream *parameters, PacketPriority priority, SystemAddress target, RakNetTime *includedTimestamp, UInt32 group ) { }
+
+void NetworkManager::PerformRPCSpecificTarget(const char* function, PlayerTable *player, RakNet::BitStream& parameters, UInt32 group) { }
+
+void NetworkManager::PeformRPCRelayAll(char *name, int mode, NetworkViewID viewID, UInt32 group, RakNetTime timestamp, SystemAddress sender, RakNet::BitStream &stream) { }
+
+void NetworkManager::PerformRPCRelaySpecific(char *name, RakNet::BitStream *stream, NetworkPlayer player) { }
+
+
+void NetworkManager::AddRPC(const std::string& name, NetworkPlayer sender, NetworkViewID viewID, UInt32 group, RakNet::BitStream& stream) { }
+
+void NetworkManager::MsgRemoveRPCs() { }
+
+void NetworkManager::RemoveRPCs(NetworkPlayer playerIndex, NetworkViewID viewID, UInt32 groupMask) { }
+
+bool NetworkManager::ShouldIgnoreInGarbageDependencyTracking ()
+{
+ return true;
+}
+
+#pragma mark -
+
+NetworkView* NetworkManager::ViewIDToNetworkView(const NetworkViewID& ID)
+{
+ return NULL;
+}
+
+
+NetworkViewID NetworkManager::NetworkViewToViewID(NetworkView* view)
+{
+ NetworkViewID dummy;
+ return dummy;
+}
+
+int NetworkManager::GetValidInitIndex()
+{
+ return 0;
+}
+
+NetworkViewID NetworkManager::AllocateViewID()
+{
+ NetworkViewID dummy;
+ return dummy;
+}
+
+NetworkViewID NetworkManager::AllocateSceneViewID()
+{
+ NetworkViewID dummy;
+ return dummy;
+}
+
+NetworkViewID NetworkManager::ValidateSceneViewID(NetworkView* validateView, NetworkViewID viewID)
+{
+ NetworkViewID dummy;
+ return dummy;
+}
+
+bool NetworkManager::WasViewIdAllocatedByPlayer (NetworkViewID viewID, NetworkPlayer playerID)
+{
+ return false;
+}
+
+bool NetworkManager::WasViewIdAllocatedByMe(NetworkViewID viewID)
+{
+ return false;
+}
+
+NetworkPlayer NetworkManager::GetNetworkViewIDOwner(NetworkViewID viewID)
+{
+ NetworkPlayer dummy;
+ return dummy;
+}
+
+int NetworkManager::GetPlayerID()
+{
+ return 0;
+}
+
+int NetworkManager::GetPeerType()
+{
+ return 0;
+}
+
+int NetworkManager::GetDebugLevel()
+{
+ return 0;
+}
+
+SystemAddress NetworkManager::GetPlayerAddress()
+{
+ return UNASSIGNED_SYSTEM_ADDRESS;
+}
+
+bool NetworkManager::IsClient()
+{
+ return false;
+}
+
+bool NetworkManager::IsServer()
+{
+ return false;
+}
+
+void NetworkManager::SetSimulation (NetworkSimulation simulation) { }
+
+void NetworkManager::MsgClientDidDisconnect() { }
+
+void NetworkManager::MsgClientDidDisconnect(SystemAddress clientAddress) { }
+
+void NetworkManager::SetIncomingPassword (const std::string& incomingPassword) { }
+
+std::string NetworkManager::GetIncomingPassword ()
+{
+ return string();
+}
+
+int NetworkManager::GetMaxConnections()
+{
+ return 0;
+}
+
+int NetworkManager::GetConnectionCount()
+{
+ return 0;
+}
+
+PlayerTable* NetworkManager::GetPlayerEntry(SystemAddress playerAddress)
+{
+ return NULL;
+}
+
+PlayerTable* NetworkManager::GetPlayerEntry(NetworkPlayer index)
+{
+ return NULL;
+}
+
+SystemAddress NetworkManager::GetSystemAddressFromIndex(NetworkPlayer playerIndex)
+{
+ return UNASSIGNED_SYSTEM_ADDRESS;
+}
+
+int NetworkManager::GetIndexFromSystemAddress(SystemAddress playerAddress)
+{
+ return -1;
+}
+
+std::vector<PlayerTable> NetworkManager::GetPlayerAddresses()
+{
+ return std::vector<PlayerTable>();
+}
+
+
+bool NetworkManager::IsPasswordProtected()
+{
+ return false;
+}
+
+std::string NetworkManager::GetIPAddress()
+{
+ return string();
+}
+
+std::string NetworkManager::GetExternalIP()
+{
+ return std::string();
+}
+
+int NetworkManager::GetExternalPort()
+{
+ return 0;
+}
+
+std::string NetworkManager::GetIPAddress(int player)
+{
+ return string ();
+}
+
+int NetworkManager::GetPort()
+{
+ return 0;
+}
+
+int NetworkManager::GetPort(int player)
+{
+ return 0;
+}
+
+void NetworkManager::GetConnections(int* connection) { }
+
+bool NetworkManager::GetUseNat()
+{
+ return false;
+}
+
+void NetworkManager::SetUseNat(bool enabled) { }
+
+
+
+
+template<class TransferFunc>
+void NetworkManager::Transfer (TransferFunc& transfer) { }
+
+int NetworkManager::GetLastPing (NetworkPlayer player)
+{
+ return 0;
+}
+
+int NetworkManager::GetAveragePing (NetworkPlayer player)
+{
+ return 0;
+}
+
+Object* NetworkManager::Instantiate (Object& prefab, Vector3f pos, Quaternionf rot, UInt32 group)
+{
+ return NULL;
+}
+
+
+Object* NetworkManager::NetworkInstantiateImpl (RakNet::BitStream& bitstream, SystemAddress sender, RakNetTime time)
+{
+ return NULL;
+}
+
+void NetworkManager::RPCNetworkInstantiate (RPCParameters* rpcParameters) { }
+
+void NetworkManager::SetLevelPrefix(int levelPrefix) { }
+
+IMPLEMENT_CLASS_HAS_INIT (NetworkManager)
+IMPLEMENT_OBJECT_SERIALIZE (NetworkManager)
+GET_MANAGER (NetworkManager)
+GET_MANAGER_PTR (NetworkManager)
+
+void NetworkManager::InitializeClass () { }
+
+void NetworkManager::CleanupClass () { }
+
+bool NetworkManager::MaySend( int group )
+{
+ return false;
+}
+
+RakNetTime NetworkManager::GetTimestamp()
+{
+ return 0;
+}
+
+double NetworkManager::GetTime()
+{
+ return 0;
+}
+
+RakPeerInterface* NetworkManager::GetPeer()
+{
+ return 0;
+}
+
+void NetworkManager::SwapFacilitatorID(SystemAddress newAddress) { }
+
+void NetworkManager::SetOldMasterServerAddress(SystemAddress address) { }
+
+void NetworkManager::SetConnTesterAddress(SystemAddress address) { }
+
+int NetworkManager::TestConnection(bool forceNATType, bool forceTest)
+{
+ return -1;
+}
+
+void NetworkManager::SetMaxConnections(int connections) { }
+
+void NetworkManager::PingWrapper(Ping *time) { }
+
+
+static SystemAddress dummy_system_address;
+SystemAddress& NetworkManager::GetFacilitatorAddress(bool resolve)
+{
+ return dummy_system_address;
+}
+
+ConnectionTester::ConnectionTester(SystemAddress address) { }
+
+ConnectionTester::~ConnectionTester() { }
+
+void ConnectionTester::SetAddress(SystemAddress address) { }
+
+int ConnectionTester::Update()
+{
+ return 0;
+}
+
+void ConnectionTester::ReportTestSucceeded() { }
+
+int ConnectionTester::RunTest(bool forceNATType)
+{
+ return 0;
+}
+
+
+// NetworkView
+
+NetworkView::NetworkView (BaseAllocator* baseAllocator, ObjectCreationMode mode)
+: Super(baseAllocator, mode)
+, m_Node (this)
+, m_AllNode(this) { }
+
+NetworkView::~NetworkView () { }
+
+void NetworkView::Update() { }
+
+void NetworkView::RPCCall (const std::string &function, int inMode, MonoArray* args) { }
+
+void NetworkView::RPCCallSpecificTarget (const std::string &function, NetworkPlayer target, MonoArray* args) { }
+
+void NetworkView::AddToManager () { }
+
+void NetworkView::RemoveFromManager () { }
+
+void NetworkView::SetupSceneViewID () { }
+
+void NetworkView::AwakeFromLoad (AwakeFromLoadMode mode) { }
+
+void NetworkView::SetObserved (Unity::Component* component) { }
+
+Unity::Component* NetworkView::GetObserved ()
+{
+ return NULL;
+}
+
+void NetworkView::Unpack (RakNet::BitStream& bitStream, NetworkMessageInfo& info, int msgType) { }
+
+bool NetworkView::Pack(RakNet::BitStream &stream, PackState* writeStatePtr, UInt8* readData, int &readSize, int msgID)
+{
+ return false;
+}
+
+void NetworkView::Send (SystemAddress systemAddress, bool broadcast) { }
+
+void NetworkView::SendToAllButOwner() { }
+
+NetworkViewID NetworkView::GetViewID()
+{
+ NetworkViewID dummy;
+ return dummy;
+}
+
+void NetworkView::SetViewID(NetworkViewID viewID) { }
+
+void NetworkView::SetGroup(unsigned group) { }
+
+void NetworkView::Reset () { }
+
+void NetworkView::SetStateSynchronization (int sync) { }
+
+void NetworkView::SetInitState(int index, bool isSent) { }
+
+bool NetworkView::GetInitStateStatus(int index)
+{
+ return false;
+}
+
+void NetworkView::ClearInitStateAndOwner() { }
+
+bool NetworkView::SetPlayerScope(NetworkPlayer playerIndex, bool relevancy)
+{
+ return false;
+}
+
+void NetworkView::SetScope(unsigned int initIndex, bool relevancy) { }
+
+bool NetworkView::CheckScope(int initIndex)
+{
+ return false;
+}
+
+template<class TransferFunc>
+void NetworkView::Transfer (TransferFunc& transfer) { }
+
+SystemAddress NetworkView::GetOwnerAddress ()
+{
+ return UNASSIGNED_SYSTEM_ADDRESS;
+}
+
+void RegisterRPC (const char* name) { }
+
+void NetworkView::InitializeClass () { }
+
+void NetworkView::CleanupClass () { }
+
+
+IMPLEMENT_CLASS_HAS_INIT (NetworkView)
+IMPLEMENT_OBJECT_SERIALIZE (NetworkView)
+
+
+// BitStreamPacker
+
+void BitstreamPacker::ReadPackState (Quaternionf& t) { }
+
+void BitstreamPacker::ReadPackState (Vector3f& t) { }
+
+void BitstreamPacker::WritePackState (Vector3f& t) { }
+
+void BitstreamPacker::WritePackState (Quaternionf& t) { }
+
+void BitstreamPacker::WritePackState (string& t) { }
+
+void BitstreamPacker::ReadPackState (string& t) { }
+
+void BitstreamPacker::WritePackState (char* t, int& length) { }
+
+void BitstreamPacker::ReadPackState (char*& t, int& length) { }
+
+void BitstreamPacker::ReadPackState (NetworkViewID& t) { }
+
+void BitstreamPacker::WritePackState (NetworkViewID& t) { }
+
+#define READ_WRITE_PACKSTATE(TYPE) \
+void BitstreamPacker::ReadPackState (TYPE& t) \
+{ \
+} \
+void BitstreamPacker::WritePackState (TYPE t) \
+{ \
+}
+
+READ_WRITE_PACKSTATE(UInt32)
+READ_WRITE_PACKSTATE(float)
+READ_WRITE_PACKSTATE(short)
+READ_WRITE_PACKSTATE(UInt8)
+READ_WRITE_PACKSTATE(bool)
+
+void BitstreamPacker::Serialize (NetworkViewID& value) { }
+
+void BitstreamPacker::Serialize (float& value, float maxDelta) { }
+
+void BitstreamPacker::Serialize (bool& value) { }
+
+#define SERIALIZE(TYPE) void BitstreamPacker::Serialize (TYPE& value) {\
+}
+
+SERIALIZE(UInt32)
+SERIALIZE(short)
+SERIALIZE(UInt8)
+
+
+void BitstreamPacker::Serialize (Vector3f& value, float maxDelta) { }
+
+void BitstreamPacker::Serialize (Quaternionf& value, float maxDelta) { }
+
+void BitstreamPacker::Serialize (std::string& value) { }
+
+void BitstreamPacker::Serialize (char* value, int& valueLength) { }
+
+BitstreamPacker::BitstreamPacker (RakNet::BitStream& stream, std::vector<UInt8>* delta, UInt8* readData, int readSize, bool reading) { }
+
+BitstreamPacker::BitstreamPacker (RakNet::BitStream& stream, bool reading) { }
+
+void BitstreamPacker::Init(RakNet::BitStream& stream, std::vector<UInt8>* delta, UInt8* readData, int readSize, bool reading) { }
+
+
+
+// MasterServerInterface
+
+
+MasterServerInterface::MasterServerInterface(BaseAllocator* baseAllocator, ObjectCreationMode mode)
+: Super(baseAllocator, mode) { }
+
+MasterServerInterface::~MasterServerInterface() { }
+
+void MasterServerInterface::NetworkOnApplicationQuit() { }
+
+// Resolve the master server address if it is invalid
+void ResolveMasterServerAddress(SystemAddress& address) { }
+
+void MasterServerInterface::Connect() { }
+
+void MasterServerInterface::ProcessPacket(Packet *packet) { }
+
+void MasterServerInterface::NetworkUpdate() { }
+
+void MasterServerInterface::QueryHostList() { }
+
+void MasterServerInterface::QueryHostList(string gameType) { }
+
+void MasterServerInterface::ClearHostList() { }
+
+bool MasterServerInterface::PopulateUpdate()
+{
+ return false;
+}
+
+bool MasterServerInterface::PopulateUpdate(string gameName, string comment)
+{
+ return false;
+}
+
+void MasterServerInterface::RegisterHost(string gameType, string gameName, string comment) { }
+
+// Uses the game server peer
+void MasterServerInterface::SendHostUpdate() { }
+
+void MasterServerInterface::Disconnect() { }
+
+// Uses the game server peer
+void MasterServerInterface::UnregisterHost() { }
+
+std::vector<HostData> MasterServerInterface::PollHostList()
+{
+ return std::vector<HostData>();
+}
+
+void MasterServerInterface::ResetHostState() { }
+
+IMPLEMENT_CLASS (MasterServerInterface)
+GET_MANAGER (MasterServerInterface)
+GET_MANAGER_PTR (MasterServerInterface)
+
+
+
+// Utilities
+
+std::string GetLocalIP()
+{
+ std::string s = "0.0.0.0";
+ return s;
+}
+
+bool CheckForPublicAddress()
+{
+ return false;
+}
+
+int Ping::GetTime()
+{
+ return 0;
+}
+
+void Ping::SetTime(int value) { }
+
+int Ping::GetIsDone()
+{
+ return 0;
+}
+
+void Ping::SetIsDone(bool value) { }
+
+std::string Ping::GetIP()
+{
+ return std::string();
+}
+
+void Ping::SetIP(std::string value) { }
+
+
+
+// NetworkViewID
+
+
+std::string NetworkViewID::ToString () const
+{
+ return std::string();
+}
+
+bool operator == (const NetworkViewID& lhs, const NetworkViewID& rhs)
+{
+ return false;
+}
+
+
+// NetworkViewIDAllocator
+
+
+NetworkViewIDAllocator::NetworkViewIDAllocator() { }
+
+
+
+// RakNet
+
+
+using namespace RakNet;
+
+BitStream::BitStream() { }
+
+BitStream::~BitStream() { }
+
+LightweightDatabaseClient::LightweightDatabaseClient() { }
+
+LightweightDatabaseClient::~LightweightDatabaseClient() { }
+
+using namespace DataStructures;
+
+Table::Cell::Cell() { }
+
+Table::Cell::~Cell() { }
+
+NatPunchthroughClient::NatPunchthroughClient() { }
+
+NatPunchthroughClient::~NatPunchthroughClient() { }
+
+bool NatPunchthroughClient::OpenNAT(RakNetGUID destination, SystemAddress facilitator)
+{
+ return true;
+}
+
+void NatPunchthroughClient::Clear(void) { }
+
+void NatPunchthroughClient::OnAttach(void) { }
+
+void NatPunchthroughClient::Update(void) { }
+
+PluginReceiveResult NatPunchthroughClient::OnReceive(Packet *packet)
+{
+ return RR_STOP_PROCESSING_AND_DEALLOCATE;
+}
+
+void NatPunchthroughClient::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason) { }
+
+void NatPunchthroughClient::OnRakPeerShutdown(void) { }
+
+PluginInterface2::PluginInterface2() { }
+
+PluginInterface2::~PluginInterface2() { }
+
+const char *SystemAddress::ToString(bool writePort) const
+{
+ return "";
+}
+
+void SystemAddress::SetBinaryAddress(const char *str) { }
diff --git a/Runtime/Network/MasterServerInterface.cpp b/Runtime/Network/MasterServerInterface.cpp
new file mode 100644
index 0000000..8ce20d7
--- /dev/null
+++ b/Runtime/Network/MasterServerInterface.cpp
@@ -0,0 +1,692 @@
+#include "UnityPrefix.h"
+#include "MasterServerInterface.h"
+
+#if ENABLE_NETWORK
+#include "External/RakNet/builds/include/TableSerializer.h"
+#include "External/RakNet/builds/include/BitStream.h"
+#include "External/RakNet/builds/include/StringCompressor.h"
+#include "External/RakNet/builds/include/DS_Table.h"
+#include "External/RakNet/builds/include/RakNetworkFactory.h"
+#include "External/RakNet/builds/include/SocketLayer.h"
+#include "Configuration/UnityConfigureVersion.h"
+#include "NetworkManager.h"
+#include <time.h>
+#include "NetworkUtility.h"
+
+// Future todos
+// TODO: ATM there is a 200 ms delay on disconnects. It "could" be possible that more than 200 ms pass before a network action is completed
+// maybe this should be done only after we make sure the operation is done.
+// NOTE: Row IDs are sent to clients when they do not have a row ID (first reg) or when the master server has restarted (and forgotten everything).
+// The problem is that updates from the client are sent with the old row ID which is not found during lookup and a new one sent to the client
+// The client never receives this new row ID because the connection is always closed after sending updates. The simples solution is to
+// keep client connections persistent for clients running hosts. Clients which just query for the host list (game clients) disconnect immediately.
+
+namespace {
+ const int kMasterServerPort = 23466;
+ const time_t kMaxUpdateInterval = 2;
+}
+
+MasterServerInterface::MasterServerInterface(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_Peer = RakNetworkFactory::GetRakPeerInterface();
+ m_GameType = "";
+ m_HostName = "";
+ m_PendingRegister = false;
+ m_PendingHostUpdate = false;
+ m_PendingQuery = false;
+ m_RowID = -1;
+ m_LastHostUpdateTime = 0;
+ m_Registered = false;
+ m_MasterServerID.binaryAddress = 0;
+ m_MasterServerID.port = kMasterServerPort;
+ m_Version[0]=2; m_Version[1]=0; m_Version[2]=0;
+ m_UpdateRate = 10;
+ m_IsDedicatedServer = false;
+ time(&m_ShutdownTimer);
+
+ m_HostDatabaseClient = new LightweightDatabaseClient;
+
+ // Create the cell update that will be reused for storing last update
+ strcpy(m_LastUpdate[0].columnName,"NAT");
+ m_LastUpdate[0].columnType = DataStructures::Table::NUMERIC;
+ m_LastUpdate[0].cellValue.Set(0);
+ strcpy(m_LastUpdate[1].columnName,"Game name");
+ m_LastUpdate[1].columnType = DataStructures::Table::STRING;
+ m_LastUpdate[1].cellValue.Set(0);
+ strcpy(m_LastUpdate[2].columnName,"Connected players");
+ m_LastUpdate[2].columnType = DataStructures::Table::NUMERIC;
+ m_LastUpdate[2].cellValue.Set(0);
+ strcpy(m_LastUpdate[3].columnName,"Player limit");
+ m_LastUpdate[3].columnType = DataStructures::Table::NUMERIC;
+ m_LastUpdate[3].cellValue.Set(0);
+ strcpy(m_LastUpdate[4].columnName,"Password protected");
+ m_LastUpdate[4].columnType = DataStructures::Table::NUMERIC;
+ m_LastUpdate[4].cellValue.Set(0);
+ strcpy(m_LastUpdate[5].columnName,"IP address");
+ m_LastUpdate[5].columnType = DataStructures::Table::BINARY;
+ m_LastUpdate[5].cellValue.Set(NULL, 0);
+ strcpy(m_LastUpdate[6].columnName,"Port");
+ m_LastUpdate[6].columnType = DataStructures::Table::NUMERIC;
+ m_LastUpdate[6].cellValue.Set(0);
+ strcpy(m_LastUpdate[7].columnName,"Comment");
+ m_LastUpdate[7].columnType = DataStructures::Table::STRING;
+ m_LastUpdate[7].cellValue.Set(0);
+}
+
+MasterServerInterface::~MasterServerInterface()
+{
+ delete m_HostDatabaseClient;
+ m_HostDatabaseClient = NULL;
+ RakNetworkFactory::DestroyRakPeerInterface(m_Peer);
+ m_Peer = NULL;
+}
+
+void MasterServerInterface::NetworkOnApplicationQuit()
+{
+ m_Peer->Shutdown(100);
+ m_HostList.clear();
+ // Reset to default values
+ m_MasterServerID.binaryAddress = 0;
+ m_MasterServerID.port = kMasterServerPort;
+ m_GameType = "";
+ m_HostName = "";
+ m_HostComment = "";
+ m_PendingRegister = false;
+ m_PendingHostUpdate = false;
+ m_PendingQuery = false;
+ m_RowID = -1;
+ m_Registered = false;
+ m_UpdateRate = 10;
+ m_IsDedicatedServer = false;
+}
+
+// Resolve the master server address if it is invalid
+void MasterServerInterface::ResolveMasterServerAddress()
+{
+ ResolveAddress(m_MasterServerID, "masterserver.unity3d.com", "masterserverbeta.unity3d.com",
+ "Cannot resolve master server address, you must be connected to the internet before using it or set the address to something accessible to you.");
+}
+
+void MasterServerInterface::ClientConnect()
+{
+ ResolveMasterServerAddress();
+
+ SocketDescriptor sd(0,0);
+ if (!m_Peer->Startup(1, 30, &sd, 1))
+ {
+ SendToAllNetworkViews(kMasterServerConnectionAttemptFailed, kFailedToCreatedSocketOrThread);
+ }
+ m_Peer->AttachPlugin(&m_DatabaseClient);
+ if (!m_Peer->Connect(m_MasterServerID.ToString(false), m_MasterServerID.port, 0, 0))
+ {
+ if (m_Peer->GetMaximumNumberOfPeers() >= m_Peer->NumberOfConnections())
+ {
+ ErrorString("Internal error while connecting to master server. Too many connected peers.");
+ }
+ else
+ {
+ ErrorString("Internal error while attempting to connect to master server.");
+ SendToAllNetworkViews(kMasterServerConnectionAttemptFailed, kIncorrectParameters);
+ }
+ }
+}
+
+bool MasterServerInterface::CheckServerConnection()
+{
+ ResolveMasterServerAddress();
+
+ if (!GetNetworkManager().GetPeer()->IsConnected(m_MasterServerID))
+ {
+ ServerConnect();
+ return false;
+ }
+ if (!GetNetworkManager().GetPeer()->IsActive())
+ {
+ ServerConnect();
+ return false;
+ }
+ return true;
+}
+
+void MasterServerInterface::ServerConnect()
+{
+ if (!GetNetworkManager().GetPeer()->Connect(m_MasterServerID.ToString(false), m_MasterServerID.port, 0, 0))
+ {
+ ErrorString("Internal error while attempting to connect to master server\n");
+ SendToAllNetworkViews(kMasterServerConnectionAttemptFailed, kInternalDirectConnectFailed);
+ }
+ NetworkInfo(NULL, "Attempting to connect to master server at %s:%d", m_MasterServerID.ToString(false), m_MasterServerID.port);
+ m_PendingRegister = true;
+}
+
+void MasterServerInterface::ProcessPacket(Packet *packet)
+{
+ switch(packet->data[0])
+ {
+ // Disconnect and connection lost only occurs for clients requesting host info, not servers registering their info
+ case ID_DISCONNECTION_NOTIFICATION:
+ {
+ NetworkInfo(NULL, "Disconnected from master server");
+ SendToAllNetworkViews(kDisconnectedFromMasterServer, ID_DISCONNECTION_NOTIFICATION);
+ m_PendingQuery = false;
+ break;
+ }
+ case ID_CONNECTION_LOST:
+ {
+ // If connection was lost with master server we should re-register the host. If running as client do nothing.
+ if (GetNetworkManager().IsServer())
+ {
+ NetworkInfo(NULL, "Lost connection to master server, reconnecting and resending host info");
+ ResetHostState();
+ SendHostUpdate();
+ }
+ else
+ {
+ ErrorString("Connection with master server lost");
+ SendToAllNetworkViews(kDisconnectedFromMasterServer, ID_CONNECTION_LOST);
+ m_PendingQuery = false;
+ }
+ break;
+ }
+ case ID_CONNECTION_BANNED:
+ {
+ ErrorString("Temporarily banned from the master server");
+ SendToAllNetworkViews(kMasterServerConnectionAttemptFailed, ID_CONNECTION_BANNED);
+ break;
+ }
+ case ID_CONNECTION_REQUEST_ACCEPTED:
+ {
+ NetworkInfo(NULL, "Connected to master server at %s", packet->systemAddress.ToString());
+ if (m_PendingRegister)
+ {
+ m_PendingRegister = false;
+ RegisterHost(m_GameType, m_HostName, m_HostComment);
+ }
+ if (m_PendingQuery)
+ {
+ m_PendingQuery = false;
+ QueryHostList(m_GameType);
+ }
+ if (m_PendingHostUpdate)
+ {
+ m_PendingHostUpdate = false;
+ SendHostUpdate();
+ }
+ break;
+ }
+ case ID_ALREADY_CONNECTED:
+ {
+ NetworkError(NULL, "Already connected to the master server, the server probably hasn't cleaned up because of an abrupt disconnection.");
+ SendToAllNetworkViews(kMasterServerConnectionAttemptFailed, ID_ALREADY_CONNECTED);
+ m_PendingQuery = false;
+ break;
+ }
+ case ID_CONNECTION_ATTEMPT_FAILED:
+ {
+ ErrorString(Format("Failed to connect to master server at %s", packet->systemAddress.ToString()));
+ SendToAllNetworkViews(kMasterServerConnectionAttemptFailed, ID_CONNECTION_ATTEMPT_FAILED);
+ ResetHostState();
+ break;
+ }
+ // TODO: atm the server does not send this during client lookups, but maybe it should (as all tables are dynamically created this will never be returned to hosts)
+ case ID_DATABASE_UNKNOWN_TABLE:
+ {
+ ErrorString("Unkown game type");
+ // This uses NetworkConnectionError enums, and this status message doesn't belong in there
+ //SendToAllNetworkViews(kMasterServerConnectionAttemptFailed, kUnkownGameType);
+ break;
+ }
+ // This should never occur as we don't use db passwords directly
+ case ID_DATABASE_INCORRECT_PASSWORD:
+ {
+ ErrorString("Incorrect master server password");
+ break;
+ }
+ case ID_DATABASE_QUERY_REPLY:
+ {
+ NetworkInfo(NULL, "Incoming host list query response from master server.");
+
+ DataStructures::Table table;
+ if (TableSerializer::DeserializeTable(packet->data+sizeof(MessageID), packet->length-sizeof(MessageID), &table))
+ {
+ m_HostList.clear();
+ DataStructures::Page<unsigned, DataStructures::Table::Row*, _TABLE_BPLUS_TREE_ORDER> *cur = table.GetListHead();
+ while (cur)
+ {
+ for (int i=0; i < (unsigned)cur->size; i++)
+ {
+ // NOTE: The first three cell values are the systemId (binary), last ping response time and next ping
+ // send time(numerics)
+ DataStructures::List<DataStructures::Table::Cell*> cells = cur->data[i]->cells;
+ HostData data;
+ if (cells[5]->c != NULL &&
+ (int)cells[9]->i != 0 &&
+ cells[9]->c != NULL &&
+ (int)cells[10]->i != 0 &&
+ cells[12]->c != NULL)
+ {
+ data.useNat = (int)cells[4]->i;
+ data.gameType = m_GameType;
+ data.gameName = cells[5]->c;
+ data.connectedPlayers = (int)cells[6]->i;
+ data.playerLimit = (int)cells[7]->i;
+ data.passwordProtected = (int)cells[8]->i;
+ data.guid = cells[12]->c;
+
+ int ipCount = int(cells[9]->i / 16);
+ // If the size of the data is not a factor of 16 then something has gone wrong (IP addresses have size 16)
+ if (((int)cells[9]->i) % 16 == 0)
+ {
+ for (int ip=0; ip < ipCount; ip++)
+ {
+ const char* ipData = cells[9]->c + ip * 16;
+ if( ipData[0] == 0 ) break;
+ data.IP.push_back( ipData );
+ }
+ }
+ else
+ {
+ ErrorString(Format("Malformed data inside IP information packet. Size was %f", cells[8]->i));
+ }
+ data.port = int(cells[10]->i);
+ if (cells[11]->c != NULL)
+ data.comment = cells[11]->c;
+ else
+ data.comment = "";
+ m_HostList.push_back(data);
+ }
+ else
+ {
+ ErrorString("Received malformed data in the host list from the master server\n");
+ }
+ }
+ cur=cur->next;
+ }
+ }
+ else
+ {
+ m_HostList.clear();
+ }
+ SendToAllNetworkViews(kMasterServerEvent, kHostListReceived);
+ break;
+ }
+ case ID_DATABASE_ROWID:
+ {
+ unsigned int rowID;
+ RakNet::BitStream stream;
+ stream.Write((char*)packet->data, packet->length);
+ stream.IgnoreBits(8);
+ stream.Read(rowID);
+
+ NetworkInfo(NULL, "Received identifier %u from master server", rowID);
+ SendToAllNetworkViews(kMasterServerEvent, kRegistrationSucceeded);
+ m_RowID = rowID;
+
+ break;
+ }
+ case ID_MASTERSERVER_REDIRECT:
+ {
+ SystemAddress newMaster, newFacilitator;
+ RakNet::BitStream b(packet->data, packet->length, false);
+ b.IgnoreBits(8);
+ b.Read(newMaster);
+ b.Read(newFacilitator);
+
+ GetNetworkManager().SwapFacilitatorID(newFacilitator);
+ SystemAddress oldMasterServer = m_MasterServerID;
+ GetNetworkManager().SetOldMasterServerAddress(oldMasterServer);
+ m_MasterServerID = newMaster;
+ ResetHostState();
+
+ if (GetNetworkManager().IsServer())
+ {
+ GetNetworkManager().GetPeer()->CloseConnection(oldMasterServer, true);
+ NetworkInfo(NULL, "Redirecting master server host updates to %s", newMaster.ToString());
+ NetworkInfo(NULL, "Changing facilitator location to %s", newFacilitator.ToString());
+ SendHostUpdate();
+ }
+ else
+ {
+ NetworkInfo(NULL, "Redirecting master server host list queries to %s", newMaster.ToString());
+ NetworkInfo(NULL, "Changing facilitator location to %s", newFacilitator.ToString());
+ Disconnect();
+ QueryHostList();
+ }
+ break;
+ }
+ case ID_MASTERSERVER_MSG:
+ {
+ int msgLength;
+ RakNet::BitStream b(packet->data, packet->length, false);
+ b.IgnoreBits(8);
+ b.Read(msgLength);
+ if (msgLength > 0)
+ {
+ char* msg = new char[msgLength];
+ b.Read(msg, msgLength);
+ LogString(Format("Message from master server: %s", msg));
+ delete[] msg;
+ }
+ break;
+ }
+ default:
+ {
+ NetworkError(NULL, "Unknown message from master server (%s) %d", packet->systemAddress.ToString(), packet->data[0]);
+ break;
+ }
+ }
+}
+
+void MasterServerInterface::NetworkUpdate()
+{
+ if (!m_Peer)
+ return;
+
+ // Send heartbeat if running as server, only send if running as server and we have already registered (m_HostName set)
+ if (m_UpdateRate > 0 && m_Registered)
+ {
+ if ((time(0) - m_LastHostUpdateTime > m_UpdateRate) && m_HostName.size() > 1 && !m_PendingRegister)
+ {
+ SendHostUpdate();
+ }
+ }
+
+ if (!m_Peer->IsActive())
+ return;
+
+ // If not registering or already registered, this is a client, then check for shutdown timeout
+ if (!m_Registered && !m_PendingRegister && time(0) > m_ShutdownTimer + 20)
+ {
+ // Use a short delay, its not that bad if the disconnect notification never arrives at master server
+ m_Peer->Shutdown(50, 0);
+ }
+
+ Packet *p;
+ p=m_Peer->Receive();
+ while (p)
+ {
+ ProcessPacket(p);
+ m_Peer->DeallocatePacket(p);
+ p=m_Peer->Receive();
+ }
+}
+
+void MasterServerInterface::QueryHostList()
+{
+ QueryHostList(m_GameType);
+}
+
+void MasterServerInterface::QueryHostList(string gameType)
+{
+ time(&m_ShutdownTimer);
+
+ // Wait for previous query to clear
+ if (m_PendingQuery) return;
+
+ if (gameType.empty())
+ {
+ ErrorString("Empty game type given in QueryHostList(), aborting query.");
+ return;
+ }
+ m_GameType = gameType;
+
+ ResolveMasterServerAddress();
+
+ if (m_Peer == NULL)
+ {
+ ClientConnect();
+ m_PendingQuery = true;
+ return;
+ }
+ else if (!m_Peer->IsActive())
+ {
+ ClientConnect();
+ m_PendingQuery = true;
+ return;
+ }
+ else if (!m_Peer->IsConnected(m_MasterServerID))
+ {
+ ClientConnect();
+ m_PendingQuery = true;
+ return;
+ }
+
+ m_DatabaseClient.QueryTable(m_Version, gameType.c_str(), 0, 0, 0, 0, 0, 0, 0, m_MasterServerID, false);
+// LogString("Sent host query to master server");
+ // Disconnect after the list has arrived
+}
+
+void MasterServerInterface::ClearHostList()
+{
+ m_HostList.clear();
+}
+
+bool MasterServerInterface::PopulateUpdate()
+{
+ return PopulateUpdate(m_HostName, m_HostComment);
+}
+
+bool MasterServerInterface::PopulateUpdate(string gameName, string comment)
+{
+ // TODO: The function inside GetIPs uses char arrays which are pre-allocated. If it returns a full array then it is possible there
+ // are more IP addresses, in which case it should get a larger char array to use.
+ char ips[10][16];
+ int size = GetIPs(ips)*16;
+ if (size == 0)
+ ErrorString("Could not retrieve internal IP address. Host registration failed.");
+
+ bool changed = false;
+
+ if (((int)m_LastUpdate[0].cellValue.i) != static_cast<int>(GetNetworkManager().GetUseNat()))
+ changed = true;
+
+ if (((int)m_LastUpdate[1].cellValue.i) != 0 && changed != true)
+ {
+ if (strcmp(m_LastUpdate[1].cellValue.c,gameName.c_str()) != 0)
+ {
+ changed = true;
+ m_LastUpdate[1].cellValue.Clear();
+ m_LastUpdate[1].cellValue.Set(const_cast<char*>(gameName.c_str()));
+ }
+ }
+ else
+ {
+ changed = true;
+ }
+
+ //printf_console("connCount: Comparing %d and %d\n", intCell, GetNetworkManager().GetConnectionCount());
+ if (((int)m_LastUpdate[2].cellValue.i) != (GetNetworkManager().GetConnectionCount() + static_cast<int>(!m_IsDedicatedServer)) && changed != true)
+ changed = true;
+ //printf_console("maxCount: Comparing %d and %d\n", intCell, GetNetworkManager().GetMaxConnections());
+ if (((int)m_LastUpdate[3].cellValue.i) != GetNetworkManager().GetMaxConnections() + static_cast<int>(!m_IsDedicatedServer) && changed != true)
+ changed = true;
+ //printf_console("password: Comparing %d and %d\n", intCell, GetNetworkManager().IsPasswordProtected());
+ if (((int)m_LastUpdate[4].cellValue.i) != static_cast<int>(GetNetworkManager().IsPasswordProtected()) && changed != true)
+ changed = true;
+
+ if (((int)m_LastUpdate[5].cellValue.i) != 0 && changed != true)
+ {
+ /*printf_console("IPs: Comparing size %d and %d\n", intCell, size);
+ for (int i=0; i < intCell; i++)
+ printf_console("%x", tmpIPs[i]);
+ printf_console(" ");
+ for (int i=0; i < size; i++)
+ printf_console("%x", static_cast<char*>(ips[0])[i]);
+ printf_console("\n");*/
+ if (m_LastUpdate[5].cellValue.i != size)
+ changed = true;
+ else if (memcmp(m_LastUpdate[5].cellValue.c, ips, size) != 0)
+ changed = true;
+ }
+ else
+ {
+ changed = true;
+ }
+
+ //printf_console("port: Comparing %d and %d\n", intCell, GetNetworkManager().GetPort());
+ if (((int)m_LastUpdate[6].cellValue.i) != GetNetworkManager().GetPort() && changed != true)
+ changed = true;
+
+ if (((int)m_LastUpdate[7].cellValue.i) != 0 && changed != true)
+ {
+ //printf_console("comment: Comparing %s and %s\n", tmpComment, comment.c_str());
+ if (strcmp(m_LastUpdate[7].cellValue.c, comment.c_str()) != 0)
+ changed = true;
+ }
+ else
+ {
+ changed = true;
+ }
+
+ if (changed)
+ {
+ for (int i = 0; i < CELL_COUNT; i++)
+ m_LastUpdate[i].cellValue.Clear();
+ m_LastUpdate[0].columnType=DataStructures::Table::NUMERIC;
+ m_LastUpdate[0].cellValue.Set(GetNetworkManager().GetUseNat());
+ m_LastUpdate[1].columnType=DataStructures::Table::STRING;
+ m_LastUpdate[1].cellValue.Set(const_cast<char*>(gameName.c_str()));
+ m_LastUpdate[2].columnType=DataStructures::Table::NUMERIC;
+ m_LastUpdate[2].cellValue.Set(GetNetworkManager().GetConnectionCount() + static_cast<int>(!m_IsDedicatedServer));
+ m_LastUpdate[3].columnType=DataStructures::Table::NUMERIC;
+ m_LastUpdate[3].cellValue.Set(GetNetworkManager().GetMaxConnections() + static_cast<int>(!m_IsDedicatedServer));
+ m_LastUpdate[4].columnType=DataStructures::Table::NUMERIC;
+ m_LastUpdate[4].cellValue.Set(static_cast<int>(GetNetworkManager().IsPasswordProtected()));
+ m_LastUpdate[5].columnType=DataStructures::Table::BINARY;
+ m_LastUpdate[5].cellValue.Set((char*)ips, size);
+ m_LastUpdate[6].columnType=DataStructures::Table::NUMERIC;
+ m_LastUpdate[6].cellValue.Set(GetNetworkManager().GetPort());
+ m_LastUpdate[7].columnType=DataStructures::Table::STRING;
+ m_LastUpdate[7].cellValue.Set(const_cast<char*>(comment.c_str()));
+ }
+
+ return changed;
+}
+
+void MasterServerInterface::RegisterHost(string gameType, string gameName, string comment)
+{
+ // Wait until pending registrations have cleared or that a certain interval has passed (don't want to hammer the server)
+ if (m_PendingRegister || m_LastHostUpdateTime > time(0) - kMaxUpdateInterval)
+ return;
+
+ if (gameType.empty())
+ {
+ ErrorString("Empty game type given during host registration, aborting");
+ SendToAllNetworkViews(kMasterServerEvent, kRegistrationFailedGameName);
+ return;
+ }
+ if (gameName.empty())
+ {
+ ErrorString("Empty game name given during host registration, aborting");
+ SendToAllNetworkViews(kMasterServerEvent, kRegistrationFailedGameType);
+ return;
+ }
+ if (GetNetworkManager().GetPort() == 0)
+ {
+ ErrorString("It's not possible to register a host until it is running.");
+ SendToAllNetworkViews(kMasterServerEvent, kRegistrationFailedNoServer);
+ return;
+ }
+
+ m_GameType = gameType;
+ m_HostName = gameName;
+ m_HostComment = comment;
+
+ GetNetworkManager().GetPeer()->AttachPlugin(m_HostDatabaseClient);
+
+ if (!CheckServerConnection())
+ return;
+
+ PopulateUpdate();
+
+ m_LastHostUpdateTime = time(0);
+
+ m_HostDatabaseClient->UpdateRow(m_Version, gameType.c_str(), 0, RUM_UPDATE_OR_ADD_ROW, false, 0, m_LastUpdate, CELL_COUNT, m_MasterServerID, false );
+ NetworkLog(NULL, "Sent host registration to master server, registering a %sNAT assisted game as\n \"%s\", %d, %d, %s, \"%s\"",
+ (GetNetworkManager().GetUseNat()) ? "" : "non-",
+ gameName.c_str(),
+ GetNetworkManager().GetConnectionCount() + static_cast<int>(!m_IsDedicatedServer),
+ GetNetworkManager().GetMaxConnections() + static_cast<int>(!m_IsDedicatedServer),
+ (GetNetworkManager().IsPasswordProtected()) ? "password protected" : "not password protected",
+ comment.c_str());
+
+ m_Registered = true;
+}
+
+// Uses the game server peer
+void MasterServerInterface::SendHostUpdate()
+{
+ // Wait until pending host updates are finished
+ if (m_PendingHostUpdate)
+ {
+ NetworkInfo(NULL, "Still waiting for a master server reponse to another host update, ignoring this update.");
+ return;
+ }
+
+ if (!CheckServerConnection())
+ return;
+
+ if (!PopulateUpdate())
+ return;
+
+ m_LastHostUpdateTime = time(0);
+
+ if (m_RowID == -1)
+ {
+ m_HostDatabaseClient->UpdateRow(m_Version, m_GameType.c_str(), 0, RUM_UPDATE_OR_ADD_ROW, false, 0, m_LastUpdate, CELL_COUNT, m_MasterServerID, false );
+ NetworkInfo(NULL, "Sent new host update to master server");
+ }
+ else
+ {
+ m_HostDatabaseClient->UpdateRow(m_Version, m_GameType.c_str(), 0, RUM_UPDATE_OR_ADD_ROW, true, m_RowID, m_LastUpdate, CELL_COUNT, m_MasterServerID, false );
+ NetworkInfo(NULL, "Sent host update to master server with identifier %d", m_RowID);
+ }
+
+ m_Registered = true;
+}
+
+void MasterServerInterface::Disconnect()
+{
+ m_Peer->Shutdown(200);
+ m_Peer->DetachPlugin(&m_DatabaseClient);
+}
+
+// Uses the game server peer
+void MasterServerInterface::UnregisterHost()
+{
+ if (GetNetworkManagerPtr())
+ {
+ if (GetNetworkManager().GetPeer()->IsConnected(m_MasterServerID))
+ m_HostDatabaseClient->RemoveRow(m_GameType.c_str(), 0, m_RowID, m_MasterServerID, false);
+ // Always detach DB plugin when running as a server, nothing will happen if it's not attached
+ if (GetNetworkManager().IsServer())
+ GetNetworkManager().GetPeer()->DetachPlugin(m_HostDatabaseClient);
+ }
+
+ // Reset some local variables
+ m_RowID = -1;
+ m_GameType = "";
+ m_HostName = "";
+ m_HostComment = "";
+ m_Registered = false;
+}
+
+std::vector<HostData> MasterServerInterface::PollHostList()
+{
+ return m_HostList;
+}
+
+void MasterServerInterface::ResetHostState()
+{
+ m_PendingRegister = false;
+ m_PendingHostUpdate = false;
+ m_Registered = false;
+}
+
+
+
+IMPLEMENT_CLASS (MasterServerInterface)
+GET_MANAGER (MasterServerInterface)
+GET_MANAGER_PTR (MasterServerInterface)
+#endif
diff --git a/Runtime/Network/MasterServerInterface.h b/Runtime/Network/MasterServerInterface.h
new file mode 100644
index 0000000..9f59c37
--- /dev/null
+++ b/Runtime/Network/MasterServerInterface.h
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+#include "Runtime/GameCode/Behaviour.h"
+#include "NetworkEnums.h"
+#include "External/RakNet/builds/include/RakPeerInterface.h"
+#include "External/RakNet/builds/include/LightweightDatabaseClient.h"
+#include "External/RakNet/builds/include/MessageIdentifiers.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+
+#include <vector>
+
+const int CELL_COUNT=8;
+
+
+
+class MasterServerInterface : public GlobalGameManager
+{
+public:
+
+ REGISTER_DERIVED_CLASS (MasterServerInterface, GlobalGameManager)
+ typedef std::vector<HostData> HostList;
+
+ MasterServerInterface(MemLabelId label, ObjectCreationMode mode);
+ // ~MasterServerInterface(); declared-by-macro
+
+ virtual void NetworkOnApplicationQuit();
+ virtual void NetworkUpdate();
+
+ void ClientConnect();
+ void ServerConnect();
+ bool CheckServerConnection();
+ void QueryHostList();
+ void QueryHostList(string gameType);
+ void ClearHostList();
+ void RegisterHost(string gameType, string gameName, string comment);
+ void SendHostUpdate();
+ void UnregisterHost();
+ void Disconnect();
+ HostList PollHostList();
+ void ProcessPacket(Packet *packet);
+ void ResetHostState();
+
+ string GetIPAddress() { return string(m_MasterServerID.ToString(false)); }
+ void SetIPAddress(std::string address) { m_MasterServerID.SetBinaryAddress(address.c_str()); }
+ int GetPort() { return m_MasterServerID.port; }
+ void SetPort(int port) { m_MasterServerID.port = port; }
+ SystemAddress& GetMasterServerID() { return m_MasterServerID; }
+
+ void SetUpdateRate(int rate) { m_UpdateRate = rate; }
+ int GetUpdateRate() { return m_UpdateRate; }
+
+ bool PopulateUpdate();
+ bool PopulateUpdate(string gameName, string comment);
+
+ void SetDedicatedServer(bool value) { m_IsDedicatedServer = value; };
+ bool GetDedicatedServer() { return m_IsDedicatedServer; };
+
+private:
+ void ResolveMasterServerAddress();
+
+ RakPeerInterface *m_Peer;
+ LightweightDatabaseClient m_DatabaseClient;
+ LightweightDatabaseClient *m_HostDatabaseClient;
+ bool m_PendingRegister;
+ bool m_PendingQuery;
+ bool m_PendingHostUpdate;
+ string m_GameType;
+ string m_HostName;
+ string m_HostComment;
+ HostList m_HostList;
+ unsigned int m_RowID;
+ bool m_Registered;
+ time_t m_LastHostUpdateTime;
+ SystemAddress m_MasterServerID;
+ char m_Version[3];
+ int m_UpdateRate;
+ DatabaseCellUpdate m_LastUpdate[CELL_COUNT];
+ bool m_IsDedicatedServer;
+ time_t m_ShutdownTimer;
+};
+
+MasterServerInterface* GetMasterServerInterfacePtr ();
+MasterServerInterface& GetMasterServerInterface ();
+
+#endif
diff --git a/Runtime/Network/MulticastSocket.cpp b/Runtime/Network/MulticastSocket.cpp
new file mode 100644
index 0000000..5553be5
--- /dev/null
+++ b/Runtime/Network/MulticastSocket.cpp
@@ -0,0 +1,224 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_SOCKETS
+#include "MulticastSocket.h"
+#include "SocketUtils.h"
+#if UNITY_EDITOR && UNITY_OSX
+#include <ifaddrs.h>
+#include <net/if.h>
+#endif
+
+MulticastSocket::MulticastSocket()
+: Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
+, m_Bound(false)
+{
+ SetReuseAddress(true);
+#if UNITY_WINRT
+ m_initalised = false;
+#endif
+}
+
+#if !UNITY_WINRT
+
+bool MulticastSocket::Initialize(const char* group, unsigned short port, bool block)
+{
+ if (!SetBlocking(block))
+ return false;
+
+ SetupAddress(inet_addr(group), htons(port), &m_MulticastAddress);
+ return true;
+}
+
+bool MulticastSocket::Join()
+{
+ if (!m_Bound)
+ {
+ struct sockaddr_in listen_addr;
+ SetupAddress(htonl(INADDR_ANY), m_MulticastAddress.sin_port, &listen_addr);
+ if (CheckError(bind(m_SocketHandle, (struct sockaddr*) &listen_addr, sizeof(sockaddr_in)), "bind failed"))
+ return false;
+ m_Bound = true;
+ }
+
+#if UNITY_EDITOR
+ // Join the multicast group on all valid network addresses, so that a lower value routing metric adapter doesn't
+ // override a higher value routing metric adapter from processing the message (e.g. Ethernet and Wifi); This is
+ // primarily needed to fix the Profiler being able to auto-discover available players
+
+ // NOTE: Simply because we are able to join a multicast group doesn't mean that the port isn't blocked by a firewall.
+ // A common issue among Windows machines is when a network is marked as "Public" and when Unity first ran the user
+ // disallowed traffic on Public networks to come through the firewall.
+ return SetOptionForAllAddresses(IP_ADD_MEMBERSHIP, "unable to join multicast group");
+#else
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr.s_addr = m_MulticastAddress.sin_addr.s_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ return !CheckError(SetSocketOption(IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), "unable to join multicast group");
+#endif
+}
+
+bool MulticastSocket::Disband()
+{
+#if UNITY_EDITOR
+ return SetOptionForAllAddresses(IP_DROP_MEMBERSHIP, "unable to disband multicast group");
+#else
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr.s_addr = m_MulticastAddress.sin_addr.s_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ return !CheckError(SetSocketOption(IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)), "unable to disband multicast group");
+#endif
+}
+
+#if UNITY_EDITOR
+bool MulticastSocket::SetOptionForAllAddresses(int option, const char* msg)
+{
+ bool error = false;
+ // Windows doesn't have the getifaddrs call and OSX doesn't return all addresses with an empty
+ // string passed to getaddrinfo, so it is necessary to have two approaches.
+#if UNITY_WIN
+#define IFADDR_T ADDRINFOA
+#define PIFADDR_T PADDRINFOA
+#define GET_PSOCKADDR(i) i->ai_addr
+#define GET_NEXT_IFADDR(i) i->ai_next
+#define FREE_IFADDRS(i) freeaddrinfo(i);
+
+ // Set the filter to return network addresses that can handle multicast (udp)
+ IFADDR_T hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ PIFADDR_T ifaddr = NULL;
+ // Empty string is sent to return all addresses registered to the local computer
+ getaddrinfo("", NULL, &hints, &ifaddr);
+#else
+#define IFADDR_T ifaddrs
+#define PIFADDR_T ifaddrs*
+#define GET_PSOCKADDR(i) i->ifa_addr
+#define GET_NEXT_IFADDR(i) i->ifa_next
+#define FREE_IFADDRS(i) freeifaddrs(i);
+
+ PIFADDR_T ifaddr = NULL;
+ if (getifaddrs(&ifaddr) != -1)
+#endif
+ {
+ PIFADDR_T ifa = ifaddr;
+ while (ifa)
+ {
+#if !UNITY_WIN
+ // We only care about IPv4 interfaces that support multicast
+ if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == AF_INET && (ifa->ifa_flags & IFF_MULTICAST) != 0)
+#endif
+ {
+ sockaddr_in* addr = (sockaddr_in*)GET_PSOCKADDR(ifa);
+ //char* address = inet_ntoa(addr->sin_addr);
+
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr= m_MulticastAddress.sin_addr;
+ mreq.imr_interface = addr->sin_addr;
+
+ error |= CheckError(SetSocketOption(IPPROTO_IP, option, &mreq, sizeof(mreq)), msg);
+ }
+
+ ifa = GET_NEXT_IFADDR(ifa);
+ }
+ }
+ FREE_IFADDRS(ifaddr);
+
+#undef IFADDR_T
+#undef PIFADDR_T
+#undef GET_PSOCKADDR
+#undef GET_NEXT_IFADDR
+#undef FREE_IFADDRS
+
+ return !error;
+}
+#endif
+
+bool MulticastSocket::SetTTL(unsigned char ttl)
+{
+#if UNITY_XENON
+ return false;
+#else
+ return !CheckError(SetSocketOption(IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)), "failed to set TTL");
+#endif
+}
+
+bool MulticastSocket::SetLoop(bool loop)
+{
+#if UNITY_XENON
+ return false;
+#else
+ return !CheckError(SetSocketOption(IPPROTO_IP, IP_MULTICAST_LOOP, loop), "failed to set loop mode");
+#endif
+}
+
+bool MulticastSocket::SetBroadcast(bool broadcast)
+{
+ return !CheckError(SetSocketOption(SOL_SOCKET, SO_BROADCAST, broadcast), "failed to set broadcast mode");
+}
+
+int MulticastSocket::Send(const void* data, size_t data_len)
+{
+ SendUserData userData;
+ userData.dstAddr = (sockaddr*)&m_MulticastAddress;
+ userData.dstLen = sizeof(m_MulticastAddress);
+ return Socket::Send(data, data_len, &userData);
+}
+
+int MulticastSocket::Recv(void* data, size_t data_len, RecvUserData* userData)
+{
+ int result = Socket::Recv(data, data_len, userData);
+ return result;
+}
+
+#undef Error
+#undef SocketError
+
+// ---------------------------------------------------------------------------
+#if ENABLE_UNIT_TESTS && !UNITY_XENON
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "NetworkUtility.h"
+SUITE (MulticastSocketTests)
+{
+ struct SocketFixture
+ {
+ SocketFixture()
+ {
+ NetworkInitialize();
+ };
+
+ ~SocketFixture()
+ {
+ NetworkCleanup();
+ }
+ };
+#if !UNITY_XENON
+ TEST_FIXTURE(SocketFixture, Multicast)
+ {
+ char actual[10], expected[] = "foobar";
+
+ MulticastSocket sender;
+ CHECK(sender.Initialize("225.0.0.224", 54996));
+ CHECK(sender.SetTTL(0));
+ CHECK(sender.SetLoop(true));
+
+ MulticastSocket receiver;
+ CHECK(receiver.Initialize("225.0.0.224", 54996, true));
+ CHECK(receiver.Join());
+
+ CHECK_EQUAL(sizeof(expected), sender.Send(expected, sizeof(expected)));
+ CHECK_EQUAL(sizeof(expected), receiver.Recv(actual, sizeof(actual)));
+ CHECK_EQUAL(expected, actual);
+ CHECK(receiver.Disband());
+ }
+#endif
+}
+
+#endif //ENABLE_UNIT_TESTS
+
+#endif // !UNITY_WINRT
+
+#endif // ENABLE_SOCKETS
diff --git a/Runtime/Network/MulticastSocket.h b/Runtime/Network/MulticastSocket.h
new file mode 100644
index 0000000..0b99d60
--- /dev/null
+++ b/Runtime/Network/MulticastSocket.h
@@ -0,0 +1,59 @@
+#ifndef MULTICASTSOCKET_H
+#define MULTICASTSOCKET_H
+
+#if ENABLE_SOCKETS
+#include "Sockets.h"
+
+#if UNITY_WINRT
+namespace UnityPlayer
+{
+ [Windows::Foundation::Metadata::WebHostHidden]
+ public ref class MulticastSocketContext sealed
+ {
+ public:
+ MulticastSocketContext( Windows::Networking::HostName^ host, Platform::String^ port );
+
+ void OnSocketMessageReceived( Windows::Networking::Sockets::DatagramSocket^ dagSocket, Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs^ args);
+ Windows::Networking::Sockets::DatagramSocket^ GetSocket() { return dagSocket; }
+ void Bind();
+ void Send(const Platform::Array<byte>^ dataToSend);
+ void SetLoop(bool loop);
+
+ private:
+ Windows::Networking::Sockets::DatagramSocket^ dagSocket;
+ Windows::Networking::HostName^ hostName;
+ Platform::String^ portNumber;
+ bool isLoopingBroadcast;
+ };
+}
+#endif // UNITY_WINRT
+
+class MulticastSocket : protected Socket
+{
+public:
+ MulticastSocket();
+
+ bool Initialize(const char* group, unsigned short port, bool block = false);
+ bool Join();
+ bool Disband();
+ bool SetBroadcast(bool broadcast);
+ bool SetTTL(unsigned char ttl);
+ bool SetLoop(bool loop);
+ int Send(const void* data, size_t data_len);
+ int Recv(void* data, size_t data_len, RecvUserData* userData = NULL);
+
+private:
+#if UNITY_EDITOR
+ bool SetOptionForAllAddresses(int option, const char* msg = NULL);
+#endif
+ bool m_Bound;
+ struct sockaddr_in m_MulticastAddress;
+#if UNITY_WINRT
+ UnityPlayer::MulticastSocketContext^ m_context;
+ bool m_initalised;
+#endif
+};
+
+
+#endif
+#endif
diff --git a/Runtime/Network/NetworkEnums.h b/Runtime/Network/NetworkEnums.h
new file mode 100644
index 0000000..55a9c13
--- /dev/null
+++ b/Runtime/Network/NetworkEnums.h
@@ -0,0 +1,196 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+#include "Runtime/BaseClasses/MessageIdentifier.h"
+#include "External/RakNet/builds/include/BitStream.h"
+#include "External/RakNet/builds/include/MessageIdentifiers.h"
+#endif
+
+#include "NetworkViewID.h"
+#include "Runtime/Threads/Mutex.h"
+
+enum { /*kMaxOrderChannels = 32, */kDefaultChannel = 0, kInternalChannel = 0, kMaxGroups = 32 };
+enum { kUndefindedPlayerIndex = -1 };
+// Default timeout when disconnecting
+enum { kDefaultTimeout = 200 };
+
+//typedef int NetworkViewID;
+typedef SInt32 NetworkPlayer;
+
+struct RPCMsg
+{
+ std::string name; // name of the function
+ NetworkViewID viewID; // view id
+ int sender; // player index of the sender
+ int group;
+ RakNet::BitStream* stream;
+};
+
+struct PlayerTable
+{
+ int playerIndex;
+ // Index for checking what players have received initial state updates, must match the m_InitReceived flag in the network views.
+ unsigned int initIndex;
+ SystemAddress playerAddress;
+ UInt32 mayReceiveGroups;
+ UInt32 maySendGroups;
+ bool isDisconnected;
+ bool relayed;
+ std::string guid;
+};
+
+struct NetworkMessageInfo
+{
+ double timestamp;
+ int sender;
+ NetworkViewID viewID;
+};
+
+// Peer type
+enum {
+ kDisconnected = 0,
+ kServer = 1,
+ kClient = 2,
+};
+
+// RPC modes
+enum {
+/*
+ self
+ buffer
+ server
+ others
+ immediate
+*/
+ /// The first 2 bits are used for the target
+ kServerOnly = 0,
+ kOthers = 1,
+ kAll = 2,
+ kSpecificTarget = 3,
+ kTargetMask = 3,
+ // The third bit is used for buffering or not
+ kBufferRPCMask = 4,
+ kRPCModeNbBits = 3
+};
+
+inline UInt32 GetTargetMode (UInt32 mode)
+{
+ return mode & kTargetMask;
+}
+
+enum { kChannelCompressedBits = 5 }; //0-32
+
+// Debug level
+enum {
+ kImportantErrors = 0,
+ kInformational = 1,
+ kCompleteLog = 2
+};
+
+enum {
+ kPlayerIDBase = 10000000
+};
+
+enum NetworkSimulation {
+ kNoSimulation = 0,
+ kBroadband = 1,
+ kDSL = 2,
+ kISDN = 3,
+ kDialUp = 4
+};
+
+enum {
+ kAlreadyConnectedToOtherServer = -1,
+ kFailedToCreatedSocketOrThread = -2,
+ kIncorrectParameters = -3,
+ kEmptyConnectTarget = -4,
+ kInternalDirectConnectFailed = -5,
+ kUnkownGameType = -6,
+ kCannotConnectToGUIDWithoutNATEnabled = -7
+};
+
+// Connection Tester status enums
+enum {
+ kConnTestError = -2,
+ /// Test result undetermined, still in progress.
+ kConnTestUndetermined = -1,
+ /// Private IP address detected which cannot do NAT punchthrough.
+ kPrivateIPNoNATPunchthrough = 0,
+ /// Private IP address detected which can do NAT punchthrough.
+ kPrivateIPHasNATPunchThrough = 1,
+ /// Public IP address detected and game listen port is accessible to the internet.
+ kPublicIPIsConnectable = 2,
+ /// Public IP address detected but the port it's not connectable from the internet.
+ kPublicIPPortBlocked = 3,
+ /// Public IP address detected but server is not initialized and no port is listening.
+ kPublicIPNoServerStarted = 4,
+ /// Port-restricted NAT type, can do NAT punchthrough to everyone except symmetric.
+ kLimitedNATPunchthroughPortRestricted = 5,
+ /// Symmetric NAT type, cannot do NAT punchthrough to other symmetric types nor port restricted type.
+ kLimitedNATPunchthroughSymmetric = 6,
+ /// Full cone type, NAT punchthrough fully supported.
+ kNATpunchthroughFullCone = 7,
+ /// Address-restricted cone type, NAT punchthrough fully supported.
+ kNATpunchthroughAddressRestrictedCone = 8
+};
+
+enum {
+ kRegistrationFailedGameName = 0,
+ kRegistrationFailedGameType = 1,
+ kRegistrationFailedNoServer = 2,
+ kRegistrationSucceeded = 3,
+ kHostListReceived = 4
+};
+
+enum {
+ kConnTestTimeout = 60
+};
+
+// Network packet types
+enum {
+ ID_STATE_UPDATE
+
+#if ENABLE_NETWORK
+ = ID_USER_PACKET_ENUM // 127
+#endif
+ ,
+
+ ID_STATE_INITIAL,
+ ID_CLIENT_INIT,
+ ID_REMOVE_RPCS,
+ ID_REQUEST_CLIENT_INIT,
+ ID_PROXY_INIT_MESSAGE,
+ ID_PROXY_CLIENT_MESSAGE,
+ ID_PROXY_SERVER_MESSAGE,
+ ID_PROXY_MESSAGE,
+ ID_PROXY_SERVER_INIT,
+ // Master server specific network messages. This must be reflected in Tools/MasterServer/MasterServerMessages.h
+ ID_DATABASE_ROWID = 200,
+ ID_MASTERSERVER_REDIRECT,
+ ID_MASTERSERVER_MSG
+};
+
+// NetworkViewIDAllocator enums
+enum { kDefaultViewIDBatchSize = 50, kMinimumViewIDs = 100 };
+
+inline double TimestampToSeconds (RakNetTime time)
+{
+ return (double)time / 1000.0;
+}
+
+// Host data used with the master server
+struct HostData
+{
+ int useNat;
+ std::string gameType;
+ std::string gameName;
+ int connectedPlayers;
+ int playerLimit;
+ std::vector<std::string> IP;
+ int port;
+ bool passwordProtected;
+ std::string comment;
+ std::string guid;
+};
diff --git a/Runtime/Network/NetworkManager.cpp b/Runtime/Network/NetworkManager.cpp
new file mode 100644
index 0000000..0db2d83
--- /dev/null
+++ b/Runtime/Network/NetworkManager.cpp
@@ -0,0 +1,2825 @@
+#include "UnityPrefix.h"
+#include "NetworkManager.h"
+
+#if !UNITY_IPHONE && !UNITY_ANDROID && !UNITY_EDITOR
+// TODO: what's a sensible default for pepper? web player?
+NetworkReachability GetInternetReachability ()
+{
+ return ReachableViaLocalAreaNetwork;
+}
+#endif
+
+#if ENABLE_NETWORK
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoScriptCache.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Profiler/Profiler.h"
+
+#include "PackMonoRPC.h"
+#include "MasterServerInterface.h"
+#include "External/RakNet/builds/include/RakNetworkFactory.h"
+#include "External/RakNet/builds/include/RakPeerInterface.h"
+#include "External/RakNet/builds/include/RakNetStatistics.h"
+#include "External/RakNet/builds/include/RakSleep.h"
+#include "External/RakNet/builds/include/SocketLayer.h"
+#include "Runtime/Input/InputManager.h"
+#include "BitStreamPacker.h"
+#include "Runtime/GameCode/CloneObject.h"
+#include "Runtime/Utilities/Utility.h"
+#include "Configuration/UnityConfigureVersion.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/AssetPipeline/AssetDatabase.h"
+#endif
+
+#define PACKET_LOGGER 0
+#if PACKET_LOGGER
+#include "External/RakNet/builds/include/PacketLogger.h"
+PacketLogger messageHandler;
+PacketLogger messageHandler2;
+#endif
+
+namespace {
+ const int kFacilitatorPort = 50005;
+ const int kConnectionTesterPort = 10737;
+ const int kProxyPort = 10746;
+}
+
+/*
+ * TODO: When players can take ownership of another players instantiated object then
+ * the Destory and RemoveRPC functions will no longer work. They remove
+ * based on player ID/prefix
+ *
+ * TODO: When sending timestamp from client to other clients the timestamp should be the timestamp from the client it was originally sent from not from the server.
+ * when it is forwarding the RPC.
+ *
+ * TODO: optimize sending of RPCs to only include timestamp if the receiver uses it via NetworkMessageInfo
+ */
+
+
+using namespace std;
+
+NetworkManager::NetworkManager(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_Peer = RakNetworkFactory::GetRakPeerInterface();
+ m_DebugLevel = kImportantErrors;
+ m_PeerType = kDisconnected;
+ m_Sendrate = 15.0F;
+ m_MessageQueueRunning = true;
+
+ m_Peer->RegisterAsRemoteProcedureCall("__RPCNetworkInstantiate", RPCNetworkInstantiate);
+ m_Peer->RegisterAsRemoteProcedureCall("__RPCReceiveViewIDBatch", RPCReceiveViewIDBatch);
+ m_Peer->RegisterAsRemoteProcedureCall("__RPCRequestViewIDBatch", RPCRequestViewIDBatch);
+ m_Peer->RegisterAsRemoteProcedureCall("__RPCNetworkDestroy", RPCNetworkDestroy);
+
+ m_Peer->SetOccasionalPing(true);
+ m_DoNAT = false;
+ m_MinimumAllocatableViewIDs = kMinimumViewIDs;
+ Disconnect(0);
+ m_ConnTester = NULL;
+
+ m_ServerAddress = UNASSIGNED_SYSTEM_ADDRESS;
+ m_ServerPassword = "";
+ m_FacilitatorID.binaryAddress = 0;
+ m_FacilitatorID.port = kFacilitatorPort;
+ m_ConnTesterAddress.binaryAddress = 0;
+ m_ConnTesterAddress.port = kConnectionTesterPort;
+ m_ConnStatus = kConnTestUndetermined;
+ m_MaxConnections = 0;
+ m_ProxyAddress.binaryAddress = 0;
+ m_ProxyAddress.port = kProxyPort;
+ m_UseProxy = false;
+ m_ProxyPassword = "";
+#if PACKET_LOGGER
+ m_Peer->AttachPlugin(&messageHandler);
+ messageHandler.LogHeader();
+#endif
+}
+
+NetworkManager::~NetworkManager()
+{
+ RakNetworkFactory::DestroyRakPeerInterface(m_Peer);
+
+ while (!m_PingQueue.empty())
+ {
+ m_PingQueue.back()->Release();
+ m_PingQueue.pop();
+ }
+}
+
+void NetworkManager::AddNetworkView (ListNode<NetworkView>& s)
+{
+ m_Sources.push_back(s);
+}
+
+void NetworkManager::AddAllNetworkView (ListNode<NetworkView>& s)
+{
+ m_AllSources.push_back(s);
+}
+
+void NetworkManager::AddNonSyncNetworkView (ListNode<NetworkView>& s)
+{
+ m_NonSyncSources.push_back(s);
+}
+
+void NetworkManager::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad(awakeMode);
+ #if UNITY_EDITOR
+ AssertIf (!m_AssetToPrefab.empty());
+ #endif
+
+ m_PrefabToAsset.clear();
+ for (AssetToPrefab::iterator i=m_AssetToPrefab.begin();i != m_AssetToPrefab.end();i++)
+ {
+ m_PrefabToAsset.insert(make_pair (i->second, i->first));
+ }
+}
+
+void NetworkManager::SetAssetToPrefab (const std::map<UnityGUID, PPtr<GameObject> >& mapping)
+{
+ m_AssetToPrefab = mapping;
+ m_PrefabToAsset.clear();
+ for (AssetToPrefab::iterator i=m_AssetToPrefab.begin();i != m_AssetToPrefab.end();i++)
+ m_PrefabToAsset.insert(make_pair (i->second, i->first));
+}
+
+void NetworkManager::NetworkOnApplicationQuit()
+{
+// Don't tear down network if Application.CancelQuit has been called from user scripting (only for standalones)
+#if !WEBPLUG && !UNITY_EDITOR
+ if (GetInputManager().ShouldQuit()) {
+#endif
+ // Disconnect all connected peers, no wait interval, ordering channel 0
+ Disconnect(100);
+
+ m_MinimumAllocatableViewIDs = kMinimumViewIDs;
+ SetUseNat(false);
+ m_FacilitatorID.binaryAddress = 0;
+ m_FacilitatorID.port = kFacilitatorPort;
+ m_ConnTesterAddress.binaryAddress = 0;
+ m_ConnTesterAddress.port = kConnectionTesterPort;
+ delete m_ConnTester;
+ m_ConnTester = NULL;
+ m_ConnStatus = kConnTestUndetermined;
+ m_MaxConnections = 0;
+ m_PingThread.WaitForExit();
+ m_ProxyAddress.binaryAddress = 0;
+ m_ProxyAddress.port = kProxyPort;
+ m_UseProxy = false;
+ m_ProxyPassword = "";
+#if !WEBPLUG && !UNITY_EDITOR
+ }
+#endif
+}
+
+void NetworkManager::InitializeSecurity()
+{
+ if (m_Peer->IsActive())
+ {
+ ErrorString("You may not be connected when initializing security layer.");
+ return;
+ }
+
+ m_Peer->InitializeSecurity(0,0,0,0);
+}
+
+int NetworkManager::InitializeServer(int connections, int listenPort, bool useNat)
+{
+ Disconnect(kDefaultTimeout, false);
+
+ m_MaxConnections = connections;
+ SetUseNat(useNat);
+
+ SocketDescriptor sd(listenPort,0);
+ // Add two extra connections for the master server and facilitator
+ if (!m_Peer->Startup(connections+2, 1, &sd, 1))
+ {
+ ErrorString("Failed to initialize network interface. Is the listen port already in use?");
+ return kFailedToCreatedSocketOrThread;
+ }
+ // Hide the extra connections again.
+ m_Peer->SetMaximumIncomingConnections(connections);
+
+ m_PlayerID = 0;
+ m_HighestPlayerID = 0;
+ m_SendingEnabled = 0xFFFFFFFF;
+ m_ReceivedInitialState = true;
+
+ m_NetworkViewIDAllocator.Clear(kDefaultViewIDBatchSize, m_MinimumAllocatableViewIDs, m_PlayerID, kUndefindedPlayerIndex);
+ m_NetworkViewIDAllocator.FeedAvailableBatchOnServer(m_NetworkViewIDAllocator.AllocateBatch(m_PlayerID));
+
+ AssertIf(!m_Players.empty());
+
+ m_PeerType = kServer;
+ NetworkInfo(NULL, "Running as server. Player ID is 0.");
+
+ // When running as server there is no need to go through the facilitator connection routine again. You just need to stay connected.
+ if (m_DoNAT && !m_Peer->IsConnected(m_FacilitatorID))
+ {
+ GetFacilitatorAddress(true);
+
+ // NOTE: At the moment the server peer interface maintains a constant connection to the master server for keeping the NAT
+ // port open. This isn't neccessary after clients have connected as they will maintain the port. However, if all clients disconnect for
+ // a while, the port will close sooner or later. Then the master server needs to have another port for the next client. Another
+ // reason for keeping the connection persistant is that when a new client connects and needs NAT punch through the server must already
+ // have a connection open or else the connection fails. Keeping it persistant is the most easy solution.
+ if (!m_Peer->Connect(m_FacilitatorID.ToString(false),m_FacilitatorID.port,0,0))
+ {
+ ErrorString("Failed to connect to NAT facilitator\n");
+ }
+ }
+
+ // TODO: Here we assume a connection to the proxy server cannot already be established. Could this happen?
+ if (m_UseProxy)
+ {
+ ResolveProxyAddress();
+ if (!m_Peer->Connect(m_ProxyAddress.ToString(false), m_ProxyAddress.port, m_ProxyPassword.c_str(), m_ProxyPassword.size(),0))
+ {
+ ErrorString(Format("Failed to connect to proxy server at %s.", m_ProxyAddress.ToString()));
+ }
+ }
+ else
+ {
+ SendToAllNetworkViews(kServerInitialized, m_PlayerID);
+ }
+
+ return 0;
+}
+
+// Check if the IP addresses supplied are connectable and connect to the first successful IP.
+int NetworkManager::Connect(std::vector<string> IPs, int remotePort, int listenPort, const std::string& password)
+{
+ if (IPs.size() == 1)
+ {
+ Connect(IPs[0].c_str(), remotePort, listenPort, password);
+ return 0;
+ }
+
+ if (IPs.size() == 0)
+ {
+ // Must prevent this or raknet will crash when an empty address is used
+ ErrorString("Empty host IP list given in Connect\n");
+ return kEmptyConnectTarget;
+ }
+
+ if (!password.empty()) m_ServerPassword = password;
+
+ for (int i = 0; i < IPs.size(); i++)
+ {
+ if (IPs[i] != "")
+ {
+ if (!m_Peer->IsActive())
+ {
+ SocketDescriptor sd(listenPort,0);
+ if (!m_Peer->Startup(2, 1, &sd, 1))
+ {
+ ErrorString("Failed to initialize network connection before connecting.");
+ return kFailedToCreatedSocketOrThread;
+ }
+ }
+ // Do request a reply if the system is not accepting connections, then can show the error message
+ m_Peer->Ping(IPs[i].c_str(), remotePort, false);
+ m_ConnectingAfterPing = true;
+ m_PingConnectTimestamp = time(0);
+ }
+ }
+ return 0;
+}
+
+int NetworkManager::Connect(std::string IP, int remotePort, const std::string& password)
+{
+ m_ReceivedInitialState = true;
+ SetUseNat(false);
+
+ // Connect to proxy which will from then on relay all server communication.
+ // A proxy behind NAT is not supported, the UseNAT property is ignored here but
+ // it applies to the game server the proxy server will connect to
+ if (m_UseProxy)
+ {
+ ResolveProxyAddress();
+ if (!m_Peer->Connect(m_ProxyAddress.ToString(false), m_ProxyAddress.port, m_ProxyPassword.c_str(), m_ProxyPassword.size(),0))
+ {
+ ErrorString(Format("Failed to connect to proxy server at %s\n", m_ProxyAddress.ToString()));
+ return kFailedToCreatedSocketOrThread;
+ }
+ else
+ {
+ NetworkInfo(NULL, "Sent connect request to proxy at %s\n", m_ProxyAddress.ToString());
+ }
+ // Memorize server address as we will need it later when the proxy connection is established
+ m_ServerAddress.SetBinaryAddress(IP.c_str());
+ m_ServerAddress.port = (unsigned)remotePort;
+ if (!password.empty())
+ m_ServerPassword = password;
+ }
+ else
+ {
+ if (!m_Peer->Connect(IP.c_str(), remotePort, password.c_str(), password.size()))
+ {
+ ErrorString("Failed to connect. This is caused by an incorrect parameters, internal error or too many existing connections.");
+ return kIncorrectParameters;
+ }
+ }
+ AssertIf(!m_Players.empty());
+
+ NetworkInfo(NULL, "Running as client. No player ID set yet.");
+
+ return 0;
+}
+
+void NetworkManager::ResolveProxyAddress()
+{
+ ResolveAddress(m_ProxyAddress, "proxy.unity3d.com", "proxybeta.unity3d.com",
+ "Cannot resolve proxy address, make sure you are connected to the internet before connecting to a server.");
+}
+
+// NOTE: If this client is supposed to accept connections then SetMaximumIncomingConnections
+// must be set.
+int NetworkManager::Connect(std::string IP, int remotePort, int listenPort, const std::string& password)
+{
+ Disconnect(kDefaultTimeout);
+
+ // Connect returns immediately on a successful connection ATTEMPT. IsConnected() tells you if the connection actually succeded.
+ // The network message ID_UNABLE_TO_CONNECT_TO_REMOTE_HOST if the remote hosts responds (closed port etc.)
+ // NOTE: At the moment client connections are limited to only two connections. In p2p mode each peer will need a seperate connection
+ SocketDescriptor sd(listenPort,0);
+ if (!m_Peer->Startup(2, 1, &sd, 1))
+ {
+ ErrorString("Failed to initialize network connection before connecting.");
+ return kFailedToCreatedSocketOrThread;
+ }
+
+ return Connect(IP, remotePort, password);
+}
+
+int NetworkManager::Connect(RakNetGUID serverGUID, int listenPort, const std::string& password)
+{
+ SetUseNat(true);
+ Disconnect(kDefaultTimeout);
+
+ // Connect returns immediately on a successful connection ATTEMPT. IsConnected() tells you if the connection actually succeded.
+ // The network message ID_UNABLE_TO_CONNECT_TO_REMOTE_HOST if the remote hosts responds (closed port etc.)
+ // NOTE: At the moment client connections are limited to only two connections. In p2p mode each peer will need a seperate connection
+ SocketDescriptor sd(listenPort,0);
+ if (!m_Peer->Startup(2, 1, &sd, 1))
+ {
+ ErrorString("Failed to initialize network connection before connecting.");
+ return kFailedToCreatedSocketOrThread;
+ }
+
+ ResolveFacilitatorAddress();
+
+ if (!m_Peer->Connect(m_FacilitatorID.ToString(false), m_FacilitatorID.port,0,0))
+ {
+ ErrorString(Format("Failed to connect to NAT facilitator at %s\n", m_FacilitatorID.ToString()));
+ return kFailedToCreatedSocketOrThread;
+ }
+ else
+ {
+ NetworkInfo(NULL, "Sent connect request to facilitator at %s\n", m_FacilitatorID.ToString());
+ }
+
+ if (!password.empty()) m_ServerPassword = password;
+
+ m_ServerGUID = serverGUID;
+
+ return 0;
+}
+
+// The CloseConnection function allows you to disconnect a specific playerID
+void NetworkManager::Disconnect(int timeout, bool resetParams)
+{
+ if (GetMasterServerInterfacePtr())
+ {
+ GetMasterServerInterface().UnregisterHost();
+ GetMasterServerInterface().Disconnect();
+ }
+ m_Peer->Shutdown(timeout);
+
+ if (IsServer () || IsClient())
+ SendToAllNetworkViews(kDisconnectedFromServer, ID_DISCONNECTION_NOTIFICATION);
+
+ if (resetParams)
+ {
+ m_Peer->DisableSecurity();
+ SetIncomingPassword("");
+ }
+
+ m_Players.clear();
+ m_PeerType = kDisconnected;
+ m_MessageQueueRunning = true;
+ m_SendingEnabled = 0;
+ m_LevelPrefix = 0;
+
+ for (list<RPCMsg>::iterator irpc = m_RPCBuffer.begin(); irpc != m_RPCBuffer.end(); ++irpc )
+ {
+ RPCMsg& rpc = *irpc;
+ delete rpc.stream;
+ }
+ m_RPCBuffer.clear();
+
+ m_ServerAddress = UNASSIGNED_SYSTEM_ADDRESS;
+ m_ServerPassword = "";
+ m_HighestPlayerID = 0;
+ m_PlayerID = -1;
+ m_LastSendTime = -1.0F;
+ m_ConnectingAfterPing = false;
+ m_UsedInitIndices.clear();
+
+ for( NetworkViewIterator iview = m_AllSources.begin(); iview != m_AllSources.end(); ++iview ) {
+ NetworkView& view = **iview;
+ view.ClearInitStateAndOwner();
+ }
+}
+
+void NetworkManager::CloseConnection(int target, bool sendDisconnect)
+{
+ SystemAddress address = GetSystemAddressFromIndex(target);
+ if (address != UNASSIGNED_SYSTEM_ADDRESS)
+ m_Peer->CloseConnection(address, sendDisconnect, kInternalChannel);
+ else
+ {
+ ErrorString("Couldn't close connection because the player is not connected.");
+ }
+}
+
+PROFILER_INFORMATION(gNetworkUpdateProfile, "Network.Update", kProfilerNetwork)
+
+void NetworkManager::NetworkUpdate()
+{
+ PROFILER_AUTO(gNetworkUpdateProfile, NULL)
+
+ m_Packet = NULL;
+ if (m_MessageQueueRunning)
+ m_Packet = m_Peer->ReceiveIgnoreRPC(); // Receive whole message from peer
+
+ if (m_ConnectingAfterPing && time(0) - m_PingConnectTimestamp > 5)
+ {
+ m_ConnectingAfterPing = false;
+ NetworkError (NULL, "Unable to connect internally to NAT target(s), no response.");
+ SendToAllNetworkViews(kConnectionAttemptFailed, kInternalDirectConnectFailed);
+ }
+
+ // Destroy connection tester after the timeout (test should be over) or if an error occured
+ if (m_ConnTester)
+ {
+ m_ConnStatus = m_ConnTester->Update();
+ // ATM -2 = timeout || error
+ /*if (m_ConnStatus == -2)
+ {
+ delete m_ConnTester;
+ m_ConnTester = NULL;
+ }*/
+ }
+
+ // Check if there are any pending pings
+ if (!m_PingQueue.empty())
+ {
+ if (!m_PingThread.IsRunning())
+ {
+ m_PingThread.Run(&PingImpl, (void*)m_PingQueue.front(), DEFAULT_UNITY_THREAD_STACK_SIZE, 2);
+ m_PingQueue.pop();
+ }
+ }
+
+ while (m_Packet)
+ {
+ unsigned char packetIdentifier = m_Packet->data[0];
+ if (packetIdentifier == ID_TIMESTAMP && m_Packet->length > sizeof(unsigned char) + sizeof (RakNetTime))
+ {
+ packetIdentifier = m_Packet->data[sizeof(unsigned char) + sizeof(RakNetTime)];
+ }
+
+ bool fromMasterServer = m_Packet->systemAddress == GetMasterServerInterface().GetMasterServerID();
+ if (fromMasterServer)
+ GetMasterServerInterface().ProcessPacket(m_Packet);
+ else
+ ProcessPacket(packetIdentifier);
+
+ m_Peer->DeallocatePacket(m_Packet); // Ditch message
+
+ if (m_MessageQueueRunning)
+ m_Packet = m_Peer->ReceiveIgnoreRPC(); // Check if there are other messages queued
+ else
+ m_Packet = NULL;
+ }
+ m_Packet = NULL;
+
+ if (!m_Peer->IsActive())
+ return;
+
+ float realtime = GetTimeManager().GetRealtime();
+ if (realtime > m_LastSendTime + 1.0F / m_Sendrate && GetConnectionCount() >= 1)
+ {
+ // Send updates
+ for (NetworkViewIterator i = m_Sources.begin(); i != m_Sources.end(); i++) {
+ NetworkView *view = i->GetData();
+
+ // Are we allowed to send?
+ if (m_SendingEnabled & (1 << view->GetGroup()))
+ {
+ // If running as server and one or more client is connected
+ if ( IsServer() )
+ {
+ view->SendToAllButOwner();
+ }
+ // If running as client and the views owner player address matches this one then send to server
+ else if (IsClient() && m_Peer->GetInternalID() == view->GetOwnerAddress())
+ {
+ view->Send(m_ServerAddress, false);
+ }
+ }
+ }
+
+ m_LastSendTime = realtime;
+ }
+
+ RakSleep(0);
+}
+
+void NetworkManager::ProcessPacket(unsigned char packetIdentifier)
+{
+ switch (packetIdentifier)
+ {
+ case ID_CONNECTION_REQUEST_ACCEPTED:
+ {
+ bool fromFacilitator = m_Packet->systemAddress == m_FacilitatorID;
+ bool fromProxyServer = m_Packet->systemAddress == m_ProxyAddress;
+
+ // A client doing NAT punch through to game server with help from facilitator
+ if (m_DoNAT && fromFacilitator && !IsServer())
+ {
+ NetworkInfo(NULL, "Connected to facilitator at %s\n",m_Packet->systemAddress.ToString());
+ char tmp[32];
+ strcpy(tmp, m_FacilitatorID.ToString());
+ NetworkInfo(NULL, "Doing NAT punch through to %s using %s\n", m_ServerGUID.ToString(), tmp);
+ m_NatPunchthrough.OpenNAT(m_ServerGUID, m_FacilitatorID);
+ }
+ else if (fromFacilitator && IsServer())
+ {
+ NetworkInfo(NULL, "Connected to facilitator at %s\n",m_Packet->systemAddress.ToString());
+ }
+ else if (fromProxyServer && IsServer())
+ {
+ if (!m_UseProxy)
+ {
+ ErrorString("Connected to proxy server but proxy support not enabled.");
+ return;
+ }
+ m_BitStream.Reset();
+ m_BitStream.Write((unsigned char) (ID_PROXY_SERVER_INIT));
+ m_BitStream.Write(1); // Proxy protocol version
+ if (!m_Peer->Send(&m_BitStream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ProxyAddress, false))
+ {
+ ErrorString("Failed to send server init to proxy server.");
+ }
+ else
+ {
+ NetworkInfo(NULL, "Sending init req. to proxy server at %s", m_ProxyAddress.ToString());
+ }
+ }
+ // Client connected to server (proxy or game)
+ else
+ {
+ SystemAddress target = m_Packet->systemAddress;
+ m_BitStream.Reset();
+ if (m_UseProxy)
+ {
+ m_BitStream.Write((unsigned char) (ID_PROXY_INIT_MESSAGE));
+ m_BitStream.Write(1); // Proxy protocol version
+ m_BitStream.Write(m_ServerAddress);
+ if (!m_ServerPassword.empty())
+ {
+ m_BitStream.Write1();
+ m_BitStream.Write(m_ServerPassword.size());
+ m_BitStream.Write(m_ServerPassword.c_str(), m_ServerPassword.size());
+ }
+ else
+ {
+ m_BitStream.Write0();
+ }
+ m_BitStream.Write(m_DoNAT);
+ m_BitStream.Write(1); // Network protocol version
+ target = m_ProxyAddress;
+ NetworkInfo(NULL, "Sending init req. to %s, relayed through proxy", m_ServerAddress.ToString());
+ }
+ else
+ {
+ // Specifically request init from server
+ m_BitStream.Write((unsigned char) (ID_REQUEST_CLIENT_INIT));
+ m_BitStream.Write((int)1); // Network protocol version
+ m_ServerAddress = m_Packet->systemAddress; // Record server address
+ }
+ if (!m_Peer->Send(&m_BitStream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, target, false))
+ ErrorString("Failed to send client init to server.");
+ NetworkInfo(NULL, "Connected to %s\n",m_Packet->systemAddress.ToString());
+ }
+ break;
+ }
+ case ID_NO_FREE_INCOMING_CONNECTIONS:
+ ErrorString("The system we attempted to connect to is not accepting new connections.");
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_NO_FREE_INCOMING_CONNECTIONS);
+ break;
+ case ID_RSA_PUBLIC_KEY_MISMATCH:
+ ErrorString("We preset an RSA public key which does not match what the system we connected to is using.");
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_RSA_PUBLIC_KEY_MISMATCH);
+ break;
+ case ID_INVALID_PASSWORD:
+ {
+ if (m_Packet->systemAddress == m_ProxyAddress)
+ {
+ ErrorString("The proxy server is using a password and has refused our connection because we did not set it correctly.");
+ }
+ else
+ {
+ ErrorString("The remote system is using a password and has refused our connection because we did not set the correct password.");
+ }
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_INVALID_PASSWORD);
+ break;
+ }
+ case ID_CONNECTION_ATTEMPT_FAILED:
+ {
+ ErrorString(Format("The connection request to %s failed. Are you sure the server can be connected to?", m_Packet->systemAddress.ToString()));
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_CONNECTION_ATTEMPT_FAILED);
+ break;
+ }
+ case ID_ALREADY_CONNECTED:
+ {
+ NetworkError(NULL, "Failed to connect to %s because this system is already connected. This can occur when the network connection is disconnected too quickly for the remote system to receive the disconnect notification, for example when using Network.Disconnect(0).", m_Packet->systemAddress.ToString());
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_ALREADY_CONNECTED);
+ break;
+ }
+ case ID_CONNECTION_BANNED:
+ {
+ ErrorString("We are banned from the system we attempted to connect to.");
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_CONNECTION_BANNED);
+ break;
+ }
+ case ID_NEW_INCOMING_CONNECTION:
+ {
+ // Happens during public IP firewall test
+ if (m_Packet->systemAddress == m_ConnTesterAddress)
+ {
+ if (m_ConnTester)
+ {
+ m_ConnStatus = kPublicIPIsConnectable;
+ m_ConnTester->ReportTestSucceeded();
+ }
+ return;
+ }
+
+ if (!IsServer())
+ {
+ m_Peer->CloseConnection(m_Packet->systemAddress, true);
+ NetworkError(NULL, "New incoming connection but not a server.");
+ return;
+ }
+
+ NetworkInfo(NULL, "New connection established (%s)", m_Packet->systemAddress.ToString());
+ break;
+ }
+ case ID_REQUEST_CLIENT_INIT:
+ {
+ MsgNewConnection();
+ break;
+ }
+ // TODO: Should we differentiate between these? One is expected, the other is not (important for integration tests)
+ case ID_DISCONNECTION_NOTIFICATION:
+ case ID_CONNECTION_LOST:
+ {
+ // When master server ID has been changed we need to ignore the disconnect from the old master server
+ if (m_Packet->systemAddress == m_OldMasterServerID && IsServer())
+ {
+ // Do nothing
+ }
+ // Same with the old facilitator
+ else if (m_Packet->systemAddress == m_OldFacilitatorID && IsServer())
+ {
+ // Do nothing
+ }
+ else if (m_Packet->systemAddress == m_ConnTesterAddress)
+ {
+ // Do nothing
+ }
+ // If connection was lost with the NAT facilitator we should reconnect as it must be persistent for now.
+ // If it is not persistent then the connections must be set up every time a client wants to connect (and at the moment there
+ // is no way of knowing when that is).
+ else if (m_Packet->systemAddress == m_FacilitatorID && IsServer())
+ {
+ // No need to resolve again as the above condition will never happen if the address in unresolved
+ if (!m_Peer->Connect(m_FacilitatorID.ToString(false),m_FacilitatorID.port,0,0))
+ {
+ ErrorString(Format("Failed to reconnect to NAT facilitator at %s\n", m_FacilitatorID.ToString()));
+ return;
+ }
+ else
+ {
+ LogString("Connection lost to NAT facilitator, reconnecting.\n");
+ }
+ }
+ else if (m_Packet->systemAddress == m_ProxyAddress)
+ {
+ NetworkLog(NULL, "Lost connection to proxy server at %s", m_Packet->systemAddress.ToString());
+ // If we are a client then either the proxy server connection was lost or the game server connection
+ if (IsClient())
+ {
+ //SendToAllNetworkViews(kConnectionAttemptFailed, ID_CONNECTION_ATTEMPT_FAILED);
+ SendToAllNetworkViews(kDisconnectedFromServer, m_Packet->data[0]);
+ }
+ }
+ else if (IsServer())
+ {
+ NetworkInfo(NULL, "A client has disconnected from this server. Player ID %d, IP %s", GetIndexFromSystemAddress(m_Packet->systemAddress), m_Packet->systemAddress.ToString());
+ MsgClientDidDisconnect();
+ }
+ else
+ {
+ LogString("The server has disconnected from the client. Most likely the server has shut down.");
+ ClientConnectionDisconnected(m_Packet->data[0]);
+ }
+ break;
+ }
+ case ID_STATE_UPDATE:
+ case ID_STATE_INITIAL:
+ // Normal state update, reset bitstream accordingly
+ m_BitStream.Reset();
+ m_BitStream.Write((char*)m_Packet->data, m_Packet->length);
+ MsgStateUpdate(m_Packet->systemAddress);
+ break;
+ case ID_CLIENT_INIT:
+ MsgClientInit();
+ break;
+ case ID_MODIFIED_PACKET:
+ ErrorString("A packet has been tampered with in transit.");
+ break;
+ case ID_REMOVE_RPCS:
+ MsgRemoveRPCs();
+ break;
+ // TODO: Implement other error codes: ID_NAT_TARGET_UNRESPONSIVE ID_NAT_ALREADY_IN_PROGRESS
+ case ID_NAT_PUNCHTHROUGH_SUCCEEDED:
+ {
+ unsigned char weAreTheSender = m_Packet->data[1];
+ if (weAreTheSender)
+ {
+ NetworkInfo(NULL, "Successfully performed NAT punchthrough to %s\n",m_Packet->systemAddress.ToString());
+ m_ServerAddress = m_Packet->systemAddress;
+ // TODO: Maybe we should be going through Connect just so we always go through that function.
+ if (!m_Peer->Connect(m_ServerAddress.ToString(true), m_ServerAddress.port, m_ServerPassword.c_str(), m_ServerPassword.size()))
+ {
+ ErrorString("Failed to connect. This is caused by an incorrect parameters, internal error or too many existing connections.");
+ }
+ }
+ else
+ {
+ NetworkInfo(NULL, "Successfully accepted NAT punchthrough connection from %s\n",m_Packet->systemAddress.ToString());
+ }
+ break;
+ }
+ case ID_NAT_TARGET_NOT_CONNECTED:
+ {
+ RakNetGUID remoteGuid;
+ SystemAddress systemAddress;
+ RakNet::BitStream b(m_Packet->data, m_Packet->length, false);
+ b.IgnoreBits(8); // Ignore the ID_...
+ b.Read(remoteGuid);
+
+ // TODO: Lookup IP/port of the guid we were trying to connect to
+
+ // SystemAddress.ToString() cannot be used twice in the same line because it uses a static char array
+ ErrorString(Format("NAT target %s not connected to NAT facilitator %s\n", remoteGuid.ToString(), m_Packet->systemAddress.ToString()));
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_NAT_TARGET_NOT_CONNECTED);
+ break;
+ }
+ case ID_NAT_CONNECTION_TO_TARGET_LOST:
+ {
+ RakNetGUID remoteGuid;
+ SystemAddress systemAddress;
+ RakNet::BitStream b(m_Packet->data, m_Packet->length, false);
+ b.IgnoreBits(8); // Ignore the ID_...
+ b.Read(remoteGuid);
+
+ // TODO: Lookup IP/port of the guid we were trying to connect to
+
+ ErrorString(Format("Connection to target %s lost\n", systemAddress.ToString()));
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_NAT_CONNECTION_TO_TARGET_LOST);
+ break;
+ }
+ case ID_NAT_PUNCHTHROUGH_FAILED:
+ {
+ bool isConnecting = false;
+ RakNet::BitStream b(m_Packet->data, m_Packet->length, false);
+ b.IgnoreBits(8); // Ignore the ID_...
+ b.Read(isConnecting);
+ if (isConnecting)
+ {
+ ErrorString(Format("Initiating NAT punchthrough attempt to target %s failed\n", m_Packet->guid.ToString()));
+ SendToAllNetworkViews(kConnectionAttemptFailed, ID_NAT_PUNCHTHROUGH_FAILED);
+ }
+ else
+ {
+ ErrorString(Format("Receiving NAT punchthrough attempt from target %s failed\n", m_Packet->guid.ToString()));
+ // No need to report the failed connection attempt on the receiver
+ //SendToAllNetworkViews(kConnectionAttemptFailed, ID_NAT_PUNCHTHROUGH_FAILED);
+ }
+ break;
+ }
+ case ID_PONG:
+ {
+ LogString(Format("Received pong from %s", m_Packet->systemAddress.ToString()));
+ if (m_ConnectingAfterPing)
+ {
+ m_ConnectingAfterPing = false;
+ Connect(m_Packet->systemAddress.ToString(false), m_Packet->systemAddress.port, m_ServerPassword);
+ }
+ break;
+ }
+ case ID_PING:
+ // Do nothing, this should be handled internally by RakNet (actually shouldn't be here)
+ break;
+ case ID_RPC:
+ {
+ char *message = NULL;
+ message = m_Peer->HandleRPCPacket( ( char* ) m_Packet->data, m_Packet->length, m_Packet->systemAddress );
+ if (message != NULL)
+ {
+ NetworkError(NULL, "%s", message);
+ }
+ break;
+ }
+ case ID_PROXY_SERVER_INIT:
+ {
+ int proxyVer;
+ unsigned short relayPort;
+ RakNet::BitStream b(m_Packet->data, m_Packet->length, false);
+ b.IgnoreBits(8);
+ b.Read(proxyVer); // Proxy protocol version
+ b.Read(relayPort);
+ NetworkLog(NULL, "Proxy version %d", proxyVer);
+
+ if (relayPort > 0)
+ {
+ m_RelayPort = relayPort;
+ NetworkLog(NULL, "Successfully initialized relay service from proxy server. Using port %u.", relayPort);
+ SendToAllNetworkViews(kServerInitialized, -2);
+ }
+ else if (relayPort == 0)
+ {
+ NetworkLog(NULL, "Failed to initialize relay service from proxy server. No available ports reported.");
+ SendToAllNetworkViews(kServerInitialized, -1);
+ }
+ else
+ {
+ NetworkLog(NULL, "Failed to initialize relay service from proxy server. Unkown error.");
+ SendToAllNetworkViews(kServerInitialized, -1);
+ }
+ break;
+ }
+ case ID_PROXY_MESSAGE:
+ {
+ SystemAddress senderAddress;
+ unsigned char messageID;
+ int proxyVer = 0;
+ m_BitStream.Reset();
+ m_BitStream.Write((char*)m_Packet->data, m_Packet->length);
+ m_BitStream.IgnoreBits(8);
+
+ m_BitStream.Read(senderAddress);
+ int proxiedMessageID = sizeof(MessageID) + SystemAddress::size();
+ messageID = m_Packet->data[proxiedMessageID];
+
+ // Record the proxy system address for future reference
+ // TODO: With this method, only one proxy is allowed. Enforce it? Proxy address could be added to player structure.
+ m_ProxyAddress = m_Packet->systemAddress;
+
+ // If its a time stamp then we need to dig deeper and find out what it really is.
+ // If there is a timestamp embedded, the real ID is at:
+ if (messageID == ID_TIMESTAMP)
+ {
+ int timestampedProxiedMessageID = proxiedMessageID + sizeof(MessageID) + sizeof(RakNetTime);
+ messageID = m_Packet->data[timestampedProxiedMessageID];
+ }
+
+ switch (messageID)
+ {
+ case ID_DISCONNECTION_NOTIFICATION:
+ {
+ int playerID = GetIndexFromSystemAddress(senderAddress);
+ NetworkInfo(NULL, "A proxied client has disconnected from this server. Player ID %d, IP %s", playerID, senderAddress.ToString());
+ MsgClientDidDisconnect(senderAddress);
+ break;
+ }
+ break;
+ case ID_REQUEST_CLIENT_INIT:
+ m_BitStream.IgnoreBits(8);
+ m_BitStream.Read(proxyVer); // Proxy protocol version
+ NetworkLog(NULL, "Proxy version %d", proxyVer);
+ MsgNewConnection(senderAddress);
+ break;
+ // State update from client
+ case ID_STATE_INITIAL:
+ case ID_STATE_UPDATE:
+ NetworkLog(NULL, "State update from client %s, through proxy", senderAddress.ToString());
+ MsgStateUpdate(senderAddress);
+ break;
+ case ID_RPC:
+ {
+ char *message = NULL;
+ // When passing packet data, skip over proxy data (ID+sender address)
+ message = m_Peer->HandleRPCPacket( ( char* ) m_Packet->data+7, m_Packet->length-7, senderAddress );
+ if (message != NULL)
+ NetworkError(NULL, "Error receiving proxy RPC: %s", message);
+ }
+ break;
+ default:
+ NetworkError(NULL, "Unhandled relayed message %d from %s", messageID, m_Packet->systemAddress.ToString());
+ break;
+ }
+ break;
+ }
+ default:
+ NetworkError(NULL, "Unhandled message %d from %s", (int)m_Packet->data[0], m_Packet->systemAddress.ToString());
+ break;
+ }
+}
+
+string NetworkManager::GetStats(int i)
+{
+ char buf[8000];
+ string output;
+ if (IsServer())
+ {
+ RakNetStatistics* stats = m_Peer->GetStatistics(m_Players[i].playerAddress);
+ if (stats)
+ {
+ StatisticsToString(stats, buf, 1);
+ output += Format("Player %d\n", i);
+ output += buf;
+ }
+ }
+ else if (IsClient())
+ {
+ RakNetStatistics* serverStats = m_Peer->GetStatistics(m_ServerAddress);
+ if (serverStats)
+ {
+ StatisticsToString(serverStats, buf, 1);
+ output += buf;
+ }
+ }
+ return output;
+}
+
+void NetworkManager::ClientConnectionDisconnected(int msgType)
+{
+ SendToAllNetworkViews(kDisconnectedFromServer, msgType);
+ m_PeerType = kDisconnected;
+ m_Players.clear();
+}
+
+
+void NetworkManager::MsgNewConnection(SystemAddress clientAddress)
+{
+ NetworkPlayer playerID = ++m_HighestPlayerID;
+ int networkProtocolVer = 0;
+
+ m_BitStream.Read(networkProtocolVer);
+
+ NetworkLog(NULL, "Network protocol version %d connected", networkProtocolVer);
+
+ m_BitStream.Reset();
+
+ // Record the address of this new player
+ PlayerTable player;
+ player.playerIndex = playerID;
+ player.initIndex = GetValidInitIndex();
+ player.mayReceiveGroups = 0xFFFFFFFF;
+ player.isDisconnected = false;
+ player.maySendGroups = 0xFFFFFFFF;
+ player.guid = m_Packet->guid.ToString();
+ if (clientAddress != UNASSIGNED_SYSTEM_ADDRESS)
+ {
+ player.playerAddress = clientAddress;
+ player.relayed = true;
+ NetworkInfo(NULL, "Registering new proxied client %s", clientAddress.ToString());
+ m_BitStream.Write((unsigned char)ID_PROXY_SERVER_MESSAGE);
+ m_BitStream.Write(clientAddress);
+ }
+ else
+ {
+ player.playerAddress = m_Packet->systemAddress;
+ player.relayed = false;
+ }
+ m_Players.push_back(player);
+
+ m_BitStream.Write((unsigned char) (ID_CLIENT_INIT));
+ m_BitStream.Write(1); // Server version
+ m_BitStream.Write(m_PlayerID);
+ m_BitStream.Write(playerID);
+
+ // Send Network allocator batch data
+ UInt32 batchSize = m_NetworkViewIDAllocator.GetBatchSize();
+ UInt32 nbBatches = ((m_MinimumAllocatableViewIDs - 1) / batchSize) + 1;
+ m_BitStream.Write(batchSize);
+ m_BitStream.Write(nbBatches);
+ for (int i=0;i<nbBatches;i++)
+ {
+ UInt32 batch = m_NetworkViewIDAllocator.AllocateBatch(playerID);
+ m_BitStream.Write(batch);
+ }
+ NetworkLog(NULL, "Allocated %d batches of size %d for player %d", nbBatches, batchSize, playerID);
+
+ // Send init data only to client which connected
+ if (!m_Peer->Send(&m_BitStream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_Packet->systemAddress, false))
+ {
+ ErrorString("Failed to send initialization message to new client");
+ }
+ else
+ {
+ NetworkInfo(NULL, "Sent initalization to player %d", playerID);
+ }
+
+ // Send buffered RPCs
+ SendRPCBuffer(player);
+
+ SendToAllNetworkViews(kPlayerConnected, playerID);
+}
+
+void NetworkManager::MsgClientInit()
+{
+ int serverVersion = 0;
+ NetworkPlayer serverId = 0;
+
+ // Extract player ID
+ m_BitStream.Reset();
+ m_BitStream.Write((char*)m_Packet->data, m_Packet->length);
+ m_BitStream.IgnoreBits(8);
+ m_BitStream.Read(serverVersion);
+ m_BitStream.Read(serverId);
+ m_BitStream.Read(m_PlayerID);
+
+ // Setup network view id allocator
+ // Feed initial available batches
+ UInt32 batchSize = 0;
+ m_BitStream.Read(batchSize);
+ UInt32 batchCount = 0;
+ m_BitStream.Read(batchCount);
+ m_NetworkViewIDAllocator.Clear(batchSize, m_MinimumAllocatableViewIDs, serverId, m_PlayerID);
+ for (int i=0;i<batchCount;i++)
+ {
+ UInt32 batch = 0;
+ m_BitStream.Read(batch);
+ m_NetworkViewIDAllocator.FeedAvailableBatchOnClient(batch);
+ }
+
+ // Register the server as a player in the player table. Note that the server address is already in m_ServerAddress
+ PlayerTable player;
+ player.playerIndex = serverId;
+ player.initIndex = 0;
+ player.playerAddress = m_Packet->systemAddress; // This is the proxy address when proxy is in use
+ player.mayReceiveGroups = 0xFFFFFFFF;
+ player.maySendGroups = 0xFFFFFFFF;
+ player.isDisconnected = false;
+ player.relayed = false;
+ m_Players.push_back(player);
+
+ m_PeerType = kClient;
+ m_SendingEnabled = 0xFFFFFFFF;
+
+ // Set the proxy address as the server address if appropriate or network loop will break
+ if (m_UseProxy)
+ m_ServerAddress = m_ProxyAddress;
+
+ SendToAllNetworkViews(kConnectedToServer);
+
+ NetworkInfo(NULL,"Set player ID to %d\n", m_PlayerID);
+}
+
+void NetworkManager::RPCReceiveViewIDBatch (RPCParameters *rpcParameters)
+{
+ NetworkManager& nm = GetNetworkManager();
+ RakNet::BitStream bitStream;
+ bitStream.Write((char*)rpcParameters->input, BITS_TO_BYTES(rpcParameters->numberOfBitsOfData));
+ UInt32 batchIndex;
+ if (bitStream.Read(batchIndex) && rpcParameters->sender == nm.m_ServerAddress)
+ {
+ nm.m_NetworkViewIDAllocator.FeedAvailableBatchOnClient(batchIndex);
+ nm.m_NetworkViewIDAllocator.AddRequestedBatches(-1);
+ }
+ else
+ {
+ NetworkError (NULL, "Failed receiving RPC batch index");
+ }
+}
+
+void NetworkManager::RPCRequestViewIDBatch (RPCParameters *rpcParameters)
+{
+ NetworkManager& nm = GetNetworkManager();
+ NetworkPlayer player = nm.GetIndexFromSystemAddress(rpcParameters->sender);
+ if (player != kUndefindedPlayerIndex)
+ {
+ UInt32 batchIndex = nm.m_NetworkViewIDAllocator.AllocateBatch(player);
+
+ RakNet::BitStream bitStream;
+ bitStream.Write(batchIndex);
+
+ if (!nm.m_Peer->RPC("__RPCReceiveViewIDBatch", &bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, rpcParameters->sender, false, NULL, UNASSIGNED_NETWORK_ID, 0))
+ NetworkError(NULL, "Failed to send RPC batch to client!");
+ else
+ NetworkLog(NULL, "Sent batch %d of size %d to %d\n", batchIndex, nm.m_NetworkViewIDAllocator.GetBatchSize(), player);
+ }
+ else
+ {
+ NetworkError(NULL, "Failed to send RPC batch to because he is not in the player list!");
+ }
+}
+
+void NetworkManager::SendRPCBuffer (PlayerTable &player)
+{
+ RakNetTime time = GetTimestamp();
+
+ /// Send the whole RPC buffer to the player
+ list<RPCMsg>::iterator i;
+ for( i = m_RPCBuffer.begin(); i != m_RPCBuffer.end(); i++ )
+ {
+ RPCMsg& rpc = *i;
+ // Send RPC call just to client which requested RPC flush
+
+ if (player.relayed)
+ {
+ if (!m_Peer->RPC((char*)rpc.name.c_str(), rpc.stream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ProxyAddress, false, &time, UNASSIGNED_NETWORK_ID, 0, (unsigned char) ID_PROXY_SERVER_MESSAGE, player.playerAddress))
+ ErrorString("Couldn't send buffered RPCs to proxied client");
+ }
+ else
+ {
+ if (!m_Peer->RPC((char*)rpc.name.c_str(), rpc.stream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, player.playerAddress, false, &time, UNASSIGNED_NETWORK_ID, 0))
+ ErrorString("Couldn't send buffered RPCs to client");
+ }
+
+ /* ***** needs to be updated to match new rpc signature
+
+ // DEBUG, print out info on buffered rpc call, erase eventually
+ if (m_DebugLevel >= kInformational)
+ {
+ int parentID, newID, mode;
+ i->stream->ResetReadPointer();
+ i->stream->Read(parentID);
+ i->stream->Read(mode);
+ i->stream->Read(newID);
+ LogString(Format("Sending buffered RPC \"%s\" to new client parent ID %d, inst ID %d, mode %d\n", i->name.c_str(), parentID, newID, mode));
+ }
+ */
+ }
+}
+
+bool NetworkManager::MayReceiveFromPlayer( SystemAddress adress, int group )
+{
+ PlayerTable* player = GetPlayerEntry( adress );
+ if( player )
+ {
+ return player->mayReceiveGroups & (1 << group);
+ }
+ else
+ {
+ //ErrorString( "May not receive channel from player which does not exist" );
+ ///@TODO: ERROR ON THIS AND PREVENT SENDING!!!
+
+ return false;
+ }
+}
+
+bool NetworkManager::MaySendToPlayer( SystemAddress address, int group )
+{
+ PlayerTable* player = GetPlayerEntry( address );
+ if( player )
+ {
+ return player->maySendGroups & (1 << group);
+ }
+ else
+ {
+ NetworkInfo(NULL,"NetworkPlayer instance not found for address %s, probably not connected", address.ToString());
+ return false;
+ }
+}
+
+
+void NetworkManager::SetReceivingGroupEnabled (int playerIndex, int group, bool enabled)
+{
+ PlayerTable* player = GetPlayerEntry(playerIndex);
+ if (player)
+ {
+ if (enabled)
+ player->mayReceiveGroups |= 1 << group;
+ else
+ player->mayReceiveGroups &= ~(1 << group);
+ }
+ else
+ {
+ ErrorString("SetReceivingEnabled failed because the player is not connected.");
+ }
+}
+
+void NetworkManager::SetSendingGroupEnabled (int group, bool enabled)
+{
+ if (enabled)
+ m_SendingEnabled |= 1 << group;
+ else
+ m_SendingEnabled &= ~(1 << group);
+}
+
+void NetworkManager::SetSendingGroupEnabled (int playerIndex, int group, bool enabled)
+{
+ PlayerTable* player = GetPlayerEntry(playerIndex);
+ if (player)
+ {
+ if (enabled) {
+ NetworkInfo(NULL, "Enabling sending group %d for player %d", group, playerIndex);
+ player->maySendGroups |= 1 << group;
+ }
+ else {
+ NetworkInfo(NULL, "Disabling sending group %d for player %d", group, playerIndex);
+ player->maySendGroups &= ~(1 << group);
+ }
+ }
+ else
+ {
+ ErrorString("SetSendingEnabled failed because the player is not connected.");
+ }
+}
+
+void NetworkManager::MsgStateUpdate(SystemAddress senderAddress)
+{
+ // TODO: Maybe do some integrity checking and such to make sure
+ // there are no inconsistencies like two objects having the same
+ // network ID or one ID not being found on the receiver, etc.
+
+ //m_BitStream.Reset();
+ //m_BitStream.Write((char*)m_Packet->data, m_Packet->length);
+ // IMPORTANT: The bitstream must have the read pointer at the correct position
+
+ UInt8 msgType;
+ m_BitStream.Read(msgType);
+
+ NetworkMessageInfo info;
+ info.timestamp = -1.0F;
+ if (msgType == ID_TIMESTAMP)
+ {
+ RakNetTime timeStamp = 0;
+ if (m_BitStream.Read(timeStamp))
+ info.timestamp = TimestampToSeconds(timeStamp);
+ m_BitStream.Read(msgType);
+ }
+
+ NetworkViewID viewID;
+ viewID.Read(m_BitStream);
+
+ info.viewID = viewID;
+ info.sender = GetIndexFromSystemAddress(senderAddress);
+ AssertIf(info.sender == -1);
+
+ NetworkView* view = ViewIDToNetworkView(viewID);
+ if (view != NULL)
+ {
+ if (MayReceiveFromPlayer (senderAddress, view->GetGroup()))
+ {
+ SystemAddress owner = view->GetOwnerAddress();
+ // Check incoming packets if the sender is allowed to send them.
+ if (m_PeerType == kClient)
+ {
+ if (owner.binaryAddress != 0)
+ {
+ NetworkError (NULL, "State update for an object this players owns has been received. Packet was ignored.");
+ return;
+ }
+
+ // If the client has useProxy enabled then all state updates should come from the proxy.
+ if (m_UseProxy)
+ {
+ if (m_ProxyAddress != m_Packet->systemAddress)
+ {
+ NetworkError (NULL, "State update was received from someone else than the server. Packet was ignored. Sender was %s", m_Packet->systemAddress.ToString());
+ return;
+ }
+ }
+ else if (m_Packet->systemAddress != m_ServerAddress)
+ {
+ NetworkError (NULL, "State update was received from someone else than the server. Packet was ignored. Sender was %s", m_Packet->systemAddress.ToString());
+ return;
+ }
+ }
+ // This check is not valid with proxy servers in existance
+ // And for server
+ // We could lookup the system address in the player table list and check is it is supposed to be relayed
+ // and that the sender address is the proxy server
+ /*else
+ {
+ if ( owner != m_Packet->systemAddress )
+ {
+ NetworkError (NULL, "State update for an object received that is not owned by the sender. Packet was ignored. Sender was %s", m_Packet->systemAddress.ToString());
+ return;
+ }
+ }*/
+
+ view->Unpack(m_BitStream, info, msgType);
+ }
+ else
+ {
+ NetworkInfo(view, "Received state update for view '%s' and ignored it because the channel %d is disabled.\n", viewID.ToString().c_str(), view->GetGroup());
+ }
+ }
+ else
+ {
+ NetworkWarning(NULL, "Received state update for view id' %s' but the NetworkView doesn't exist", viewID.ToString().c_str());
+ }
+}
+
+void NetworkManager::DestroyPlayerObjects(NetworkPlayer playerID)
+{
+ if (IsClient() && playerID != GetPlayerID())
+ {
+ NetworkError(NULL, "A client can only destroy his own player objects, %d is a remote player", playerID);
+ return;
+ }
+
+ NetworkInfo(NULL, "Destroying objects belonging to player %d",playerID);
+
+ bool erased = false;
+
+ for (int s=0;s<2;s++)
+ {
+ NetworkViewList& list = s == 0 ? m_Sources : m_NonSyncSources;
+
+ SafeIterator<NetworkViewList> i (list);
+ while (i.Next())
+ {
+ NetworkView& view = *i->GetData();
+ NetworkViewID viewID = NetworkViewToViewID(&view);
+ if (WasViewIdAllocatedByPlayer(viewID, playerID))
+ {
+ DestroyDelayed(viewID);
+ erased = true;
+ }
+ }
+ }
+
+ if (!erased)
+ {
+ LogString(Format("No objects for the given player ID were deleted %d", (int)playerID));
+ }
+}
+
+void NetworkManager::DestroyDelayed(NetworkViewID viewID)
+{
+ if (m_DebugLevel >= kInformational) LogString(Format("Destroying object with view ID '%s'", viewID.ToString().c_str()));
+
+ // Destroy object locally
+ NetworkView* view = ViewIDToNetworkView(viewID);
+ if (view)
+ {
+ Scripting::DestroyObjectFromScripting(&view->GetGameObject(), -1.0F);
+ }
+ else
+ {
+ ErrorString("Couldn't destroy object because the associate network view was not found");
+ return;
+ }
+
+ // Destroy remotely
+ m_BitStream.Reset();
+ viewID.Write(m_BitStream);
+
+ if (IsClient())
+ {
+ // NOTE: It is assumed that the server is the first entry in the player table
+ PerformRPCSpecificTarget("__RPCNetworkDestroy", GetPlayerEntry(0), m_BitStream, view->GetGroup());
+ }
+ else
+ {
+ BroadcastRPC ("__RPCNetworkDestroy", &m_BitStream, HIGH_PRIORITY, UNASSIGNED_SYSTEM_ADDRESS, NULL, view->GetGroup());
+ }
+}
+
+void NetworkManager::RPCNetworkDestroy(RPCParameters *rpcParameters)
+{
+ NetworkManager& nm = GetNetworkManager();
+ RakNet::BitStream stream (rpcParameters->input, BITS_TO_BYTES(rpcParameters->numberOfBitsOfData), false);
+
+ NetworkViewID viewID;
+ viewID.Read(stream);
+
+ NetworkLog(NULL,"Network destroying view ID '%s'", viewID.ToString().c_str());
+
+ NetworkView *view = nm.ViewIDToNetworkView(viewID);
+ int group = 0;
+ if (view)
+ {
+ group = view->GetGroup();
+ Scripting::DestroyObjectFromScripting(&view->GetGameObject(), -1.0F);
+ }
+ else
+ NetworkError (NULL, "Couldn't perform remote Network.Destroy because the network view '%s' could not be located.", viewID.ToString().c_str());
+
+ stream.ResetReadPointer();
+
+ // If running as server relay the message to all except owner
+ if (nm.IsServer())
+ {
+ nm.BroadcastRPC ("__RPCNetworkDestroy", &stream, HIGH_PRIORITY, rpcParameters->sender, NULL, group);
+ }
+}
+
+void NetworkManager::SetMessageQueueRunning(bool run)
+{
+ m_MessageQueueRunning = run;
+}
+
+void NetworkManager::RegisterRPC(const char* reg, void ( *functionPointer ) ( RPCParameters *rpcParms ))
+{
+ m_Peer->RegisterAsRemoteProcedureCall(const_cast<char*> (reg), functionPointer);
+}
+
+void NetworkManager::PerformRPC(const std::string &function, int mode, RakNet::BitStream& parameters, NetworkViewID viewID, UInt32 group)
+{
+ char* name = const_cast<char*>(function.c_str());
+ RakNetTime time = GetTimestamp();
+
+ // Also check for send permission on client, he can block the server if he wants to
+ if (m_PeerType == kClient)
+ {
+ if (m_UseProxy && MaySendToPlayer(m_ServerAddress, group))
+ {
+ NetworkLog(NULL, "Performing proxied RPC '%s' to server %s", function.c_str(), m_ServerAddress.ToString());
+ // Send to proxy, the server address is actually not used, the proxy knows what server the client is using
+ if(!m_Peer->RPC(name, &parameters, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ProxyAddress, false, &time, UNASSIGNED_NETWORK_ID,0, (unsigned char) ID_PROXY_CLIENT_MESSAGE, m_ServerAddress))
+ {
+ NetworkError(NULL, "Couldn't send proxied RPC function '%s' to proxy server\n", name);
+ }
+ }
+ // Send only to registered server address with no ordering, no timestamp and no reply
+ else if(!m_Peer->RPC(name, &parameters, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ServerAddress, false, &time, UNASSIGNED_NETWORK_ID,0))
+ {
+ NetworkError(NULL, "Couldn't send RPC function '%s' to server\n", name);
+ }
+ }
+ else if (m_PeerType == kServer)
+ {
+ BroadcastRPC(name, &parameters, HIGH_PRIORITY, UNASSIGNED_SYSTEM_ADDRESS, &time, group);
+
+ NetworkInfo(NULL, "Sent RPC call '%s' to all connected clients\n", name);
+
+ if (mode & kBufferRPCMask)
+ {
+ AssertIf(GetTargetMode(mode) != kOthers && GetTargetMode(mode) != kAll);
+ //printf_console("DEBUG: Couldn't send RPC call: %s. Buffering call.\n", name);
+ AddRPC(function, GetPlayerID(), viewID, group, parameters);
+ }
+ }
+}
+
+void NetworkManager::BroadcastRPC(const char* name, const RakNet::BitStream *parameters, PacketPriority priority, SystemAddress target, RakNetTime *includedTimestamp, UInt32 group )
+{
+ for (int i=0;i<m_Players.size();i++)
+ {
+ SystemAddress current = m_Players[i].playerAddress;
+ if (current != target && MaySendToPlayer(current, group) && !m_Players[i].isDisconnected)
+ {
+ if (m_Players[i].relayed)
+ {
+ if (!m_Peer->RPC ((const char*)name, (const RakNet::BitStream*)parameters, priority, RELIABLE_ORDERED, kDefaultChannel, m_ProxyAddress, false, includedTimestamp, UNASSIGNED_NETWORK_ID, NULL, (unsigned char) ID_PROXY_SERVER_MESSAGE, m_Players[i].playerAddress))
+ {
+ NetworkError(NULL, "Couldn't send RPC function '%s' through proxy\n", name);
+ }
+ }
+ else
+ {
+ if (!m_Peer->RPC ((const char*)name, (const RakNet::BitStream*)parameters, priority, RELIABLE_ORDERED, kDefaultChannel, current, false, includedTimestamp, UNASSIGNED_NETWORK_ID, NULL))
+ {
+ NetworkError(NULL, "Couldn't send RPC function '%s'\n", name);
+ }
+ }
+ }
+ }
+}
+
+void NetworkManager::PerformRPCSpecificTarget(char const* function, PlayerTable *player, RakNet::BitStream& parameters, UInt32 group)
+{
+ RakNetTime timestamp = GetTimestamp();
+
+ if (MaySendToPlayer(player->playerAddress, group))
+ {
+ if (IsClient() && m_UseProxy)
+ {
+ NetworkLog(NULL, "Client sending specific target RPC '%s' to %s", function, player->playerAddress.ToString());
+ if (!m_Peer->RPC(function, &parameters, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ProxyAddress, false, &timestamp, UNASSIGNED_NETWORK_ID, 0, (unsigned char) ID_PROXY_CLIENT_MESSAGE, player->playerAddress))
+ {
+ NetworkError(NULL, "Couldn't send RPC function '%s'\n", function);
+ }
+ }
+ else if (IsServer() && player->relayed)
+ {
+ NetworkLog(NULL, "Server sending specific target RPC '%s' to %s", function, player->playerAddress.ToString());
+ if (!m_Peer->RPC(function, &parameters, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ProxyAddress, false, &timestamp, UNASSIGNED_NETWORK_ID, 0, (unsigned char) ID_PROXY_SERVER_MESSAGE, player->playerAddress))
+ {
+ NetworkError(NULL, "Couldn't send RPC function '%s'\n", function);
+ }
+ }
+ else
+ {
+ // Send only to registered server address with no ordering, no timestamp and no reply
+ if (!m_Peer->RPC(function, &parameters, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, player->playerAddress, false, &timestamp, UNASSIGNED_NETWORK_ID,0))
+ {
+ NetworkError(NULL, "Couldn't send RPC function '%s'\n", function);
+ }
+ }
+ }
+}
+
+void NetworkManager::PeformRPCRelayAll(char *name, int mode, NetworkViewID viewID, UInt32 group, RakNetTime timestamp, SystemAddress sender, RakNet::BitStream &stream)
+{
+ NetworkLog(NULL, "Relay RPC - name: %s - mode %d - sender %s", name, GetTargetMode(mode), sender.ToString());
+ // Relay the RPC call to all other clients!
+ if ( IsServer() && (GetTargetMode(mode) == kOthers || GetTargetMode(mode) == kAll))
+ {
+ BroadcastRPC(name, &stream, HIGH_PRIORITY, sender, &timestamp, group);
+ }
+
+ // Buffer the message if appropriate
+ if (GetNetworkManager().IsServer() && (mode & kBufferRPCMask) != 0 )
+ {
+ AssertIf(GetTargetMode(mode) != kOthers && GetTargetMode(mode) != kAll);
+ NetworkPlayer senderIndex = GetNetworkManager().GetIndexFromSystemAddress(sender);
+ AddRPC(name, senderIndex, viewID, group, stream);
+ }
+}
+
+void NetworkManager::PerformRPCRelaySpecific(char *name, RakNet::BitStream *stream, NetworkPlayer player)
+{
+ //SystemAddress address = GetSystemAddressFromIndex (player);
+ PlayerTable *playerEntry = GetPlayerEntry (player);
+
+ if (playerEntry == NULL)
+ {
+ NetworkError(NULL, "Couldn't relay RPC call '%s' because the player %d is not connected", name, player);
+ return;
+ }
+
+ RakNetTime time = GetTimestamp();
+ if (playerEntry->relayed)
+ {
+ NetworkLog(NULL, "Server sending proxied relay specific target RPC '%s' to %s", name, playerEntry->playerAddress.ToString());
+ if (!m_Peer->RPC(name, stream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ProxyAddress, false, &time, UNASSIGNED_NETWORK_ID, 0, (unsigned char)ID_PROXY_SERVER_MESSAGE, playerEntry->playerAddress))
+ {
+ NetworkError(NULL, "Couldn't relay RPC call '%s'", name);
+ }
+ }
+ else
+ {
+ if (!m_Peer->RPC(name, stream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, playerEntry->playerAddress, false, &time, UNASSIGNED_NETWORK_ID,0))
+ {
+ NetworkError(NULL, "Couldn't relay RPC call '%s'", name);
+ }
+ }
+}
+
+
+void NetworkManager::AddRPC(const std::string& name, NetworkPlayer sender, NetworkViewID viewID, UInt32 group, RakNet::BitStream& stream)
+{
+ RPCMsg msg;
+ msg.name = name;
+ msg.viewID = viewID;
+ msg.sender = sender;
+ msg.group = group;
+ msg.stream = NULL;
+ m_RPCBuffer.push_back(msg);
+ m_RPCBuffer.back().stream = new RakNet::BitStream(stream.GetData(), stream.GetNumberOfBytesUsed(), true);
+ NetworkInfo (NULL, "Added RPC '%s' to buffer.", name.c_str());
+}
+
+void NetworkManager::MsgRemoveRPCs()
+{
+ NetworkViewID viewID;
+ int systemAddress;
+ UInt32 groupMask;
+ m_BitStream.Reset();
+ m_BitStream.Write((char*)m_Packet->data, m_Packet->length);
+ m_BitStream.IgnoreBits(8);
+ m_BitStream.Read(systemAddress);
+ viewID.Read(m_BitStream);
+ m_BitStream.Read(groupMask);
+ RemoveRPCs(systemAddress, viewID, groupMask);
+}
+
+void NetworkManager::RemoveRPCs(NetworkPlayer playerIndex, NetworkViewID viewID, UInt32 groupMask)
+{
+ if (m_PeerType == kClient)
+ {
+ m_BitStream.Reset();
+ m_BitStream.Write((unsigned char)ID_REMOVE_RPCS);
+ m_BitStream.Write(playerIndex);
+ viewID.Write(m_BitStream);
+ m_BitStream.Write(groupMask);
+ if (!m_Peer->Send(&m_BitStream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ServerAddress, false))
+ {
+ NetworkError(NULL, "Failed to send remove RPCs command to network");
+ }
+ else
+ {
+ // @todo: more details
+ NetworkInfo (NULL, "Sent remove RPCs player command to server");
+ }
+ }
+ else
+ {
+ int erased = 0;
+ RPCBuffer::iterator next = m_RPCBuffer.begin();
+ for (RPCBuffer::iterator i=next;i != m_RPCBuffer.end();i=next)
+ {
+ RPCMsg& rpc = *i;
+ next++;
+
+ if (((1 << rpc.group) & groupMask) == 0)
+ continue;
+ if (rpc.viewID != viewID && viewID != NetworkViewID::GetUnassignedViewID())
+ continue;
+ if (rpc.sender != playerIndex && playerIndex != kUndefindedPlayerIndex)
+ continue;
+
+ NetworkInfo(NULL, "RPC %s with %s, player ID %d and group %d, removed from RPC buffer.", rpc.name.c_str(), rpc.viewID.ToString().c_str(), rpc.sender, rpc.group);
+ delete rpc.stream;
+ m_RPCBuffer.erase(i);
+ erased++;
+ }
+
+ NetworkInfo (NULL, "%d RPC function were removed with RemoveRPC", erased);
+ }
+}
+
+NetworkView* NetworkManager::ViewIDToNetworkView(const NetworkViewID& ID)
+{
+ NetworkView* view;
+ NetworkViewIterator i;
+ for (i = m_Sources.begin(); i != m_Sources.end(); i++) {
+ view = i->GetData();
+ if (view->GetViewID() == ID) {
+ return view;
+ }
+ }
+
+ for (i = m_NonSyncSources.begin(); i != m_NonSyncSources.end(); i++) {
+ view = i->GetData();
+ if (view->GetViewID() == ID) {
+ return view;
+ }
+ }
+
+ ErrorString(Format("View ID %s not found during lookup. Strange behaviour may occur", ID.ToString().c_str()));
+ return NULL;
+}
+
+
+NetworkViewID NetworkManager::NetworkViewToViewID(NetworkView* view)
+{
+ if (view)
+ return view->GetViewID();
+ else
+ return NetworkViewID();
+}
+
+int NetworkManager::GetValidInitIndex()
+{
+ int available = 0;
+ while (available < m_UsedInitIndices.size() && m_UsedInitIndices[available])
+ available++;
+
+ if (available == m_UsedInitIndices.size())
+ {
+ m_UsedInitIndices.push_back(true);
+ // Must set the scope or else a scope check later on will fail (the scope does not exist up until now).
+ for (NetworkViewIterator i = m_AllSources.begin(); i != m_AllSources.end(); i++) {
+ NetworkView* view = i->GetData();
+ view->SetScope(available, true); // By default all views are in scope
+ }
+ return m_UsedInitIndices.size() - 1;
+ }
+ else
+ {
+ m_UsedInitIndices[available] = true;
+
+ // Reset the initial state variable for all network views
+ NetworkView* view;
+ NetworkViewIterator i;
+ for (i = m_AllSources.begin(); i != m_AllSources.end(); i++) {
+ view = i->GetData();
+ view->SetInitState(available, false);
+ view->SetScope(available, true); // By default all views are in scope
+ }
+
+ return available;
+ }
+}
+
+NetworkViewID NetworkManager::AllocateViewID()
+{
+ // Make sure we always have enough NetworkViewId's
+ int requested = m_NetworkViewIDAllocator.ShouldRequestMoreBatches();
+ if (requested != 0)
+ {
+ if (IsServer())
+ {
+ for (int i=0;i<requested;i++)
+ {
+ UInt32 batch = m_NetworkViewIDAllocator.AllocateBatch(m_PlayerID);
+ m_NetworkViewIDAllocator.FeedAvailableBatchOnServer(batch);
+ }
+ }
+ else if (IsClient())
+ {
+ m_NetworkViewIDAllocator.AddRequestedBatches(requested);
+ for (int i=0;i<requested;i++)
+ {
+ RakNet::BitStream bitStream;
+ if (!m_Peer->RPC("__RPCRequestViewIDBatch", &bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, m_ServerAddress, false, NULL, UNASSIGNED_NETWORK_ID,0))
+ ErrorString("Failed to request view id batch");
+ }
+ }
+ }
+
+ NetworkViewID viewID = m_NetworkViewIDAllocator.AllocateViewID();
+
+ if (viewID == NetworkViewID())
+ ErrorString("Failed to allocate view id because no NetworkView's were available to allocate from. You should increase the minimum client NetworkViewID count.");
+ NetworkInfo(NULL, "Allocating view ID %s.\n", viewID.ToString().c_str());
+
+ return viewID;
+}
+
+NetworkViewID NetworkManager::AllocateSceneViewID()
+{
+ UInt32 highestViewID = 0;
+ for (NetworkViewIterator i=m_AllSources.begin();i != m_AllSources.end();i++)
+ {
+ NetworkView *view = i->GetData();
+ if (view->GetViewID().IsSceneID())
+ highestViewID = max(view->GetViewID().GetIndex(), highestViewID);
+ }
+
+ highestViewID++;
+
+ NetworkViewID viewID;
+ viewID.SetSceneID(highestViewID);
+
+ return viewID;
+}
+
+NetworkViewID NetworkManager::ValidateSceneViewID(NetworkView* validateView, NetworkViewID viewID)
+{
+ // Find conflicting view ids
+ bool isValidViewID = viewID.IsSceneID() && viewID.GetIndex() != 0;
+
+ for (int s=0;s<2;s++)
+ {
+ NetworkViewList& list = s == 0 ? m_Sources : m_NonSyncSources;
+ for (NetworkViewIterator i=list.begin();i != list.end();i++)
+ {
+ NetworkView *view = i->GetData();
+ if (validateView != view)
+ {
+ if (viewID == view->GetViewID())
+ isValidViewID = false;
+ }
+ }
+ }
+
+ if (!isValidViewID) {
+ LogString(Format("Fixing invalid scene view ID %s", viewID.ToString().c_str()));
+ viewID = AllocateSceneViewID();
+ }
+
+ return viewID;
+}
+
+bool NetworkManager::WasViewIdAllocatedByPlayer (NetworkViewID viewID, NetworkPlayer playerID)
+{
+ //AssertIf (!IsServer());
+ return m_NetworkViewIDAllocator.FindOwner(viewID) == playerID;
+}
+
+bool NetworkManager::WasViewIdAllocatedByMe(NetworkViewID viewID)
+{
+ return m_NetworkViewIDAllocator.FindOwner(viewID) == GetPlayerID ();
+}
+
+NetworkPlayer NetworkManager::GetNetworkViewIDOwner(NetworkViewID viewID)
+{
+ return m_NetworkViewIDAllocator.FindOwner(viewID);
+}
+
+int NetworkManager::GetPlayerID()
+{
+ return m_PlayerID;
+}
+
+int NetworkManager::GetPeerType()
+{
+ return m_PeerType;
+}
+
+int NetworkManager::GetDebugLevel()
+{
+ return m_DebugLevel;
+}
+
+void NetworkManager::SetDebugLevel(int value)
+{
+ m_DebugLevel = value;
+}
+
+SystemAddress NetworkManager::GetPlayerAddress()
+{
+ return m_Peer->GetInternalID();
+}
+
+bool NetworkManager::IsClient()
+{
+ return m_PeerType == kClient;
+}
+
+bool NetworkManager::IsServer()
+{
+ return m_PeerType == kServer;
+}
+
+void NetworkManager::SetSimulation (NetworkSimulation simulation)
+{
+ switch (simulation)
+ {
+ case kBroadband:
+ m_Peer->ApplyNetworkSimulator(1000000, 20, 0);
+ break;
+ case kDSL:
+ m_Peer->ApplyNetworkSimulator(700000, 40, 0);
+ break;
+ case kISDN:
+ m_Peer->ApplyNetworkSimulator(128000, 60, 0);
+ break;
+ case kDialUp:
+ m_Peer->ApplyNetworkSimulator(56000, 150, 100);
+ break;
+ default:
+ m_Peer->ApplyNetworkSimulator(0, 0, 0);
+ break;
+ }
+}
+
+void NetworkManager::MsgClientDidDisconnect()
+{
+ MsgClientDidDisconnect(m_Packet->systemAddress);
+}
+
+void NetworkManager::MsgClientDidDisconnect(SystemAddress clientAddress)
+{
+ int playerID = GetIndexFromSystemAddress(clientAddress);
+
+ if (playerID == -1)
+ {
+ ErrorString("A client which was not in the connected player list disconnected. ???");
+ return;
+ }
+
+ PlayerTable* player = GetPlayerEntry(playerID);
+ player->isDisconnected = true;
+
+ SendToAllNetworkViews(kPlayerDisconnected, playerID);
+
+ for (PlayerAddresses::iterator i = m_Players.begin(); i != m_Players.end(); i++)
+ {
+ PlayerTable& pl = *i;
+ if (clientAddress == pl.playerAddress)
+ {
+ if (pl.initIndex < m_UsedInitIndices.size())
+ m_UsedInitIndices[pl.initIndex] = false;
+ m_Players.erase(i);
+ break;
+ }
+ }
+}
+
+void NetworkManager::SetIncomingPassword (const std::string& incomingPassword)
+{
+ const char* pass = NULL;
+ if (!incomingPassword.empty())
+ pass = incomingPassword.c_str();
+
+ m_Peer->SetIncomingPassword(pass, incomingPassword.size());
+}
+
+std::string NetworkManager::GetIncomingPassword ()
+{
+ string password;
+ int size = 0;
+ m_Peer->GetIncomingPassword(NULL, &size);
+ password.resize(size);
+ m_Peer->GetIncomingPassword(const_cast<char*>(password.data()), &size);
+ return password;
+}
+
+void NetworkManager::SetMaxConnections(int connections)
+{
+ if (connections == -1)
+ {
+ int currentConnectionCount = m_Players.size();
+ m_Peer->SetMaximumIncomingConnections(currentConnectionCount);
+ m_MaxConnections = currentConnectionCount;
+ }
+ else
+ {
+ m_Peer->SetMaximumIncomingConnections(connections);
+ m_MaxConnections = connections;
+ }
+}
+
+int NetworkManager::GetMaxConnections()
+{
+ return m_MaxConnections;
+ //return m_Peer->GetMaximumIncomingConnections();
+}
+
+int NetworkManager::GetConnectionCount()
+{
+ return m_Players.size();
+}
+
+PlayerTable* NetworkManager::GetPlayerEntry(SystemAddress playerAddress)
+{
+ for (PlayerAddresses::iterator i = m_Players.begin(); i != m_Players.end(); i++)
+ {
+ PlayerTable& player = *i;
+ if (playerAddress == player.playerAddress)
+ {
+ return &player;
+ }
+ }
+ return NULL;
+}
+
+PlayerTable* NetworkManager::GetPlayerEntry(NetworkPlayer index)
+{
+ for (PlayerAddresses::iterator i = m_Players.begin(); i != m_Players.end(); i++)
+ {
+ PlayerTable& player = *i;
+ if (index == player.playerIndex)
+ {
+ return &player;
+ }
+ }
+ return NULL;
+}
+
+SystemAddress NetworkManager::GetSystemAddressFromIndex(NetworkPlayer playerIndex)
+{
+ for (PlayerAddresses::iterator i = m_Players.begin(); i != m_Players.end(); i++)
+ {
+ PlayerTable& player = *i;
+ if (playerIndex == player.playerIndex)
+ {
+ return player.playerAddress;
+ }
+ }
+ return UNASSIGNED_SYSTEM_ADDRESS;
+}
+
+int NetworkManager::GetIndexFromSystemAddress(SystemAddress playerAddress)
+{
+ for (PlayerAddresses::iterator i = m_Players.begin(); i != m_Players.end(); i++)
+ {
+ PlayerTable& player = *i;
+ if (playerAddress == player.playerAddress)
+ {
+ return player.playerIndex;
+ }
+ }
+ return -1;
+}
+
+std::vector<PlayerTable> NetworkManager::GetPlayerAddresses()
+{
+ return m_Players;
+}
+
+
+bool NetworkManager::IsPasswordProtected()
+{
+ int size = 0;
+ m_Peer->GetIncomingPassword(NULL, &size);
+ if (size == 0)
+ return false;
+ else
+ return true;
+}
+
+// NOTE: This returns only the internal address, in the case of a NATed address we do not know which port will open on the outside
+// and thus cannot know our external playerID
+std::string NetworkManager::GetIPAddress()
+{
+ if (m_Peer->IsActive())
+ return m_Peer->GetInternalID().ToString(false);
+ else
+ return string();
+}
+
+std::string NetworkManager::GetExternalIP()
+{
+ return std::string(m_Peer->GetExternalID(UNASSIGNED_SYSTEM_ADDRESS).ToString(false));
+}
+
+int NetworkManager::GetExternalPort()
+{
+ return m_Peer->GetExternalID(UNASSIGNED_SYSTEM_ADDRESS).port;
+}
+
+std::string NetworkManager::GetIPAddress(int player)
+{
+ if (player == -2 && IsServer() && m_UseProxy)
+ {
+ return m_ProxyAddress.ToString(false);
+ }
+ // NOTE: It will not work running this on clients as the client only has the server index number, no other numbers
+ SystemAddress address = GetSystemAddressFromIndex(player);
+ if (address != UNASSIGNED_SYSTEM_ADDRESS)
+ return address.ToString(false);
+ else
+ return string ();
+}
+
+int NetworkManager::GetPort()
+{
+ if (!m_Peer->IsActive())
+ return 0;
+ else
+ return m_Peer->GetInternalID().port;
+}
+
+int NetworkManager::GetPort(int player)
+{
+ if (player == -2 && IsServer() && m_UseProxy)
+ {
+ return m_RelayPort;
+ }
+ SystemAddress address = GetSystemAddressFromIndex(player);
+ if (address != UNASSIGNED_SYSTEM_ADDRESS)
+ return address.port;
+ else
+ return 0;
+}
+
+std::string NetworkManager::GetGUID()
+{
+ return m_Peer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS).ToString();
+}
+
+std::string NetworkManager::GetGUID(int player)
+{
+ if (player == -2 && IsServer() && m_UseProxy)
+ return m_Peer->GetGuidFromSystemAddress(m_ProxyAddress).ToString();
+ PlayerTable *playerTable = GetPlayerEntry(player);
+ if (playerTable)
+ return playerTable->guid;
+ else
+ return "0";
+}
+
+void NetworkManager::GetConnections(int* connection)
+{
+ for (int i=0;i<m_Players.size();i++)
+ {
+ PlayerTable& player = m_Players[i];
+ connection[i] = player.playerIndex;
+ }
+}
+
+bool NetworkManager::GetUseNat()
+{
+ return m_DoNAT;
+}
+
+void NetworkManager::SetUseNat(bool enabled)
+{
+ if (m_DoNAT != enabled)
+ {
+ m_DoNAT = enabled;
+ if (m_DoNAT)
+ m_Peer->AttachPlugin(&m_NatPunchthrough);
+ else
+ m_Peer->DetachPlugin(&m_NatPunchthrough);
+ }
+}
+
+bool NetworkManager::ShouldIgnoreInGarbageDependencyTracking ()
+{
+ return true;
+}
+
+
+
+/*
+float NetworkManager::GetTimeout ()
+{
+ return m_TimeoutTime;
+}
+
+void NetworkManager::SetTimeout (float timeout)
+{
+ m_TimeoutTime = timeout;
+ SetTimeoutTime();
+}
+*/
+int NetworkManager::GetLastPing (NetworkPlayer player)
+{
+ return m_Peer->GetLastPing(GetSystemAddressFromIndex(player));
+}
+
+int NetworkManager::GetAveragePing (NetworkPlayer player)
+{
+ return m_Peer->GetLastPing(GetSystemAddressFromIndex(player));
+}
+
+static void GetSetNetworkViewIDs (Transform& obj, NetworkViewID*& ids, SInt32& netViewCount, bool assign);
+static void GetSetNetworkViewIDs (Transform& obj, NetworkViewID*& ids, SInt32& netViewCount, bool assign)
+{
+ GameObject& go = obj.GetGameObject();
+ int count = go.GetComponentCount ();
+ for (int i=0;i<count;i++)
+ {
+ NetworkView* view = dynamic_pptr_cast<NetworkView*> (&go.GetComponentAtIndex(i));
+ if (view)
+ {
+ if (assign)
+ {
+ if (netViewCount <= 0)
+ {
+ netViewCount = -1;
+ return;
+ }
+
+ view->SetViewID(ids[0]);
+ ids++;
+ netViewCount--;
+ }
+ else
+ {
+ netViewCount++;
+ }
+ }
+ }
+
+ for (int i=0;i<obj.GetChildrenCount();i++)
+ {
+ Transform& child = obj.GetChild(i);
+ GetSetNetworkViewIDs(child, ids, netViewCount, assign);
+ }
+}
+
+Object* NetworkManager::Instantiate (Object& prefab, Vector3f pos, Quaternionf rot, UInt32 group)
+{
+ if (!IsConnected())
+ {
+ NetworkError(NULL, "Failed Network.Instantiate because we are not connected.");
+ return NULL;
+ }
+
+ // Grab the prefab game object
+ GameObject* prefabGO = dynamic_pptr_cast<GameObject*> (&prefab);
+ Unity::Component* prefabCom = dynamic_pptr_cast<Unity::Component*> (&prefab);
+ if (prefabCom != NULL)
+ prefabGO = prefabCom->GetGameObjectPtr();
+
+ if (prefabGO == NULL)
+ {
+ NetworkError(NULL, "The prefab '%s' reference must be a component or game object.", prefab.GetName());
+ return NULL;
+ }
+
+ // Get the UnityGUID of the prefab
+ #if UNITY_EDITOR
+ UnityGUID guid = ObjectToGUID(prefabGO);
+
+ const Asset* asset = AssetDatabase::Get().AssetPtrFromGUID(guid);
+ if (asset == NULL)
+ {
+ NetworkError(NULL, "The object %s must be a prefab in the project view.", prefab.GetName());
+ return NULL;
+ }
+
+ Object* mainRep = asset->mainRepresentation.object;
+ if (mainRep != prefabGO)
+ {
+ NetworkError(NULL, "The object is not the main asset. Only root objecs of a prefab may be used for Instantiating");
+ return NULL;
+ }
+
+ #else
+ PrefabToAsset::iterator found = m_PrefabToAsset.find(PPtr<GameObject> (prefabGO));
+ if (found == m_PrefabToAsset.end())
+ {
+ NetworkError(NULL, "The object %s must be a prefab in the project view.", prefab.GetName());
+ return NULL;
+ }
+ UnityGUID guid = found->second;
+ #endif
+
+ // Get the component index
+ UInt8 componentIndex = 0xFF;
+ if (prefabCom)
+ {
+ AssertIf(prefabCom->GetGameObject().GetComponentIndex(prefabCom) >= std::numeric_limits<UInt8>::max());
+ componentIndex = prefabGO->GetComponentIndex(prefabCom);
+ }
+
+ // Allocate view ids for all network views and assign them
+ SInt32 networkViewCount = 0;
+ NetworkViewID* viewIDs = NULL;
+ GetSetNetworkViewIDs (prefabGO->GetComponent(Transform), viewIDs, networkViewCount, false);
+ ALLOC_TEMP(viewIDs, NetworkViewID, networkViewCount)
+
+ for (int i=0;i<networkViewCount;i++)
+ viewIDs[i] = AllocateViewID();
+
+ /// Send message to everyone
+ RakNet::BitStream stream;
+ BitstreamPacker packer (stream, false);
+ packer.Serialize(group);
+ packer.Serialize(guid.data[0]);
+ packer.Serialize(guid.data[1]);
+ packer.Serialize(guid.data[2]);
+ packer.Serialize(guid.data[3]);
+ packer.Serialize(componentIndex);
+ packer.Serialize(pos);
+ packer.Serialize(rot);
+ packer.Serialize(networkViewCount);
+ for (int i=0;i<networkViewCount;i++)
+ packer.Serialize(viewIDs[i]);
+
+ /// Send/Buffer RPC on to other machines
+ NetworkViewID sourceViewID = NetworkViewID();
+ if (networkViewCount > 0)
+ sourceViewID = viewIDs[0];
+ PerformRPC("__RPCNetworkInstantiate", kAll | kBufferRPCMask, stream, sourceViewID, group);
+
+ stream.ResetReadPointer();
+ return NetworkInstantiateImpl(stream, GetPlayerAddress(), GetTimestamp());
+}
+
+static void RecursiveOnNetworkInstantiate (Transform& obj, RakNetTime time, SystemAddress sender)
+{
+ GameObject& go = obj.GetGameObject();
+ int count = go.GetComponentCount ();
+ RakNet::BitStream stream;
+ for (int i=0;i<count;i++)
+ {
+ MonoBehaviour* behaviour = dynamic_pptr_cast<MonoBehaviour*> (&go.GetComponentAtIndex(i));
+ if (behaviour)
+ {
+ if (!behaviour->GetInstance())
+ {
+ NetworkError(&go, "Network instantiated object, %s, has a missing script component attached", go.GetName());
+ }
+ else
+ {
+ ScriptingMethodPtr method = behaviour->GetMethod(MonoScriptCache::kNetworkInstantiate);
+ if (method)
+ {
+ int readoffset = stream.GetReadOffset();
+ UnpackAndInvokeRPCMethod(*behaviour, method->monoMethod, stream, sender, NetworkViewID(), time, NULL);
+ stream.SetReadOffset(readoffset);
+ }
+ }
+ }
+ }
+
+ for (int i=0;i<obj.GetChildrenCount();i++)
+ {
+ Transform& child = obj.GetChild(i);
+ RecursiveOnNetworkInstantiate(child, time, sender);
+ }
+}
+
+PROFILER_INFORMATION(gInstantiateProfile, "Network.Instantiate", kProfilerNetwork)
+
+Object* NetworkManager::NetworkInstantiateImpl (RakNet::BitStream& bitstream, SystemAddress sender, RakNetTime time)
+{
+ PROFILER_AUTO(gInstantiateProfile, NULL);
+
+ SInt32 networkViewCount = 0;
+ NetworkViewID* viewIDs = NULL;
+ UnityGUID guid;
+ UInt32 group;
+ Vector3f pos;
+ Quaternionf rot;
+ UInt8 componentIndex;
+ // Read Instantiate data
+ BitstreamPacker packer (bitstream, true);
+ packer.Serialize(group);
+ packer.Serialize(guid.data[0]);
+ packer.Serialize(guid.data[1]);
+ packer.Serialize(guid.data[2]);
+ packer.Serialize(guid.data[3]);
+ packer.Serialize(componentIndex);
+ packer.Serialize(pos);
+ packer.Serialize(rot);
+ packer.Serialize(networkViewCount);
+ ALLOC_TEMP(viewIDs, NetworkViewID, networkViewCount)
+ for (int i=0;i<networkViewCount;i++)
+ packer.Serialize(viewIDs[i]);
+
+ /// Find the asset
+ #if UNITY_EDITOR
+ const Asset* asset = AssetDatabase::Get().AssetPtrFromGUID(guid);
+ if (asset == NULL)
+ {
+ NetworkError(NULL, "Network.Instantiate on the receiving client failed because the asset couldn't be found in the project");
+ return NULL;
+ }
+
+ GameObject* prefabGO = dynamic_pptr_cast<GameObject*> (asset->mainRepresentation.object);
+ #else
+ AssetToPrefab::iterator found = m_AssetToPrefab.find(guid);
+ if (found == m_AssetToPrefab.end())
+ {
+ NetworkError(NULL, "Network.Instantiate on the receiving client failed because the asset couldn't be found in the project");
+ return NULL;
+ }
+ GameObject* prefabGO = found->second;
+ #endif
+
+ // Make sure we got the game object
+ if (prefabGO == NULL)
+ {
+ NetworkError(NULL, "Network.Instantiate sent component but found asset is not a prefab.");
+ return NULL;
+ }
+
+ // Get the right component
+ Object* prefab = prefabGO;
+ if (componentIndex != 0xFF)
+ {
+ if (componentIndex >= prefabGO->GetComponentCount())
+ {
+ NetworkError(NULL, "Network.Instantiate component index is out of bounds.");
+ return NULL;
+ }
+
+ prefab = &prefabGO->GetComponentAtIndex(componentIndex);
+ }
+
+ // Instantiate the object
+ TempRemapTable ptrs;
+ Object& clone = InstantiateObject(*prefab, pos, rot, ptrs);
+
+ Transform* cloneTransform = NULL;
+ GameObject* cloneGO = dynamic_pptr_cast<GameObject*> (&clone);
+ Unity::Component* cloneComponent = dynamic_pptr_cast<Unity::Component*> (&clone);
+ if (cloneGO)
+ cloneTransform = cloneGO->QueryComponent(Transform);
+ if (cloneComponent)
+ cloneTransform = cloneComponent->QueryComponent(Transform);
+ AssertIf(!cloneTransform);
+
+ // Assign view ids for all network views
+ GetSetNetworkViewIDs(*cloneTransform, viewIDs, networkViewCount, true);
+ if (networkViewCount != -0)
+ {
+ NetworkError(NULL, "Network.Instantiate received non-matching number of view id's as contained in prefab");
+ }
+
+ AwakeAndActivateClonedObjects(ptrs);
+
+ // Call network instantiate function
+ RecursiveOnNetworkInstantiate(*cloneTransform, time, sender);
+
+ return &clone;
+}
+
+void NetworkManager::RPCNetworkInstantiate (RPCParameters* rpcParameters)
+{
+ NetworkManager& nm = GetNetworkManager();
+ RakNet::BitStream stream (rpcParameters->input, BITS_TO_BYTES(rpcParameters->numberOfBitsOfData), false);
+
+ UInt32 group = 0;
+ stream.Read(group);
+
+ // Should we be receiving this at all?
+ if (!nm.MayReceiveFromPlayer(rpcParameters->sender, group))
+ {
+ NetworkInfo (NULL, "Network.Instantiate was ignored since the group of the network view is disabled.");
+ return;
+ }
+
+ stream.ResetReadPointer();
+ nm.NetworkInstantiateImpl (stream, rpcParameters->sender, rpcParameters->remoteTimestamp);
+
+ stream.ResetReadPointer();
+ nm.PeformRPCRelayAll(rpcParameters->functionName, kAll | kBufferRPCMask, NetworkViewID(), group, rpcParameters->remoteTimestamp, rpcParameters->sender, stream);
+}
+
+void NetworkManager::SetLevelPrefix(int levelPrefix)
+{
+ m_LevelPrefix = levelPrefix;
+}
+
+IMPLEMENT_CLASS_HAS_INIT (NetworkManager)
+IMPLEMENT_OBJECT_SERIALIZE (NetworkManager)
+GET_MANAGER (NetworkManager)
+GET_MANAGER_PTR (NetworkManager)
+
+void NetworkManager::InitializeClass ()
+{
+ #if UNITY_EDITOR
+ RegisterAllowNameConversion ("NetworkViewID", "m_SceneID", "m_ID");
+ #endif
+}
+
+void NetworkManager::CleanupClass ()
+{
+}
+
+bool NetworkManager::MaySend( int group )
+{
+ return m_SendingEnabled & (1 << group);
+}
+
+// Shouldn't errors always be shown???
+void NetworkError (Object* obj, const char* format, ...)
+{
+ if (GetNetworkManager().GetDebugLevel() >= kImportantErrors)
+ {
+ va_list va;
+ va_start( va, format );
+ ErrorStringObject(VFormat(format, va), obj);
+ }
+}
+
+
+void NetworkWarning (Object* obj, const char* format, ...)
+{
+ if (GetNetworkManager().GetDebugLevel() >= kImportantErrors)
+ {
+ va_list va;
+ va_start( va, format );
+ ErrorStringObject(VFormat(format, va), obj);
+ }
+}
+
+void NetworkInfo (Object* obj, const char* format, ...)
+{
+ if (GetNetworkManager().GetDebugLevel() >= kInformational)
+ {
+ va_list va;
+ va_start( va, format );
+ LogStringObject(VFormat(format, va), obj);
+ }
+}
+
+void NetworkLog (Object* obj, const char* format, ...)
+{
+ if (GetNetworkManager().GetDebugLevel() >= kCompleteLog)
+ {
+ va_list va;
+ va_start( va, format );
+ LogStringObject(VFormat(format, va), obj);
+ }
+}
+
+RakNetTime NetworkManager::GetTimestamp()
+{
+ return RakNet::GetTime();
+}
+
+double NetworkManager::GetTime()
+{
+ return TimestampToSeconds(RakNet::GetTime());
+}
+
+RakPeerInterface* NetworkManager::GetPeer()
+{
+ return m_Peer;
+}
+
+void NetworkManager::SwapFacilitatorID(SystemAddress newAddress)
+{
+ m_Peer->CloseConnection(m_FacilitatorID, true);
+ m_OldFacilitatorID = m_FacilitatorID;
+ m_FacilitatorID = newAddress;
+ if (m_Peer->IsConnected(m_OldFacilitatorID))
+ {
+ if (!m_Peer->Connect(newAddress.ToString(false), newAddress.port, 0, 0))
+ ErrorString("Internal problem connecting to new facilitator address");
+ }
+}
+
+void NetworkManager::SetOldMasterServerAddress(SystemAddress address)
+{
+ m_OldMasterServerID = address;
+}
+
+void NetworkManager::SetConnTesterAddress(SystemAddress address)
+{
+ m_ConnTesterAddress = address;
+ if (!m_ConnTester)
+ m_ConnTester = new ConnectionTester(m_ConnTesterAddress);
+ m_ConnTester->SetAddress(address);
+
+ // Reset test
+ m_ConnStatus = kConnTestUndetermined;
+}
+
+int NetworkManager::TestConnection(bool forceNATType, bool forceTest)
+{
+ // If the test is undetermined or if a test is forced -> it's OK to recreate tester and/or reenter test function
+ // If there is a kConnTestError then the test must be forced to get a new result
+ if ( m_ConnStatus == kConnTestUndetermined || forceTest)
+ {
+ if (!m_ConnTester)
+ m_ConnTester = new ConnectionTester(m_ConnTesterAddress);
+ m_ConnStatus = m_ConnTester->RunTest(forceNATType);
+ }
+ return m_ConnStatus;
+}
+
+void NetworkManager::PingWrapper(Ping *time)
+{
+ time->Retain();
+
+ // If the thread is not running then execute ping now, else put it in queue
+ if (!m_PingThread.IsRunning())
+ m_PingThread.Run(&PingImpl, (void*)time);
+ else
+ m_PingQueue.push(time);
+}
+
+void NetworkManager::ResolveFacilitatorAddress()
+{
+ ResolveAddress(m_FacilitatorID, "facilitator.unity3d.com", "facilitatorbeta.unity3d.com",
+ "Cannot resolve facilitator address, make sure you are connected to the internet before connecting to a server with NAT punchthrough enabled");
+}
+
+SystemAddress& NetworkManager::GetFacilitatorAddress(bool resolve)
+{
+ if (resolve)
+ ResolveFacilitatorAddress();
+
+ return m_FacilitatorID;
+}
+
+
+/* The connection tester.
+ m_ConnStatus indicates the connection test results:
+ -2 error
+ -1 undetermined, test not completed
+ 0 No NAT punchthrough capability
+ 1 NAT puConnectionTesternchthrough test successful
+ 2 Public IP test successful, listen port directly connectable
+ 3 Public IP test unsuccessful, tester unable to connect to listen port (firewall blocking access)
+ 4 Public IP test unsuccessful because no port is listening (server not initialized)
+ m_TestRunning indicates type of test:
+ 0 No test running
+ 1 NAT test running
+ 2 Public IP test running
+
+ The only reason m_TestRunning needs to display seperate test types is because we need to be able to interpret what
+ has happened when a successful connect has occured. I.e. did NAT punchthrough connect work or did we just connect to
+ the tester server do start a public IP connectivity test.
+*/
+ConnectionTester::ConnectionTester(SystemAddress& address)
+{
+ ResolveAddress(address, "connectiontester.unity3d.com", "connectiontesterbeta.unity3d.com",
+ "Cannot resolve connection tester address, you must be connected to the internet before performing this or set the address to something accessible to you.");
+
+ m_ConnTesterAddress = address;
+ m_Peer = RakNetworkFactory::GetRakPeerInterface();
+ m_NatTypeDetection = new RakNet::NatTypeDetectionClient;
+ m_Peer->AttachPlugin(m_NatTypeDetection);
+ m_ConnStatus = kConnTestUndetermined;
+ m_TestRunning = 0;
+ #if PACKET_LOGGER
+ m_Peer->AttachPlugin(&messageHandler2);
+ messageHandler2.LogHeader();
+ #endif
+}
+
+ConnectionTester::~ConnectionTester()
+{
+ m_Peer->DetachPlugin(m_NatTypeDetection);
+ delete m_NatTypeDetection;
+ m_NatTypeDetection = NULL;
+ RakNetworkFactory::DestroyRakPeerInterface(m_Peer);
+ m_Peer = NULL;
+}
+
+void ConnectionTester::SetAddress(SystemAddress address)
+{
+ m_ConnTesterAddress = address;
+ FinishTest(kConnTestUndetermined);
+}
+
+// TODO: At the moment -2 is returned on all errors, -1 on undetermined and 0 or 1 on success or failure. Maybe there
+// should be seperate error numbers for each kind of error and "less than -1" indicates and error
+int ConnectionTester::Update()
+{
+ // Check for timeout, if it has expired some problem has occured
+ if (m_TestRunning > 0 && time(0) - m_Timestamp > kConnTestTimeout)
+ {
+ LogString("Timeout during connection test");
+ m_TestRunning = 0;
+ return kConnTestError;
+ }
+
+ if (!m_Peer->IsActive())
+ return m_ConnStatus;
+
+ if (m_TestRunning > 0)
+ {
+ Packet* packet;
+ packet = m_Peer->Receive();
+ while (packet)
+ {
+ switch(packet->data[0])
+ {
+ case ID_CONNECTION_REQUEST_ACCEPTED:
+ StartTest();
+ break;
+ case ID_NO_FREE_INCOMING_CONNECTIONS:
+ NetworkError(NULL, "The connection tester is not accepting new connections, test finished.");
+ FinishTest();
+ break;
+ case ID_CONNECTION_ATTEMPT_FAILED:
+ NetworkInfo(NULL, "Failed to connect to connection tester at %s", packet->systemAddress.ToString());
+ FinishTest();
+ break;
+ case ID_CONNECTION_BANNED:
+ NetworkInfo(NULL, "The connection tester has banned this connection.");
+ FinishTest();
+ break;
+ case ID_DISCONNECTION_NOTIFICATION:
+ NetworkInfo(NULL, "Disconnected from connection tester.");
+ FinishTest();
+ break;
+ case ID_CONNECTION_LOST:
+ NetworkError(NULL, "Lost connection to connection tester.");
+ FinishTest();
+ break;
+ case ID_NAT_TYPE_DETECTION_RESULT:
+ {
+ // TODO: We know certain types might not be able to connect to another one. How
+ // should this be exposed... It's not as straight forward as saying NAT punchthrough
+ // capable or not capable. Maybe expose Network.CanConnect(type1,type2).
+ RakNet::NATTypeDetectionResult r = (RakNet::NATTypeDetectionResult) packet->data[1];
+ // TODO: Now check if router is UPNP compatible
+ switch (r)
+ {
+ case RakNet::NAT_TYPE_PORT_RESTRICTED: m_ConnStatus = kLimitedNATPunchthroughPortRestricted; break;
+ case RakNet::NAT_TYPE_SYMMETRIC: m_ConnStatus = kLimitedNATPunchthroughSymmetric; break;
+ case RakNet::NAT_TYPE_FULL_CONE: m_ConnStatus = kNATpunchthroughFullCone; break;
+ case RakNet::NAT_TYPE_ADDRESS_RESTRICTED: m_ConnStatus = kNATpunchthroughAddressRestrictedCone; break;
+ case RakNet::NAT_TYPE_NONE: m_ConnStatus = kPublicIPIsConnectable; break;
+ default:
+ m_ConnStatus = kConnTestError;
+ NetworkError(NULL, "Connection Tester returned invalid NAT type.");
+ break;
+ }
+ m_TestRunning = 0;
+ m_Peer->Shutdown(kDefaultTimeout);
+ }
+ break;
+ // Public IP test: tester is reporting that the couln't connect to the game server IP and port, test unsuccessful
+ case 254:
+ FinishTest(kPublicIPPortBlocked);
+ break;
+ case ID_ALREADY_CONNECTED:
+ NetworkInfo(NULL, "Already connected to connection tester, attempting to trigger new test.");
+ StartTest();
+ break;
+ default:
+ NetworkInfo(NULL, "Received invalid message type %d from connection tester at %s", (int)packet->data[0], packet->systemAddress.ToString());
+ break;
+ }
+ m_Peer->DeallocatePacket(packet);
+ packet = m_Peer->Receive();
+ }
+ }
+ return m_ConnStatus;
+}
+
+void ConnectionTester::StartTest()
+{
+ // NAT test: successfully connected with NAT punchthrough
+ if (m_TestRunning == 1)
+ {
+ NetworkInfo(NULL, "Starting NAT connection test.");
+ m_NatTypeDetection->DetectNATType(m_ConnTesterAddress);
+ }
+ // Public IP test: connected with tester so send him the connect-to-me command
+ else
+ {
+ RakNet::BitStream bitStream;
+ bitStream.Write((unsigned char)253);
+ bitStream.Write(GetNetworkManager().GetPort());
+ m_Peer->Send(&bitStream, HIGH_PRIORITY, RELIABLE, 0, m_ConnTesterAddress, false);
+ NetworkInfo(NULL, "Connection Tester requesting test on external IP and port %d", GetNetworkManager().GetPort());
+ }
+}
+
+void ConnectionTester::ReportTestSucceeded()
+{
+ FinishTest(kPublicIPIsConnectable);
+}
+
+void ConnectionTester::FinishTest(int status)
+{
+ m_ConnStatus = status;
+ m_TestRunning = 0;
+ m_Peer->Shutdown(kDefaultTimeout);
+}
+
+int ConnectionTester::RunTest(bool forceNATType)
+{
+ // If a problem occured the timeout must expire before anything is done. See below
+ if ( m_ConnStatus > kConnTestError && !(m_ConnStatus == kConnTestUndetermined && m_TestRunning > 0) )
+ {
+ // If test has already been triggered, stop here
+ if (m_TestRunning > 0)
+ return kConnTestUndetermined;
+
+ // First check if the peer interface has been initialized
+ if (!m_Peer->IsActive())
+ {
+ SocketDescriptor sd(0,0);
+ if (!m_Peer->Startup(2, 1, &sd, 1))
+ {
+ ErrorString("Failed to initialize network connection before NAT test.");
+ return kConnTestError;
+ }
+ }
+
+ // Set the timer, this ensures this instance will be destroyed after the timeout
+ m_Timestamp = time(0);
+
+ // There are two kinds of tests, one for public IP addresses and one for private addresses, either one is executed
+ if (CheckForPublicAddress() && !forceNATType)
+ {
+ NetworkInfo(NULL, "Starting public address connection test.");
+ if (GetNetworkManager().IsServer())
+ {
+ if (!m_Peer->Connect(m_ConnTesterAddress.ToString(false),m_ConnTesterAddress.port,0,0))
+ {
+ ErrorString("Failed to connect the connection tester.");
+ return kConnTestError;
+ }
+ m_TestRunning = 2;
+ }
+ else
+ {
+ FinishTest(kPublicIPNoServerStarted);
+ return m_ConnStatus;
+ }
+ }
+ else
+ {
+ if (!m_Peer->IsConnected(m_ConnTesterAddress))
+ {
+ NetworkInfo(NULL, "Connecting to connection tester at %s", m_ConnTesterAddress.ToString());
+ if (!m_Peer->Connect(m_ConnTesterAddress.ToString(false),m_ConnTesterAddress.port,0,0))
+ {
+ ErrorString("Failed to connect to connection tester during NAT test.");
+ return kConnTestError;
+ }
+ }
+
+ m_TestRunning = 1;
+ }
+
+ return m_ConnStatus = kConnTestUndetermined;
+ }
+ // If there is a problem (-2) and the timeout is expired you may try again (reset test and recall RunTest())
+ else if (m_ConnStatus == kConnTestError && (time(0) - m_Timestamp > kConnTestTimeout))
+ {
+ m_Timestamp = time(0);
+ m_ConnStatus = kConnTestUndetermined;
+ return RunTest(forceNATType);
+ }
+
+ return m_ConnStatus;
+}
+
+template<class TransferFunc>
+void NetworkManager::Transfer (TransferFunc& transfer) {
+
+ AssertIf(transfer.GetFlags() & kPerformUnloadDependencyTracking);
+
+ Super::Transfer (transfer);
+ TRANSFER(m_DebugLevel);
+ TRANSFER(m_Sendrate);
+ transfer.Transfer(m_AssetToPrefab, "m_AssetToPrefab", kHideInEditorMask);
+}
+
+#endif // ENABLE_NETWORK
diff --git a/Runtime/Network/NetworkManager.h b/Runtime/Network/NetworkManager.h
new file mode 100644
index 0000000..6109960
--- /dev/null
+++ b/Runtime/Network/NetworkManager.h
@@ -0,0 +1,358 @@
+#ifndef UNITY_NETWORK_MANAGER_H_
+#define UNITY_NETWORK_MANAGER_H_
+
+#include "Configuration/UnityConfigure.h"
+
+enum NetworkReachability
+{
+ NotReachable = 0,
+ ReachableViaCarrierDataNetwork,
+ ReachableViaLocalAreaNetwork,
+};
+
+NetworkReachability GetInternetReachability ();
+
+#if ENABLE_NETWORK
+
+#include "Runtime/BaseClasses/GameManager.h"
+#include <set>
+#include <list>
+#include <queue>
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Utilities/GUID.h"
+
+#include "NetworkEnums.h"
+#include "NetworkView.h"
+#include "Runtime/Graphics/Transform.h"
+#include "External/RakNet/builds/include/GetTime.h"
+#include "External/RakNet/builds/include/MessageIdentifiers.h"
+#include "External/RakNet/builds/include/BitStream.h"
+#include "External/RakNet/builds/include/RakNetTypes.h"
+#include "External/RakNet/builds/include/NatPunchthroughClient.h"
+#include "External/RakNet/builds/include/NatTypeDetectionClient.h"
+#include "NetworkViewIDAllocator.h"
+#include <time.h>
+#include "Runtime/Threads/Thread.h"
+#include "NetworkUtility.h"
+
+
+//class RakPeer;
+
+class ConnectionTester
+{
+ public:
+ ConnectionTester(SystemAddress& testerAddress);
+ ~ConnectionTester();
+ void SetAddress(SystemAddress address);
+ int Update();
+ int RunTest(bool forceNATType);
+ void ReportTestSucceeded();
+
+ private:
+ void StartTest();
+ void FinishTest(int status = kConnTestError);
+ int m_ConnStatus;
+ int m_TestRunning;
+ RakPeerInterface* m_Peer;
+ NatPunchthroughClient m_NATPunchthrough;
+ RakNet::NatTypeDetectionClient *m_NatTypeDetection;
+ SystemAddress m_ConnTesterAddress;
+ time_t m_Timestamp;
+};
+
+
+
+class NetworkManager : public GlobalGameManager
+{
+public:
+ typedef std::list<RPCMsg> RPCBuffer;
+ typedef std::vector<PlayerTable> PlayerAddresses;
+ typedef std::map<UnityGUID, PPtr<GameObject> > AssetToPrefab;
+ typedef std::map<PPtr<GameObject>, UnityGUID > PrefabToAsset;
+ typedef std::queue<Ping*> PingQueue;
+
+ REGISTER_DERIVED_CLASS (NetworkManager, GlobalGameManager)
+ DECLARE_OBJECT_SERIALIZE (NetworkManager)
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ NetworkManager (MemLabelId label, ObjectCreationMode mode);
+ // virtual ~NetworkManager (); declared-by-macro
+
+ /// Virtual API exposed in GameManager to avoid direct depdendencies to the network layer
+ virtual void NetworkUpdate ();
+ virtual void NetworkOnApplicationQuit();
+
+ void AddNetworkView (ListNode<NetworkView>& s);
+ void AddNonSyncNetworkView (ListNode<NetworkView>& s);
+ void AddAllNetworkView (ListNode<NetworkView>& s);
+ bool ShouldIgnoreInGarbageDependencyTracking ();
+
+ // Destroy locally and broadcast destroy message to peers (or just server)
+ void DestroyDelayed(NetworkViewID viewID);
+
+ // Destory all objects which belong the the given player locally and remotely and
+ // possibly remove all RPC calls currently in server RPC buffer.
+ void DestroyPlayerObjects(NetworkPlayer playerID);
+
+ void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ int InitializeServer(int connections, int listenPort, bool useNat);
+ void InitializeSecurity();
+
+ int Connect(std::string IP, int remotePort, int listenPort, const std::string& password);
+ int Connect(std::string IP, int remotePort, const std::string& password);
+ int Connect(std::vector<string> IPs, int remotePort, int listenPort, const std::string& password);
+ int Connect(RakNetGUID serverGUID, int listenPort, const std::string& password);
+ void Disconnect(int timeout, bool resetParams = true);
+ void CloseConnection(int target, bool sendDisconnect);
+
+ NetworkView* ViewIDToNetworkView (const NetworkViewID& ID);
+ NetworkViewID NetworkViewToViewID(NetworkView* view);
+
+ void LoadLevel(const std::string& level);
+
+ void SetIncomingPassword (const std::string& incomingPassword);
+ std::string GetIncomingPassword ();
+
+ void RegisterRPC(const char* reg, void ( *functionPointer ) ( RPCParameters *rpcParms ));
+ void BroadcastRPC(const char* name, const RakNet::BitStream *parameters, PacketPriority priority, SystemAddress target, RakNetTime *includedTimestamp, UInt32 group);
+
+ // Send an RPC call to every connected peer except the owner of the call (systemAddress)
+ void PerformRPC(const std::string &function, int mode, RakNet::BitStream& parameters, NetworkViewID viewID, UInt32 group);
+ void PerformRPCSpecificTarget(char const* function, PlayerTable *player, RakNet::BitStream& parameters, UInt32 group);
+
+ // Perform RPC to every connected client except systemAddress
+ void PeformRPCRelayAll(char *name, int mode, NetworkViewID viewID, UInt32 group, RakNetTime timestamp, SystemAddress sender, RakNet::BitStream &stream);
+ void PerformRPCRelaySpecific(char *name, RakNet::BitStream *stream, NetworkPlayer player);
+ void AddRPC(const std::string& name, NetworkPlayer sender, NetworkViewID viewID, UInt32 group, RakNet::BitStream& stream);
+
+ // This function must give out unique ID numbers per game. Getting a view ID automatically registers it.
+ // The editor must always "run" as a server so he will get the correct prefix (no prefix),
+ // then after play has been pressed the proper type is selected (server or client).
+ // m_playerID, which is used as a prefix on clients, will be updated to a proper value by the server after the connect.
+ NetworkViewID AllocateViewID();
+ // This allocate routine is for use in the editor, here we don't want any prefix and the
+ // number is decremented with each new ID (opposed to incrementing it like AllocateViewID does).
+ NetworkViewID AllocateSceneViewID();
+ NetworkViewID ValidateSceneViewID(NetworkView* validateView, NetworkViewID viewID);
+
+
+ void SetLevelPrefix(int levelPrefix);
+ int GetLevelPrefix() { return m_LevelPrefix; }
+
+ int GetPlayerID();
+
+ int GetPeerType();
+ int GetDebugLevel();
+ void SetDebugLevel(int value);
+
+ // Get this players address
+ SystemAddress GetPlayerAddress();
+
+ // Lookup a player ID number in the player address table
+ /// UNASSIGNED_SYSTEM_ADDRESS if can't be found
+ SystemAddress GetSystemAddressFromIndex(NetworkPlayer playerIndex);
+
+ /// the player index for the adress.
+ // -1 if can't be found. Server is always 0.
+ int GetIndexFromSystemAddress(SystemAddress playerAddress);
+
+ PlayerTable* GetPlayerEntry(SystemAddress playerAddress);
+ PlayerTable* GetPlayerEntry(NetworkPlayer playerAddress);
+
+ // Get all the registered player addresses
+ std::vector<PlayerTable> GetPlayerAddresses();
+
+ // For user scripting
+ bool IsClient();
+ bool IsServer();
+
+ void SetReceivingGroupEnabled (int player, int group, bool enabled);
+ void SetSendingGroupEnabled (int group, bool enabled);
+ void SetSendingGroupEnabled (int playerIndex, int group, bool enabled);
+
+ bool MayReceiveFromPlayer( SystemAddress player, int group );
+ bool MaySendToPlayer( SystemAddress adress, int group );
+ bool MaySend( int group );
+
+ void SetMessageQueueRunning(bool run);
+ bool GetMessageQueueRunning() { return m_MessageQueueRunning; }
+
+ bool WasViewIdAllocatedByMe(NetworkViewID viewID);
+ bool WasViewIdAllocatedByPlayer (NetworkViewID viewID, NetworkPlayer playerID);
+ NetworkPlayer GetNetworkViewIDOwner(NetworkViewID viewID);
+
+ void SetSimulation (NetworkSimulation simulation);
+ string GetStats(int player);
+
+ void SetSendRate (float rate) { m_Sendrate = rate; }
+ float GetSendRate () { return m_Sendrate; }
+
+// void LoadLevel(const std::string& levelName);
+// void LoadLevel(int level);
+
+ int GetMaxConnections();
+ int GetConnectionCount();
+ void GetConnections(int* connection);
+
+ bool IsPasswordProtected();
+
+ std::string GetIPAddress();
+ std::string GetIPAddress(int player);
+ SystemAddress GetServerAddress() { return m_ServerAddress; }
+ std::string GetExternalIP();
+ int GetExternalPort();
+
+ int GetPort();
+ int GetPort(int player);
+
+ std::string GetGUID();
+ std::string GetGUID(int player);
+
+// void SetChannelEnabled (UInt32 channel, bool enabled);
+// bool GetChannelEnabled (UInt32 channel) { return m_EnabledChannels & (1 << channel); }
+
+ // If viewId is 0 all view ids will be removed
+ // If playerIndex is -1 all players will be removed
+ void RemoveRPCs(NetworkPlayer playerIndex, NetworkViewID viewID, UInt32 channelMask);
+
+ bool IsConnected() { return m_PeerType != kDisconnected; };
+
+ double GetTime();
+ RakNetTime GetTimestamp();
+
+ bool GetUseNat();
+ void SetUseNat(bool enabled);
+
+ int GetLastPing (NetworkPlayer player);
+ int GetAveragePing (NetworkPlayer player);
+
+ Object* Instantiate (Object& asset, Vector3f pos, Quaternionf rot, UInt32 group);
+ Object* NetworkInstantiateImpl (RakNet::BitStream& bitstream, SystemAddress sender, RakNetTime time);
+ static void RPCNetworkInstantiate (RPCParameters* params);
+
+ void SetAssetToPrefab (const AssetToPrefab& mapping);
+
+ RakPeerInterface* GetPeer();
+
+ void ResolveFacilitatorAddress();
+ SystemAddress& GetFacilitatorAddress(bool resolve = true);
+ void SwapFacilitatorID(SystemAddress newAddress);
+
+ int TestConnection(bool forceNATType, bool forceTest = false);
+ SystemAddress GetConnTesterAddress() { return m_ConnTesterAddress; }
+ void SetConnTesterAddress(SystemAddress address);
+
+ void SetOldMasterServerAddress(SystemAddress address);
+
+ void SetMinimumAllocatableViewIDs (int v) { m_MinimumAllocatableViewIDs = v; m_NetworkViewIDAllocator.SetMinAvailableViewIDs(v); }
+ int GetMinimumAllocatableViewIDs () { return m_MinimumAllocatableViewIDs; }
+
+ void SetMaxConnections(int connections);
+
+ void PingWrapper(Ping *time);
+
+ void SetProxyIP(string address) { m_ProxyAddress.SetBinaryAddress(address.c_str()); }
+ string GetProxyIP() { return string(m_ProxyAddress.ToString(false)); }
+ void SetProxyPort(int port) { m_ProxyAddress.port = port; }
+ int GetProxyPort() { return m_ProxyAddress.port; }
+ SystemAddress GetProxyAddress() { return m_ProxyAddress; }
+ void SetUseProxy(bool value) { m_UseProxy = value; }
+ bool GetUseProxy() { return m_UseProxy; }
+ string GetProxyPassword() { return m_ProxyPassword; }
+ void SetProxyPassword(string password) { m_ProxyPassword = password; }
+
+ int GetInitIndexSize() { return m_UsedInitIndices.size(); }
+
+ private:
+
+ void ProcessPacket(unsigned char packetIdentifier);
+ void ResolveProxyAddress();
+
+// void DumpNewConnectionInitialData( SystemAddress player, int channel );
+ void SendRPCBuffer (PlayerTable &player);
+ static void RPCReceiveViewIDBatch (RPCParameters *rpcParameters);
+ static void RPCRequestViewIDBatch (RPCParameters *rpcParameters);
+ static void RPCNetworkDestroy(RPCParameters *rpcParms);
+
+ // Successfully connected to server. Request rpc buffer contents.
+ void MsgConnected();
+ // New client connected to server. Send initialization message back to him (contains player ID number)
+ void MsgNewConnection(SystemAddress sender = UNASSIGNED_SYSTEM_ADDRESS);
+ void MsgStateUpdate(SystemAddress senderAddress);
+ void MsgClientInit();
+ void MsgRemoveRPCs();
+ void MsgRemovePlayerRPCs();
+ void MsgClientDidDisconnect();
+ void MsgClientDidDisconnect(SystemAddress clientAddress);
+
+ int GetValidInitIndex();
+ void ClientConnectionDisconnected(int msgType);
+
+private:
+
+ typedef List< ListNode<NetworkView> > NetworkViewList;
+ typedef NetworkViewList::iterator NetworkViewIterator;
+
+ bool m_MessageQueueRunning;
+ float m_Sendrate;
+ float m_LastSendTime;
+// float m_TimeoutTime;
+ int m_PeerType; ///< enum { Server = 0, Client = 1, Server-Client = 2}
+ int m_PlayerID; // Player ID number
+ int m_HighestPlayerID;// Available player ID numbers (server) This is used only on the server
+ int m_MinimumAllocatableViewIDs;
+ RakPeerInterface *m_Peer;
+ Packet *m_Packet;
+ int m_LevelPrefix;
+ RakNet::BitStream m_BitStream;
+ SystemAddress m_ServerAddress; // The system address of the server
+ std::string m_ServerPassword;
+ RakNetGUID m_ServerGUID;
+ RPCBuffer m_RPCBuffer;
+ NetworkViewList m_Sources; // The set of object views which are to be synchronized
+ NetworkViewList m_NonSyncSources; // Network views which do not sync with other network objects
+ NetworkViewList m_AllSources;
+
+ PlayerAddresses m_Players;
+ int m_DebugLevel; ///< enum { Off = 0, Informational = 1, Full = 2}
+ NetworkViewIDAllocator m_NetworkViewIDAllocator;
+ UInt32 m_SendingEnabled;
+ UInt32 m_ReceivedInitialState;
+ bool m_DoNAT;
+ NatPunchthroughClient m_NatPunchthrough;
+ SystemAddress m_FacilitatorID;
+ bool m_ConnectingAfterPing;
+ time_t m_PingConnectTimestamp;
+ SystemAddress m_OldMasterServerID;
+ SystemAddress m_OldFacilitatorID;
+ dynamic_bitset m_UsedInitIndices;
+ ConnectionTester *m_ConnTester;
+ int m_ConnStatus;
+ SystemAddress m_ConnTesterAddress;
+ int m_MaxConnections;
+ Thread m_PingThread;
+ PingQueue m_PingQueue;
+ SystemAddress m_ProxyAddress;
+ bool m_UseProxy;
+ string m_ProxyPassword;
+ unsigned short m_RelayPort;
+
+ AssetToPrefab m_AssetToPrefab;
+ PrefabToAsset m_PrefabToAsset;
+};
+
+void NetworkError (Object* obj, const char* format, ...);
+void NetworkWarning (Object* obj, const char* format, ...);
+void NetworkInfo (Object* obj, const char* format, ...);
+void NetworkLog (Object* obj, const char* format, ...);
+
+NetworkManager& GetNetworkManager ();
+NetworkManager* GetNetworkManagerPtr ();
+
+#endif
+#endif //UNITY_NETWORK_MANAGER_H_
diff --git a/Runtime/Network/NetworkStubs.h b/Runtime/Network/NetworkStubs.h
new file mode 100644
index 0000000..1cca813
--- /dev/null
+++ b/Runtime/Network/NetworkStubs.h
@@ -0,0 +1,77 @@
+#ifndef UNITY_NETWORK_STUBS_H_
+#define UNITY_NETWORK_STUBS_H_
+
+#if !ENABLE_NETWORK
+typedef int RPCParameters;
+
+struct SystemAddress
+{
+ const char *ToString(bool writePort=true) const { return "dummy-system-address"; }
+ void SetBinaryAddress(const char *str) {}
+
+ unsigned int binaryAddress;
+ unsigned short port;
+};
+
+//typedef int NetworkPlayer;
+typedef int AssetToPrefab;
+typedef int NatPunchthrough;
+
+typedef unsigned int RakNetTime;
+typedef long long RakNetTimeNS;
+
+class RakPeerInterface;
+//class NetworkMessageInfo;
+
+//typedef int PlayerTable;
+//typedef int HostData;
+
+/*class Ping
+{
+ int m_Time;
+ bool m_IsDone;
+ std::string m_IP;
+// Mutex m_Mutex;
+
+ public:
+ int GetTime() { return m_Time; }
+ void SetTime(int value) { m_Time = value; }
+ int GetIsDone() { return m_IsDone; }
+ void SetIsDone(bool value) { m_IsDone=value; }
+ std::string GetIP() { return m_IP; }
+ void SetIP(std::string value) { m_IP = value; }
+};*/
+
+class BitstreamPacker
+{
+public:
+ template<class A>
+ void Serialize( A const& a ) {}
+
+ template<class A, class B>
+ void Serialize( A const& a, B const& b ) {}
+
+ bool IsReading() const { return false; }
+ bool IsWriting() const { return false; }
+};
+
+namespace RakNet {
+
+class BitStream;
+
+}
+
+typedef int PacketPriority;
+/*{
+ SYSTEM_PRIORITY, /// \internal Used by RakNet to send above-high priority messages.
+ HIGH_PRIORITY, /// High priority messages are send before medium priority messages.
+ MEDIUM_PRIORITY, /// Medium priority messages are send before low priority messages.
+ LOW_PRIORITY, /// Low priority messages are only sent when no other messages are waiting.
+ NUMBER_OF_PRIORITIES
+};*/
+
+
+#endif
+
+
+#endif // UNITY_NETWORK_STUBS_H_
diff --git a/Runtime/Network/NetworkUtility.cpp b/Runtime/Network/NetworkUtility.cpp
new file mode 100644
index 0000000..976366b
--- /dev/null
+++ b/Runtime/Network/NetworkUtility.cpp
@@ -0,0 +1,1076 @@
+#include "UnityPrefix.h"
+#include "NetworkUtility.h"
+
+#if UNITY_OSX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+#include <errno.h>
+#include "External/RakNet/builds/include/GetTime.h"
+#elif UNITY_WIN
+#include "External/RakNet/builds/include/GetTime.h"
+#include <winsock2.h>
+#if UNITY_WINRT
+#include "PlatformDependent/MetroPlayer/MetroUtils.h"
+#endif
+#if !UNITY_WP8
+#include <iphlpapi.h>
+#include <windns.h>
+#endif
+#elif UNITY_WII || UNITY_PEPPER
+#pragma message("Dummy network")
+#elif UNITY_IPHONE || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN
+#include <ifaddrs.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#elif UNITY_PS3
+#include <netex/libnetctl.h>
+typedef unsigned long u_long;
+#elif UNITY_XENON
+#include <Xtl.h>
+#elif UNITY_ANDROID
+#include "PlatformDependent/AndroidPlayer/EntryPoint.h"
+#include "PlatformDependent/AndroidPlayer/AndroidSystemInfo.h"
+#include "Runtime/Threads/Thread.h"
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <unistd.h>
+#include <errno.h>
+#include <android/log.h>
+#if DEBUGMODE
+ #define TRACE_PING(...) __android_log_print(ANDROID_LOG_VERBOSE, "PING", __VA_ARGS__)
+#else
+ #define TRACE_PING(...)
+#endif
+#elif UNITY_FLASH
+#elif ENABLE_NETWORK
+#error "Unsupported platform"
+#endif
+
+
+#if ENABLE_NETWORK
+#include "Runtime/BaseClasses/MessageIdentifier.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "NetworkEnums.h"
+#include "External/RakNet/builds/include/GetTime.h"
+#include "External/RakNet/builds/include/SocketLayer.h"
+#include "Configuration/UnityConfigureVersion.h"
+#endif
+
+
+void NetworkInitialize ()
+{
+#if USE_WINSOCK_APIS
+ WSADATA WsaData;
+ WSAStartup( MAKEWORD(2,2), &WsaData );
+#endif
+}
+
+void NetworkCleanup ()
+{
+#if USE_WINSOCK_APIS
+ WSACleanup();
+#endif
+}
+
+
+std::string GetLocalIP()
+{
+ std::string returnValue = "0.0.0.0";
+
+ #if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_ANDROID || UNITY_BB10 || UNITY_TIZEN
+
+ struct in_addr remote;
+ struct sockaddr_in raddress;
+ struct sockaddr *raddr = (struct sockaddr *)&raddress;
+
+ struct sockaddr_in laddress;
+ struct sockaddr *laddr = (struct sockaddr *)&laddress;
+
+ int sock;
+ int err;
+
+ std::string result;
+
+ sock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
+ if ( sock < 1 ) {
+ perror("GetLocalIP: Error setting socket");
+ return returnValue;
+ }
+
+ // www.unity3d.com hardcoded
+ inet_aton("83.221.146.11", &remote);
+
+ raddress.sin_port = htons(80);
+ raddress.sin_family = AF_INET;
+ raddress.sin_addr = remote;
+
+ err = connect(sock, raddr, sizeof(raddress ));
+ if ( err < 0 ) {
+ perror("GetLocalIP: Error during connect");
+ char availableIPs[10][16];
+ int ipCount = GetIPs(availableIPs);
+ close(sock);
+ if (ipCount > 0) return availableIPs[0];
+ return returnValue;
+ }
+
+ socklen_t len = sizeof(laddress);
+ err = getsockname(sock, laddr, &len );
+ if ( err < 0 ) {
+ perror("GetLocalIP: Error using getsockname");
+ char availableIPs[10][16];
+ int ipCount = GetIPs(availableIPs);
+ close(sock);
+ if (ipCount > 0) return availableIPs[0];
+ return returnValue;
+ }
+
+ close(sock);
+
+ returnValue = std::string(inet_ntoa(laddress.sin_addr));
+
+ #elif UNITY_WIN
+
+ #if !UNITY_METRO
+
+ WSADATA WSAData;
+ SOCKET s;
+
+ // Initialize winsock
+ if( ::WSAStartup(MAKEWORD(2, 2), &WSAData) != 0 ) {
+ printf_console( "GetLocalIP: Failed to initialize winsock\n" );
+ return returnValue;
+ }
+
+ s = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
+ if ( s == INVALID_SOCKET ) {
+ printf_console("GetLocalIP: Error setting socket, %d", ::WSAGetLastError());
+ return returnValue;
+ }
+
+ struct sockaddr_in raddress;
+ struct sockaddr *raddr = (struct sockaddr *)&raddress;
+
+ struct sockaddr_in laddress;
+ struct sockaddr * laddr = (struct sockaddr *)&laddress;
+
+ int err;
+
+ // www.unity3d.com hardcoded
+ char target[16];
+ strcpy(target, "83.221.146.11");
+
+ raddress.sin_family = AF_INET;
+ raddress.sin_port = htons(80);
+ raddress.sin_addr.s_addr = inet_addr(target);
+
+ err = connect(s, raddr, sizeof(raddress ));
+ if ( err != 0 ) {
+ printf_console("GetLocalIP: Error during connect, %d ", ::WSAGetLastError());
+ return returnValue;
+ }
+
+ int len = sizeof(laddress);
+ err = getsockname(s, laddr, &len );
+ if ( err == SOCKET_ERROR ) {
+ printf_console("GetLocalIP: Error using getsockname, %d ", ::WSAGetLastError());
+ return returnValue;
+ }
+
+ closesocket(s);
+
+ ::WSACleanup();
+
+ returnValue = std::string(inet_ntoa(laddress.sin_addr));
+
+ #else
+
+ #pragma message("todo: implement") // ?!-
+
+ #endif
+
+ #elif UNITY_WII || UNITY_PS3 || UNITY_PEPPER || UNITY_FLASH
+
+ returnValue = "127.0.0.1";
+
+ #elif UNITY_XENON
+
+ XNADDR xboxAddr;
+ DWORD ret = XNetGetTitleXnAddr(&xboxAddr);
+ if ((ret != XNET_GET_XNADDR_PENDING) && // IP address can not be determined yet
+ (ret & XNET_GET_XNADDR_NONE) == 0) // IP address is not available
+ {
+ char buffer[64];
+ if (XNetInAddrToString(xboxAddr.ina, buffer, sizeof(buffer)) == 0)
+ {
+ returnValue = buffer;
+ }
+ }
+
+ #elif ENABLE_NETWORK
+ #error "Unsupported platform"
+ #endif
+
+ return returnValue;
+}
+
+int GetIPs( char ips[10][16] )
+{
+ memset( ips, 0, 10*16 );
+ int ipnumber = 0;
+
+#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN
+
+ struct ifaddrs *myaddrs, *ifa;
+ struct sockaddr_in *addr;
+ int status;
+ status = getifaddrs(&myaddrs);
+ if (status != 0)
+ {
+ perror("Error getting interface addresses");
+ }
+
+ for (ifa = myaddrs; ifa != NULL; ifa = ifa->ifa_next)
+ {
+ if (ifa->ifa_addr == NULL) continue;
+ // Discard inactive interfaces
+ if ((ifa->ifa_flags & IFF_UP) == 0) continue;
+
+ // Get IPV4 address
+ if (ifa->ifa_addr->sa_family == AF_INET)
+ {
+ addr = (struct sockaddr_in *)(ifa->ifa_addr);
+ if (inet_ntop(ifa->ifa_addr->sa_family, (void *)&(addr->sin_addr), ips[ipnumber], sizeof(ips[ipnumber])) == NULL)
+ {
+ printf_console("%s: inet_ntop failed!\n", ifa->ifa_name);
+ }
+ else
+ {
+ if (strcmp(ips[ipnumber], "127.0.0.1")==0)
+ continue;
+ else
+ {
+ ipnumber++;
+ if (ipnumber == 10) break;
+ }
+ }
+ }
+ }
+
+ freeifaddrs(myaddrs);
+
+#elif UNITY_WIN
+
+ #if !UNITY_WINRT
+
+ PMIB_IPADDRTABLE ipAddrTable;
+ DWORD tableSize = 0;
+ IN_ADDR in_addr;
+
+ ipAddrTable = (MIB_IPADDRTABLE*)UNITY_MALLOC(kMemNetwork, sizeof(MIB_IPADDRTABLE));
+
+ if (ipAddrTable)
+ {
+ // If sizeof(MIB_IPADDRTABLE) is not enough, allocate appropriate size
+ if (GetIpAddrTable(ipAddrTable, &tableSize, 0) == ERROR_INSUFFICIENT_BUFFER)
+ {
+ UNITY_FREE(kMemNetwork, ipAddrTable);
+ ipAddrTable = (MIB_IPADDRTABLE*)UNITY_MALLOC(kMemNetwork, tableSize);
+ }
+ }
+ if (ipAddrTable == NULL)
+ {
+ printf_console("Memory allocation failed for GetIpAddrTable\n");
+ return 0;
+ }
+ else
+ {
+ // Get actual data
+ DWORD status = 0;
+ if ((status = GetIpAddrTable(ipAddrTable, &tableSize, 0)) != NO_ERROR)
+ {
+ printf_console("GetIpAddrTable failed with error %d\n", status);
+ LPVOID messageBuffer;
+ if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, status, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&messageBuffer, 0, NULL))
+ {
+ printf_console("Error: %s", messageBuffer);
+ LocalFree(messageBuffer);
+ UNITY_FREE(kMemNetwork, ipAddrTable);
+ ipAddrTable = NULL;
+ }
+ return 0;
+ }
+ }
+
+ for (int i = 0; i < (int)ipAddrTable->dwNumEntries; i++)
+ {
+ in_addr.S_un.S_addr = (u_long)ipAddrTable->table[i].dwAddr;
+ strcpy( ips[ipnumber], inet_ntoa(in_addr) );
+ if (strcmp(ips[ipnumber], "127.0.0.1") == 0)
+ continue;
+ else
+ {
+ ++ipnumber;
+ if (ipnumber == 10) break;
+ }
+ }
+
+ if (ipAddrTable)
+ {
+ UNITY_FREE(kMemNetwork, ipAddrTable);
+ ipAddrTable = NULL;
+ }
+
+ #else
+ using namespace Windows::Networking::Connectivity;
+
+ auto hostnames = NetworkInformation::GetHostNames();
+ for (int i = hostnames->Size-1; i >= 0; --i)
+ {
+ auto ipInfo = hostnames->GetAt(i)->IPInformation;
+
+ if (ipInfo == nullptr)
+ continue;
+
+ auto name = hostnames->GetAt(i)->CanonicalName;
+
+ // IPv4 only - TODO IPv6
+ if (name->Length() > 16)
+ continue;
+
+ strcpy( ips[ipnumber], ConvertStringToUtf8(name).c_str() );
+ ++ipnumber;
+ if (ipnumber == 10) break;
+ }
+ if (ipnumber < 10)
+ {
+ strcpy( ips[ipnumber], "127.0.0.1" );
+ ++ipnumber;
+ }
+ #endif
+
+#elif UNITY_WII || UNITY_PEPPER || UNITY_FLASH
+
+#elif UNITY_PS3
+ cellNetCtlInit();
+ CellNetCtlInfo info;
+ cellNetCtlGetInfo(CELL_NET_CTL_INFO_IP_ADDRESS, &info);
+ ipnumber = 1;
+ strcpy(&ips[0][0], info.ip_address);
+
+
+#elif UNITY_XENON
+
+ std::string localIp = GetLocalIP();
+ if (localIp.length() > 0 && localIp.length() < 16)
+ {
+ ipnumber = 1;
+ strcpy(&ips[0][0], localIp.c_str());
+ }
+
+#elif UNITY_ANDROID
+ struct ifconf ifc;
+ struct ifreq ifreqs[8];
+ memset(&ifc, 0, sizeof(ifc));
+ ifc.ifc_buf = (char*) (ifreqs);
+ ifc.ifc_len = sizeof(ifreqs);
+
+ struct ifreq* IFR;
+
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ {
+ printf_console("android.permission.INTERNET not available?");
+ return 0;
+ }
+
+ if ((ioctl(sock, SIOCGIFCONF, (char*) &ifc )) < 0 )
+ ifc.ifc_len = 0;
+
+ char* ifrp;
+ struct ifreq* ifr, ifr2;
+ IFR = ifc.ifc_req;
+ for (ifrp = ifc.ifc_buf;
+ (ifrp - ifc.ifc_buf) < ifc.ifc_len;
+ ifrp += sizeof(ifr->ifr_name) + sizeof(struct sockaddr))
+ {
+ ifr = (struct ifreq*)ifrp;
+
+ // Get network interface flags
+ ifr2 = *ifr;
+ if (ioctl(sock, SIOCGIFFLAGS, &ifr2) < 0)
+ continue;
+
+ // Discard inactive interfaces
+ if ((ifr2.ifr_flags & IFF_UP) == 0)
+ continue;
+
+ // Skip the loopback/localhost interface
+ if ((ifr2.ifr_flags & IFF_LOOPBACK))
+ continue;
+
+ if (ifr->ifr_addr.sa_family != AF_INET)
+ continue;
+
+ strcpy(&ips[ipnumber++][0], inet_ntoa(((struct sockaddr_in*)(&ifr->ifr_addr))->sin_addr));
+ if (ipnumber == 10) break;
+ }
+ close(sock);
+
+#elif ENABLE_NETWORK
+#error "Unsupported platform"
+#endif
+
+ return ipnumber;
+}
+
+std::string GetHostName ()
+{
+#if UNITY_OSX || (UNITY_WIN && !UNITY_WINRT) || UNITY_IPHONE
+ // Get local host name
+ char szHostName[128] = "";
+
+ if( ::gethostname(szHostName, sizeof(szHostName)) )
+ printf_console( "Failed to get host name - returning IP\n" );
+ else
+ return std::string(szHostName);
+#elif UNITY_WINRT
+ using namespace Windows::Networking::Connectivity;
+
+ auto hostnames = NetworkInformation::GetHostNames();
+ if (hostnames->Size > 0)
+ {
+ auto hostname = hostnames->GetAt(0)->DisplayName;
+ return ConvertStringToUtf8(hostname);
+ }
+#elif UNITY_XENON && !MASTER_BUILD
+
+ // Only available in development builds
+ char hostName[256] = "";
+ DWORD length = sizeof(hostName);
+ if (XBDM_NOERR == DmGetXboxName(hostName, &length))
+ return std::string(hostName);
+ else
+ printf_console( "Failed to get host name - returning IP\n" );
+#elif UNITY_ANDROID
+ return std::string(android::systeminfo::HardwareModel()) + ":" + GetLocalIP();
+#endif
+
+ return GetLocalIP();
+}
+
+
+
+
+#if ENABLE_SOCKETS && !UNITY_WINRT
+
+std::string InAddrToIP(sockaddr_in* in)
+{
+ #if UNITY_XENON
+ char ip[256] = "";
+ XNetInAddrToString(in->sin_addr, ip, sizeof(ip));
+ return std::string(ip);
+ #else
+ return std::string(inet_ntoa(in->sin_addr));
+ #endif
+}
+
+#endif
+
+#if ENABLE_NETWORK
+
+bool CompareToPrivateRange(u_long host_number)
+{
+ u_long network_number = htonl(host_number);
+
+ if ( (!(network_number > 167772160u && network_number < 184549375u) &&
+ !(network_number > 2851995648u && network_number < 2852061183u) &&
+ !(network_number > 2886729728u && network_number < 2887778303u) &&
+ !(network_number > 3232235520u && network_number < 3232301055u) ) &&
+ (network_number != 2130706433) )
+ return true;
+ return false;
+}
+
+bool CheckForPublicAddress()
+{
+ bool isPublic = false;
+
+ #if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_ANDROID || UNITY_WIN || UNITY_BB10 || UNITY_TIZEN
+
+ char availableIPs[10][16];
+ int ipCount = GetIPs(availableIPs);
+ for (int i=0; i<ipCount; ++i)
+ {
+ #if UNITY_WIN
+ unsigned int address;
+ #else
+ in_addr_t address;
+ #endif
+ address = inet_addr(availableIPs[i]);
+ isPublic = CompareToPrivateRange((u_long)address);
+ }
+
+ #elif UNITY_WII || UNITY_PEPPER || UNITY_PS3 || UNITY_XENON
+
+ #else
+ #error "Unsupported platform"
+ #endif
+
+ return isPublic;
+}
+
+#if UNITY_WIN
+char* DNSQueryRecursive(const char* domainName)
+{
+ IN_ADDR ipaddr;
+ char* ipAddress;
+ PDNS_RECORD pDnsRecord = NULL;
+
+ DNS_STATUS status = DnsQuery(domainName, DNS_TYPE_A, DNS_QUERY_STANDARD, NULL, &pDnsRecord, NULL);
+
+ if (status)
+ printf_console("DNSLookup: Error looking up %s (%d)\n", domainName, status);
+ // If it's a CNAME then the A record is bogus, so lookup again
+ else if (pDnsRecord->wType == DNS_TYPE_CNAME)
+ {
+ char* aliasName;
+ size_t size = strlen(pDnsRecord->Data.CNAME.pNameHost);
+ ALLOC_TEMP(aliasName, char, size+1);
+ strncpy(aliasName, pDnsRecord->Data.CNAME.pNameHost, size+1);
+ DnsRecordListFree(pDnsRecord, DnsFreeRecordListDeep);
+ return DNSQueryRecursive(aliasName);
+ }
+ else
+ {
+ ipaddr.S_un.S_addr = (pDnsRecord->Data.A.IpAddress);
+ ipAddress = inet_ntoa(ipaddr);
+ DnsRecordListFree(pDnsRecord, DnsFreeRecordListDeep);
+ return ipAddress;
+ }
+
+ return NULL;
+}
+#endif
+
+
+char* DNSLookup(const char* domainName)
+{
+ char* ipAddress;
+
+#if UNITY_WIN
+ ipAddress = DNSQueryRecursive(domainName);
+ // Fall back to gethostbyname()
+ if (!ipAddress)
+#endif
+ ipAddress = const_cast<char*>(SocketLayer::Instance()->DomainNameToIP(domainName));
+
+ return ipAddress;
+}
+
+void ResolveAddress(SystemAddress& address, const char* domainName, const char* betaDomainName, const char* errorMessage)
+{
+ if (address.binaryAddress == 0)
+ {
+ char* resolvedAddress;
+ if (UNITY_IS_BETA)
+ resolvedAddress = DNSLookup(betaDomainName);
+ else
+ resolvedAddress = DNSLookup(domainName);
+ if (resolvedAddress)
+ address.SetBinaryAddress(resolvedAddress);
+ else
+ ErrorString(errorMessage);
+ }
+}
+
+unsigned short makeChecksum(unsigned short *buffer, int size)
+{
+ unsigned long cksum=0;
+ while (size > 1)
+ {
+ cksum += *buffer++;
+ size -= sizeof(unsigned short);
+ }
+ if (size)
+ {
+ cksum += *(unsigned char*)buffer;
+ }
+ cksum = (cksum >> 16) + (cksum & 0xffff);
+ cksum += (cksum >>16);
+ return (unsigned short)(~cksum);
+}
+
+
+#if UNITY_WIN
+/*
+// ICMP structures for Windows
+typedef struct {
+ unsigned char Ttl; // Time To Live
+ unsigned char Tos; // Type Of Service
+ unsigned char Flags; // IP header flags
+ unsigned char OptionsSize; // Size in bytes of options data
+ unsigned char *OptionsData; // Pointer to options data
+} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION;
+
+typedef struct {
+ DWORD Address; // Replying address
+ unsigned long Status; // Reply status
+ unsigned long RoundTripTime; // RTT in milliseconds
+ unsigned short DataSize; // Echo data size
+ unsigned short Reserved; // Reserved for system use
+ void *Data; // Pointer to the echo data
+ IP_OPTION_INFORMATION Options; // Reply options
+} ICMP_ECHO_REPLY, * PICMP_ECHO_REPLY;
+*/
+
+// Wrapper for the ICMP library instance handle to make sure it unloads on quit.
+class PingLib
+{
+public:
+ PingLib() : hIcmp(NULL) {}
+
+ ~PingLib()
+ {
+ if(hIcmp)
+ FreeLibrary(hIcmp);
+ }
+
+ HINSTANCE getInstance() {
+ if (hIcmp == NULL)
+ {
+ hIcmp = LoadLibrary("icmp.dll");
+ if (hIcmp == 0)
+ printf_console("Unable to locate icmp.dll");
+ }
+ return hIcmp;
+ }
+private:
+ HINSTANCE hIcmp;
+};
+
+PingLib pingLib;
+
+#endif
+
+
+void* PingImpl(void* data)
+{
+ Ping& time = *(Ping*)data;
+
+ time.SetTime(-1);
+ time.SetIsDone(false);
+
+ #if UNITY_WIN
+
+ // Get an instance of the libary, it is only loaded once.
+ HINSTANCE hIcmp = pingLib.getInstance();
+
+ typedef HANDLE (WINAPI* pfnHV)(VOID);
+ typedef BOOL (WINAPI* pfnBH)(HANDLE);
+ typedef DWORD (WINAPI* pfnDHDPWPipPDD)(HANDLE, DWORD, LPVOID, WORD, PIP_OPTION_INFORMATION, LPVOID, DWORD, DWORD);
+ pfnHV pIcmpCreateFile;
+ pfnBH pIcmpCloseHandle;
+ pfnDHDPWPipPDD pIcmpSendEcho;
+ pIcmpCreateFile = (pfnHV)GetProcAddress(hIcmp, "IcmpCreateFile");
+ pIcmpCloseHandle = (pfnBH)GetProcAddress(hIcmp, "IcmpCloseHandle");
+ pIcmpSendEcho = (pfnDHDPWPipPDD)GetProcAddress(hIcmp, "IcmpSendEcho");
+ if ((pIcmpCreateFile == 0) || (pIcmpCloseHandle == 0) || (pIcmpSendEcho == 0))
+ {
+ printf_console("Failed to get proc addr info for ICMP functions.");
+ time.Release();
+ return NULL;
+ }
+
+ unsigned int targetAddress = inet_addr(time.GetIP().c_str());
+
+ HANDLE h = pIcmpCreateFile();
+ if (h == INVALID_HANDLE_VALUE) {
+ printf_console("Ping: Error creating icmp handle\n");
+ time.Release();
+ return NULL;
+ }
+
+ char pingBody[56];
+ memset(pingBody, 'X', 56);
+
+ LPVOID replyBuffer = malloc(sizeof(ICMP_ECHO_REPLY) + sizeof(pingBody));
+ if (!replyBuffer) {
+ printf_console("Ping: Error allocating reply buffer\n");
+ pIcmpCloseHandle(h);
+ time.Release();
+ return NULL;
+ }
+
+ unsigned int retval = pIcmpSendEcho(h, targetAddress, &pingBody, sizeof(pingBody), 0, replyBuffer, sizeof(ICMP_ECHO_REPLY) + sizeof(pingBody), 1000);
+ if (retval == 0)
+ {
+ printf_console("Ping: Error performing ICMP transmission. Possibly because of a timeout\n");
+ pIcmpCloseHandle(h);
+ free(replyBuffer);
+ time.Release();
+ return NULL;
+ }
+
+ PICMP_ECHO_REPLY rep = (PICMP_ECHO_REPLY)replyBuffer;
+
+ time.SetIsDone(true);
+ time.SetTime((int)rep->RoundTripTime);
+
+ pIcmpCloseHandle(h);
+ free(replyBuffer);
+
+ #elif UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN
+
+ int err = 0;
+ char packet[64];
+ int psize = 64;
+ unsigned short checksum;
+ struct sockaddr_in address_in;
+ struct sockaddr *address = (struct sockaddr *)&address_in;
+
+ if (time.GetIP().empty())
+ {
+ ErrorString("No IP present in PingTime structure.");
+ time.Release();
+ return NULL;
+ }
+
+ address_in.sin_family = AF_INET;
+ address_in.sin_addr.s_addr = inet_addr(time.GetIP().c_str());
+
+ // Use a ICMP datagram type IPv4 socket
+ int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
+
+ if (s < 0)
+ {
+ perror ("Ping: Error creating socket");
+ time.Release();
+ return NULL;
+ }
+
+ // Set timeout for recv
+ struct timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ err = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
+ if (err < 0)
+ {
+ perror("Ping: Error setting socket options");
+ time.Release();
+ return NULL;
+ }
+
+ err = connect(s, address, sizeof(address_in));
+ if (err < 0)
+ {
+ perror("Ping: Error during connect");
+ close(s);
+ time.Release();
+ return NULL;
+ }
+
+ // Prepare ping packet
+ memset(packet, 0, psize-1);
+ memset(packet+8, 'X', psize-9);
+ ((struct icmpheader*)packet)->type = 0x8;
+ checksum = makeChecksum( (unsigned short*)&packet, psize);
+ ((struct icmpheader*)packet)->checksum = checksum;
+
+ /*printf_console("Checksum is: %x\n", checksum);
+ printf_console("Packet contents: \n");
+ for (int i=0; i<psize-1; i++)
+ printf_console("%x", packet[i]);
+ printf_console("\n");*/
+
+ // NOTE: This is capable of doing multiple pings in a row with updated sequence numbers, but at the moment only a single ping is ever performed
+ RakNetTime currTime;
+ int count = 1;
+ int pingTime = -1;
+ for (int i=0; i < count; i++)
+ {
+ currTime = RakNet::GetTime();
+
+ // Send ping
+ err = send(s, (packet), psize, 0);
+ if ( err != psize )
+ {
+ perror("Ping: Error sending ICMP packet");
+ close(s);
+ time.Release();
+ return NULL;
+ }
+
+ // Receive reponse
+ char buffer[64];
+ err = recv(s, buffer, 64, 0);
+ if ( err < 0 )
+ {
+ // This is a timeout
+ perror("Ping: Error receiving ICMP packet response. Possibly a timeout.");
+ //lets just return -1 to keep it consistent with windows version
+ }
+ else if ( err == 0)
+ {
+ printf_console("Ping: Nothing to receive");
+ close(s);
+ time.Release();
+ return NULL;
+ }
+ else
+ {
+ pingTime = (int)(RakNet::GetTime() - currTime);
+ //printf_console("Ping: Ping nr. %d - Packet size is %d - Ping time %d ms\n", i, psize, pingTime);
+ }
+
+ // Update ping packet
+ ((struct icmpheader*)packet)->seq_num = htons(i+1);
+ ((struct icmpheader*)packet)->checksum = 0;
+ checksum = makeChecksum( (unsigned short*)&packet, psize);
+ ((struct icmpheader*)packet)->checksum = checksum;
+ }
+
+ // Stop receiving and transmitting
+ err = close(s);
+ if (err < 0)
+ perror ("Ping: Error closing socket");
+
+ time.SetTime(pingTime);
+ time.SetIsDone(true);
+
+ #elif UNITY_WII || UNITY_PEPPER || UNITY_PS3 || UNITY_XENON
+
+ data = 0;
+
+ #elif UNITY_ANDROID
+ // ICMP sockets are not available without root. The only protocols supported for applications are TCP and UDP.
+ // Because /system/bin/ping is a setuid program, we can use that to send ICMP packets.
+ // (using "/system/bin/ping -qnc 1 <addr>")
+
+ char ip[256];
+ strncpy(ip, time.GetIP().c_str(), sizeof(ip)); ip[sizeof(ip)-1] = 0;
+ time.SetTime(-1); // in case of error, return -1 to keep it consistent with windows version
+
+ // create stdout pipe from child process
+ int rwpipes[2];
+ if( pipe(rwpipes) )
+ {
+ ErrorStringMsg("Error creating pipe! (%s, %i)", strerror(errno), errno);
+ time.SetIsDone(true);
+ return NULL;
+ }
+ if( fcntl(rwpipes[0], F_SETFL, O_NONBLOCK) != 0 ||
+ fcntl(rwpipes[1], F_SETFL, O_NONBLOCK) != 0 )
+ {
+ ErrorStringMsg("Unable to set non-blocking pipe! (%s, %i)", strerror(errno), errno);
+ time.SetIsDone(true);
+ return NULL;
+ }
+
+ TRACE_PING("forking process @ %s:%i", __FUNCTION__, __LINE__);
+ // fork process
+ pid_t pid;
+ if( (pid=fork()) == -1)
+ {
+ ErrorStringMsg("Error forking process! (%s, %i)", strerror(errno), errno);
+ time.SetIsDone(true);
+ return NULL;
+ }
+
+ TRACE_PING("process forked @ %s:%i", __FUNCTION__, __LINE__);
+
+ if (!pid) // <child> process fork
+ {
+ TRACE_PING("secondary process @ %s:%i", __FUNCTION__, __LINE__);
+
+ TRACE_PING("dup'ing pipes @ %s:%i", __FUNCTION__, __LINE__);
+ dup2(rwpipes[1],1); // replace stdout with write pipe ..
+ dup2(rwpipes[1],2); // .. and stderr too ..
+
+ TRACE_PING("closing pipes @ %s:%i", __FUNCTION__, __LINE__);
+ close(rwpipes[0]); // .. close read pipe ..
+
+ TRACE_PING("exec'ing @ %s:%i", __FUNCTION__, __LINE__);
+ const char* ping = "/system/bin/ping";
+ if(execl(ping, ping, "-qnc", "1", ip, NULL) == -1)
+ {
+ printf("Error spawning child process '%s'! (%s, %i)", ping, strerror(errno), errno);
+ exit(1);
+ }
+
+ TRACE_PING("error? @ %s:%i", __FUNCTION__, __LINE__);
+ }
+
+ // <parent> process fork ...
+ TRACE_PING("parent fork @ %s:%i", __FUNCTION__, __LINE__);
+
+ // close write pipe
+ close(rwpipes[1]);
+
+ std::string output;
+ char buffer[256];
+
+ TRACE_PING("starting looping @ %s:%i", __FUNCTION__, __LINE__);
+
+ // loop until child process has died...
+ const double sleep_value = 0.5; // seconds
+ const double max_wait = 5.0; // seconds
+ int num_loops = (int)ceil(max_wait/sleep_value);
+ int ret_value;
+ while(waitpid(pid, &ret_value, WNOHANG) == 0)
+ {
+ TRACE_PING("reading @ %s:%i", __FUNCTION__, __LINE__);
+ int num_read = read(rwpipes[0], buffer, sizeof(buffer));
+ if (num_read > 0)
+ output.append(buffer, num_read);
+
+ TRACE_PING("sleeping # %i @ %s:%i", num_loops, __FUNCTION__, __LINE__);
+ Thread::Sleep(sleep_value);
+ if (num_loops-- < 0)
+ break;
+ }
+
+ // .. read any leftovers in the pipe ..
+ int num_read = read(rwpipes[0], buffer, sizeof(buffer));
+ if (num_read > 0)
+ output.append(buffer, num_read);
+
+ // ... and close read pipe
+ close(rwpipes[0]);
+
+ if (num_loops < 0)
+ {
+ TRACE_PING("pid killed @ %s:%i", __FUNCTION__, __LINE__);
+ kill(pid, SIGKILL);
+ ret_value = num_loops;
+ }
+
+ if (ret_value != 0)
+ {
+ ErrorString(output.c_str());
+ time.SetIsDone(true);
+ return NULL;
+ }
+
+ float ping_rtt;
+ size_t rtt_pos = output.rfind(" = ");
+ if (rtt_pos == std::string::npos || sscanf(&output[rtt_pos], "%*s%f", &ping_rtt) != 1)
+ {
+ ErrorStringMsg("Error parsing ping output!\n%s", output.c_str());
+ time.SetIsDone(true);
+ return NULL;
+ }
+
+ TRACE_PING("ping done %i @ %s:%i", (int)ping_rtt, __FUNCTION__, __LINE__);
+
+ time.SetTime((int)ping_rtt);
+ time.SetIsDone(true);
+
+ #else
+ #error "Unsupported platform"
+ #endif
+
+ time.Release();
+ return NULL;
+}
+
+Ping::Ping (const std::string& ip)
+{
+ m_Time = -1;
+ m_IsDone = false;
+ m_IP = ip;
+ m_Refcount = 1;
+}
+
+int Ping::GetTime()
+{
+ Mutex::AutoLock lock(m_Mutex);
+ return m_Time;
+}
+
+void Ping::SetTime(int value)
+{
+ Mutex::AutoLock lock(m_Mutex);
+ m_Time = value;
+}
+
+int Ping::GetIsDone()
+{
+ Mutex::AutoLock lock(m_Mutex);
+ return m_IsDone;
+}
+
+void Ping::SetIsDone(bool value)
+{
+ Mutex::AutoLock lock(m_Mutex);
+ m_IsDone = value;
+}
+
+std::string Ping::GetIP()
+{
+ Mutex::AutoLock lock(m_Mutex);
+ return m_IP;
+}
+
+void Ping::Retain()
+{
+ Mutex::AutoLock lock(m_Mutex);
+ m_Refcount++;
+}
+
+void Ping::Release()
+{
+ Mutex::AutoLock lock(m_Mutex);
+ m_Refcount--;
+ if (m_Refcount == 0)
+ delete this;
+}
+
+void SendToAllNetworkViews (const MessageIdentifier& msg, int inData)
+{
+ MessageData data;
+ data.SetData(inData, ClassID (int));
+ SendMessageToEveryone(msg, data);
+}
+
+void SendToAllNetworkViews (const MessageIdentifier& msg)
+{
+ MessageData data;
+ SendMessageToEveryone(msg, data);
+}
+
+// These are machine specific + depend on network connection, so we can't run them by default
+#if ENABLE_UNIT_TESTS && 0
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+TEST(ResolveAddressWorks)
+{
+ char* ip = DNSLookup("masterserver.unity3d.com");
+ const char* expected = "67.225.180.24";
+ bool isValid = strncmp(expected, ip, strlen(expected)) == 0;
+ CHECK(isValid);
+}
+
+TEST(CollectAllIPsWorks)
+{
+ char ips[10][16];
+ int count = GetIPs(ips);
+ printf ("Got %d ips\n", count);
+ for (int i=0; i<count; i++)
+ printf("\t%s\n", ips[i]);
+ CHECK(count == 3);
+}
+
+#endif // ENABLE_UNIT_TESTS
+
+#endif // ENABLE_NETWORK
diff --git a/Runtime/Network/NetworkUtility.h b/Runtime/Network/NetworkUtility.h
new file mode 100644
index 0000000..703ac28
--- /dev/null
+++ b/Runtime/Network/NetworkUtility.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include "Runtime/Threads/Mutex.h"
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Network/Sockets.h"
+
+#if ENABLE_NETWORK
+#include "External/RakNet/builds/include/RakNetTypes.h"
+#endif
+
+void NetworkInitialize();
+void NetworkCleanup();
+
+std::string GetLocalIP();
+int GetIPs(char ips[10][16]);
+std::string GetHostName();
+
+#if ENABLE_SOCKETS && !UNITY_WINRT
+std::string InAddrToIP(sockaddr_in* in);
+#endif
+
+#if ENABLE_NETWORK
+
+class MessageIdentifier;
+
+bool CheckForPublicAddress();
+
+char* DNSLookup(const char* domainName);
+void ResolveAddress(SystemAddress& address, const char* domainName, const char* betaDomainName, const char* errorMessage);
+
+unsigned short makeChecksum(unsigned short *buffer, int size);
+
+void* PingImpl(void* data);
+
+void SendToAllNetworkViews (const MessageIdentifier& msg, int inData);
+void SendToAllNetworkViews (const MessageIdentifier& msg);
+
+// The structure of the ICMP header. Mainly used to reference locations inside the ping packet.
+struct icmpheader
+{
+ unsigned char type;
+ unsigned char code;
+ unsigned short checksum;
+ unsigned short identifier;
+ unsigned short seq_num;
+};
+
+
+class Ping
+{
+ int m_Time;
+ bool m_IsDone;
+ std::string m_IP;
+ int m_Refcount;
+ Mutex m_Mutex;
+
+ public:
+
+ Ping (const std::string& ip);
+
+ int GetTime();
+ void SetTime(int value);
+
+ int GetIsDone();
+ void SetIsDone(bool value);
+
+ std::string GetIP();
+
+ void Retain ();
+ void Release ();
+
+};
+
+#endif
diff --git a/Runtime/Network/NetworkView.cpp b/Runtime/Network/NetworkView.cpp
new file mode 100644
index 0000000..eff6f7d
--- /dev/null
+++ b/Runtime/Network/NetworkView.cpp
@@ -0,0 +1,733 @@
+#include "UnityPrefix.h"
+#include "NetworkView.h"
+
+#if ENABLE_NETWORK
+#include "NetworkManager.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "PackStateSpecialized.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Dynamics/RigidBody.h"
+#include "Runtime/Animation/Animation.h"
+#include "Runtime/Mono/MonoScriptCache.h"
+#include "Runtime/Utilities/Utility.h"
+#include "PackMonoRPC.h"
+#include "BitStreamPacker.h"
+#include "External/RakNet/builds/include/RakPeerInterface.h"
+
+NetworkView::NetworkView (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_Node (this)
+, m_AllNode(this)
+{
+ m_OwnerAddress.binaryAddress = 0;
+ m_StateSynchronization = kReliableDeltaCompressed;
+ m_Group = 0;
+ m_InitState.clear();
+ m_Scope.clear();
+ m_HasReceivedInitialState = false;
+}
+
+void NetworkView::Update()
+{
+}
+
+// This static RPC function executes the user scripted function by taking name and input from rpcParameters.
+// Name contains the function name called and input contains the parameters for the function (in a BitStream).
+static void NetworkViewRPCCallScript (RPCParameters *rpcParameters);
+static void NetworkViewRPCCallScript (RPCParameters *rpcParameters)
+{
+ NetworkManager& nm = GetNetworkManager ();
+ NetworkViewID viewID;
+ UInt8 mode = 0;
+ RakNet::BitStream input (rpcParameters->input, BITS_TO_BYTES(rpcParameters->numberOfBitsOfData), false);
+
+ viewID.Read(input);
+ input.ReadBits(&mode, kRPCModeNbBits);
+
+ NetworkLog(NULL, "Received RPC '%s'- mode %d - sender %s", rpcParameters->functionName, GetTargetMode(mode), rpcParameters->sender.ToString());
+ // Specific target handling.
+ // We might have to reroute the RPC call to another player.
+ if (GetTargetMode(mode) == kSpecificTarget)
+ {
+ bool relaySpecificTarget = false;
+ input.Read(relaySpecificTarget);
+
+ // We have to reroute the RPC call to another player
+ if (relaySpecificTarget)
+ {
+ NetworkPlayer relayTarget;
+ input.Read(relayTarget);
+
+ NetworkLog(NULL, "Relay RPC to specifc target - player ID %s", relayTarget);
+
+ RakNet::BitStream rerouted;
+ rerouted.Write(viewID);
+ rerouted.WriteBits(&mode, kRPCModeNbBits);
+ rerouted.Write0();
+
+ int unreadBits = input.GetNumberOfUnreadBits();
+ UInt8* data;
+ ALLOC_TEMP(data, UInt8, BITS_TO_BYTES (unreadBits));
+ input.ReadBits(data, unreadBits, false);
+ rerouted.WriteBits(data, unreadBits, false);
+
+ nm.PerformRPCRelaySpecific(rpcParameters->functionName, &rerouted, relayTarget);
+ return;
+ }
+ }
+
+ // Get the view and observer which should receive the rpc call
+ NetworkView* view = nm.ViewIDToNetworkView(viewID);
+ int group = 0;
+ if (view != NULL)
+ {
+ group = view->GetGroup();
+ }
+ else
+ {
+ NetworkWarning(NULL, "Could't invoke RPC function '%s' because the networkView '%s' doesn't exist", rpcParameters->functionName, viewID.ToString().c_str());
+ return;
+ }
+
+ // Unpack and invoke rpc call
+ if (nm.MayReceiveFromPlayer(rpcParameters->sender, group))
+ {
+ nm.PeformRPCRelayAll(rpcParameters->functionName, mode, viewID, group, rpcParameters->remoteTimestamp, rpcParameters->sender, input);
+ UnpackAndInvokeRPCMethod (view->GetGameObject(), rpcParameters->functionName, input, rpcParameters->sender, view->GetViewID(), rpcParameters->remoteTimestamp, view);
+ }
+ else
+ {
+ NetworkInfo (NULL, "RPC %s is ignored since the group of the network view is disabled.");
+ return;
+ }
+}
+
+void NetworkView::RPCCall (const std::string &function, int inMode, MonoArray* args)
+{
+ NetworkManager& nm = GetNetworkManager();
+ if (!nm.IsConnected())
+ {
+ NetworkError(NULL, "Can't send RPC function since no connection was started.");
+ return;
+ }
+
+ if (!nm.MaySend(m_Group))
+ {
+ NetworkInfo (NULL, "RPC %s is ignored since the group of its network view is disabled.");
+ return;
+ }
+
+ UInt8 mode = inMode;
+
+ RakNet::BitStream parameters;
+ m_ViewID.Write(parameters);
+ parameters.WriteBits(&mode, kRPCModeNbBits);
+
+ int parameterOffset = parameters.GetWriteOffset();
+
+ if (PackRPCParameters (GetGameObject(), function.c_str(), parameters, args, this))
+ {
+ // Call ourselves immediately
+ if (GetTargetMode(mode) == kAll)
+ {
+ parameters.SetReadOffset(parameterOffset);
+ UnpackAndInvokeRPCMethod (GetGameObject(), function.c_str(), parameters, nm.GetPlayerAddress(), GetViewID(), nm.GetTimestamp(), this);
+ }
+
+ // Send the RPC function via network
+ nm.PerformRPC(function, mode, parameters, m_ViewID, m_Group);
+ }
+ else
+ {
+ //NetworkError(NULL, "Could't relay remote function message to Unity object");
+ }
+}
+
+void NetworkView::RPCCallSpecificTarget (const std::string &function, NetworkPlayer target, MonoArray* args)
+{
+ NetworkManager& nm = GetNetworkManager();
+ if (!nm.IsConnected())
+ {
+ NetworkError(NULL, "Can't send RPC function since no connection was started.");
+ return;
+ }
+
+ if (!nm.MaySend(m_Group))
+ {
+ NetworkInfo (NULL, "RPC %s is ignored since the group of its network view is disabled.");
+ return;
+ }
+
+ UInt8 mode = kSpecificTarget;
+
+ RakNet::BitStream parameters;
+ m_ViewID.Write(parameters);
+ parameters.WriteBits(&mode, kRPCModeNbBits);
+
+ PlayerTable *targetPlayer = GetNetworkManager().GetPlayerEntry(target);;
+ if (targetPlayer != NULL)
+ {
+ parameters.Write0();
+ }
+ else if (GetNetworkManager().IsClient())
+ {
+ parameters.Write1();
+ parameters.Write(target);
+ targetPlayer = GetNetworkManager().GetPlayerEntry(0);
+ }
+ else
+ {
+ NetworkError(this, "Can't send RPC function because the target is not connected to the server.");
+ return;
+ }
+
+ if (PackRPCParameters (GetGameObject(), function.c_str(), parameters, args, this))
+ {
+ // Send the rpc function via network
+ nm.PerformRPCSpecificTarget(const_cast<char*> (function.c_str()), targetPlayer, parameters, m_Group);
+ }
+ else
+ {
+ //NetworkError(NULL, "Could't relay remote function message to Unity object");
+ }
+}
+
+
+// Add this network view to the network manager so it will be synchronized over the network.
+void NetworkView::AddToManager ()
+{
+ if (m_StateSynchronization != kNoStateSynch)
+ GetNetworkManager ().AddNetworkView (m_Node);
+ else
+ GetNetworkManager ().AddNonSyncNetworkView (m_Node);
+}
+
+void NetworkView::RemoveFromManager ()
+{
+ m_Node.RemoveFromList();
+}
+
+void NetworkView::SetupSceneViewID ()
+{
+ // When in edit mode ensure that all objects get a view id.
+ // In play mode we maintain view id's
+ if (!IsWorldPlaying())
+ {
+ // No view id assigned, allocate new one
+ if (m_ViewID == NetworkViewID())
+ {
+ if (GetNetworkManager().GetDebugLevel() >= kInformational) LogString("Allocating scene view ID to new object");
+ m_ViewID = GetNetworkManager ().AllocateSceneViewID();
+ }
+ else
+ {
+ m_ViewID = GetNetworkManager ().ValidateSceneViewID(this, m_ViewID);
+ }
+ }
+}
+
+void NetworkView::AwakeFromLoad (AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad (mode);
+
+ GetNetworkManager().AddAllNetworkView(m_AllNode);
+
+ if (IsActive())
+ SetupSceneViewID ();
+
+ // When loading a new scene replace level prefix of all objects that are being loaded!
+ if (mode & kDidLoadFromDisk)
+ {
+ if (m_ViewID.IsSceneID())
+ m_ViewID.ReplaceLevelPrefix(GetNetworkManager().GetLevelPrefix());
+ }
+
+ if (IsPrefabParent())
+ m_ViewID.SetAllocatedID(0);
+}
+
+void NetworkView::SetObserved (Unity::Component* component)
+{
+ m_Observed = component;
+ SetDirty();
+}
+
+Unity::Component* NetworkView::GetObserved ()
+{
+ return m_Observed;
+}
+
+void NetworkView::Unpack (RakNet::BitStream& bitStream, NetworkMessageInfo& info, int msgType)
+{
+ // Initial can always be received, but make sure that the last unpack state is clear
+ if (msgType == ID_STATE_INITIAL)
+ {
+ m_HasReceivedInitialState = true;
+ m_LastUnpackState.clear();
+ }
+ // Updates can only be received after some initial state was already sent! Except when delta compression is not being used.
+ else
+ {
+ if (!m_HasReceivedInitialState && m_StateSynchronization == kReliableDeltaCompressed)
+ {
+ NetworkError(NULL, "Received state update for view ID %s but no initial state has ever been sent. Ignoring message.\n", m_ViewID.ToString().c_str());
+ return;
+ }
+ }
+
+ // Calculate the last state to perform delta compression against!
+ PackState writeState;
+ PackState* writeStatePtr = NULL;
+ UInt8* readData = NULL;
+ int readSize = 0;
+ if (m_StateSynchronization == kReliableDeltaCompressed)
+ {
+ writeState.resize(m_LastUnpackState.size());
+ writeStatePtr = &writeState;
+ readData = &m_LastUnpackState[0];
+ readSize = m_LastUnpackState.size();
+ }
+
+ // Pack state
+ BitstreamPacker packer (bitStream, writeStatePtr, readData, readSize, true);
+
+ Unity::Component* observed = GetObserved ();
+ Rigidbody* body = dynamic_pptr_cast<Rigidbody*> (observed);
+ Transform* transform = dynamic_pptr_cast<Transform*> (observed);
+ Animation* animation = dynamic_pptr_cast<Animation*> (observed);
+ MonoBehaviour* mono = dynamic_pptr_cast<MonoBehaviour*> (observed);
+
+ if (body)
+ SerializeRigidbody(*body, packer);
+ else if (transform)
+ UnpackTransform(*transform, packer);
+ else if (animation)
+ SerializeAnimation(*animation, packer);
+ else if (mono)
+ SerializeMono(*mono, packer, info);
+ else if (observed)
+ {
+ ErrorStringObject ("Network View synchronization error. Received packet but the observed class is not supported as a synchronization type", this);
+ }
+ else
+ {
+ LogStringObject("Receiving state for an object whose network view exists but the observed object no longer exists", this);
+ }
+
+ NetworkLog(NULL, "Received state update for view ID %s\n", m_ViewID.ToString().c_str());
+
+ m_LastUnpackState.swap(writeState);
+}
+
+bool NetworkView::Pack(RakNet::BitStream &stream, PackState* writeStatePtr, UInt8* readData, int &readSize, int msgID)
+{
+ // Pack the state with the appropriate specialized packer
+ Unity::Component* observed = GetObserved ();
+ Rigidbody* body = dynamic_pptr_cast<Rigidbody*> (observed);
+ Transform* transform = dynamic_pptr_cast<Transform*> (observed);
+ Animation* animation = dynamic_pptr_cast<Animation*> (observed);
+ MonoBehaviour* mono = dynamic_pptr_cast<MonoBehaviour*> (observed);
+
+ bool doSend = false;
+ stream.Reset();
+
+ if (GetNetworkManager().GetUseProxy() && GetNetworkManager().IsClient())
+ {
+ stream.Write((unsigned char) ID_PROXY_CLIENT_MESSAGE);
+ }
+ // For now always include timestamp
+ bool useTimeStamp = true;
+ RakNetTime timeStamp = GetNetworkManager().GetTimestamp();
+ if (useTimeStamp)
+ {
+ stream.Write((unsigned char)ID_TIMESTAMP);
+ stream.Write(timeStamp);
+ }
+
+ // Msg type
+ stream.Write((unsigned char)msgID);
+ // View ID
+ m_ViewID.Write(stream);
+
+ // Pack data
+ BitstreamPacker packer (stream, writeStatePtr, readData, readSize, false);
+ NetworkMessageInfo info;
+ info.timestamp = -1.0;
+ info.sender = -1;
+ info.viewID = GetViewID();
+
+ if (body)
+ doSend |= SerializeRigidbody(*body, packer);
+ else if (transform)
+ doSend |= PackTransform(*transform, packer);
+ else if (animation)
+ doSend |= SerializeAnimation(*animation, packer);
+ else if (mono)
+ doSend |= SerializeMono(*mono, packer, info);
+ else if (observed)
+ {
+ NetworkError (this, "Network View synchronization of %s is not supported. Pack the state manually from a script.", this);
+ return false;
+ }
+
+ return doSend;
+}
+
+inline bool MayReceiveGroup (PlayerTable& table, int group)
+{
+ return (table.mayReceiveGroups & (1<<group)) != 0;
+}
+
+// When broadcast is enabled, every connected peer gets the message except the one given in the system address
+// TODO: Optimize code for initial/update proxied clients, they could go into seperate list(s) and get different streams (no memcpy)
+// Right now real stream is memcpd'd into a stream with proxy header for each client which is proxied
+void NetworkView::Send (SystemAddress systemAddress, bool broadcast)
+{
+ // Calculate the last state to perform delta compression against!
+ PackState writeState;
+ PackState* writeStatePtr = NULL;
+ UInt8* readData = NULL;
+ int readSize = 0;
+
+ typedef std::vector<PlayerTable> Addresses;
+ Addresses initialStateAddresses;
+ Addresses updateStateAddresses;
+
+ // If doing broadcast, check if there is any intial state which needs to be sent
+ std::vector<PlayerTable> players = GetNetworkManager().GetPlayerAddresses();
+ updateStateAddresses.reserve(players.size());
+
+ // Find the players which need update or initial state
+ for (int i = 0; i != players.size(); i++)
+ {
+ // Calculate if we should include address based:
+ // * broadcast flag and ignore address
+ // * single cast must match address
+ bool include = broadcast && systemAddress != players[i].playerAddress;
+ include |= !broadcast && systemAddress == players[i].playerAddress;
+ bool maySendToPlayer = GetNetworkManager().MaySendToPlayer(players[i].playerAddress, m_Group);
+
+ if (((include && maySendToPlayer) || GetNetworkManager().IsClient()) && CheckScope(players[i].initIndex) )
+ {
+ bool hasInitialState = GetInitStateStatus(players[i].initIndex);
+ if (!hasInitialState)
+ {
+ initialStateAddresses.push_back(players[i]);
+ SetInitState(players[i].initIndex, true);
+ }
+ else
+ {
+ updateStateAddresses.push_back(players[i]);
+ }
+ }
+ }
+
+ RakPeerInterface* peer = GetNetworkManager().GetPeer();
+
+ // Send update state data
+ if (!updateStateAddresses.empty())
+ {
+ if (m_StateSynchronization == kReliableDeltaCompressed)
+ {
+ writeState.clear();
+ writeState.reserve(m_LastPackState.size());
+ writeStatePtr = &writeState;
+ readData = &m_LastPackState[0];
+ readSize = m_LastPackState.size();
+ }
+ else
+ {
+ writeStatePtr = NULL;
+ readData = NULL;
+ readSize = 0;
+ }
+
+ RakNet::BitStream stream;
+ RakNet::BitStream relayStream;
+ bool doSend = Pack(stream, writeStatePtr, readData, readSize, ID_STATE_UPDATE);
+ if (doSend)
+ {
+ PacketReliability reliability;
+ if (m_StateSynchronization == kReliableDeltaCompressed)
+ reliability = RELIABLE_ORDERED;
+ else
+ reliability = UNRELIABLE_SEQUENCED;
+
+ for (int i=0;i<updateStateAddresses.size();i++)
+ {
+ if (updateStateAddresses[i].relayed == true)
+ {
+ relayStream.Reset();
+ relayStream.Write((MessageID) ID_PROXY_SERVER_MESSAGE);
+ relayStream.Write(updateStateAddresses[i].playerAddress);
+ relayStream.Write((char*)stream.GetData(), stream.GetNumberOfBytesUsed());
+ if (!peer->Send (&relayStream, (PacketPriority)HIGH_PRIORITY, reliability, kDefaultChannel, GetNetworkManager().GetProxyAddress(), false))
+ NetworkError (this, "Failed to send relayed state update");
+
+ NetworkLog(this, "Sending state update relay message through proxy, destination is %s", updateStateAddresses[i].playerAddress.ToString());
+ }
+ else
+ {
+ if (!peer->Send (&stream, (PacketPriority)HIGH_PRIORITY, reliability, kDefaultChannel, updateStateAddresses[i].playerAddress, false))
+ NetworkError (this, "Failed to send state update");
+ }
+ }
+
+ NetworkLog(this, "Sending generic state update, broadcast %s, view ID '%s'\n", (broadcast)?"on":"off", m_ViewID.ToString().c_str());
+
+ m_LastPackState.swap(writeState);
+ }
+ }
+
+ // Send initial state data
+ if (!initialStateAddresses.empty())
+ {
+ if (m_StateSynchronization == kReliableDeltaCompressed)
+ {
+ writeState.clear();
+ writeState.reserve(m_LastPackState.size());
+ writeStatePtr = &writeState;
+ readData = NULL;
+ readSize = 0;
+ }
+ else
+ {
+ writeStatePtr = NULL;
+ readData = NULL;
+ readSize = 0;
+ }
+
+ RakNet::BitStream stream;
+ RakNet::BitStream relayStream;
+
+ Pack(stream, writeStatePtr, readData, readSize, ID_STATE_INITIAL);
+
+ for (int i=0;i<initialStateAddresses.size();i++)
+ {
+ if (initialStateAddresses[i].relayed == true)
+ {
+ relayStream.Reset();
+ relayStream.Write((MessageID) ID_PROXY_SERVER_MESSAGE);
+ relayStream.Write(initialStateAddresses[i].playerAddress);
+ relayStream.Write((char*)stream.GetData(), stream.GetNumberOfBytesUsed());
+ if (!peer->Send (&relayStream, (PacketPriority)HIGH_PRIORITY, (PacketReliability)RELIABLE_ORDERED, kDefaultChannel, GetNetworkManager().GetProxyAddress(), false))
+ NetworkError (this, "Failed to send relayed initial update");
+
+ NetworkLog(NULL, "Sending initial state relay message through proxy, destination is %s", initialStateAddresses[i].playerAddress.ToString());
+ }
+ else
+ {
+ if (!peer->Send (&stream, (PacketPriority)HIGH_PRIORITY, RELIABLE_ORDERED, kDefaultChannel, initialStateAddresses[i].playerAddress, false))
+ NetworkError (this, "Failed to send initial update");
+ }
+ }
+
+ NetworkLog(this, "Sending generic initial state update, broadcast %s, view ID '%s'\n", (broadcast)?"on":"off", m_ViewID.ToString().c_str());
+
+ m_LastPackState.swap(writeState);
+ }
+
+}
+
+void NetworkView::SendToAllButOwner()
+{
+ Send(m_OwnerAddress, true);
+}
+
+NetworkViewID NetworkView::GetViewID()
+{
+ return m_ViewID;
+}
+
+void NetworkView::SetViewID(NetworkViewID viewID)
+{
+ NetworkManager& nm = GetNetworkManager();
+ NetworkLog(NULL, "Assigning a view ID: old view ID '%s', new view ID '%s'\n", m_ViewID.ToString().c_str(), viewID.ToString().c_str());
+
+ // If this viewID does not belong to my pool of IDs (i.e. another player)
+ if ( nm.WasViewIdAllocatedByMe(viewID) )
+ {
+ m_OwnerAddress = nm.GetPlayerAddress();
+ }
+ // Since this is our own view object, send our playerId address to server
+ else
+ {
+ // If we are a server, look up from player address table
+ if (nm.IsServer())
+ {
+ NetworkPlayer player = nm.GetNetworkViewIDOwner(viewID);
+ m_OwnerAddress = nm.GetSystemAddressFromIndex(player);
+ }
+ // If we are a client, we default to server owning, since we don't know the players anyway.
+ else
+ {
+ m_OwnerAddress.binaryAddress = 0;
+ }
+ }
+
+ // Make sure all current players get added into the scope of this network view
+ m_Scope.resize(nm.GetInitIndexSize(), true);
+
+ m_ViewID = viewID;
+}
+
+void NetworkView::SetGroup(unsigned group)
+{
+ if (group < kMaxGroups)
+ {
+ m_Group = group;
+ }
+ else
+ {
+ ErrorString("Groups must be between 0 and 31.");
+ }
+}
+
+void NetworkView::Reset ()
+{
+ Super::Reset();
+
+ if (!m_Observed && GetGameObjectPtr())
+ m_Observed = QueryComponent(Transform);
+}
+
+void NetworkView::SetStateSynchronization (int sync)
+{
+ m_StateSynchronization = sync;
+ SetDirty();
+}
+
+void NetworkView::SetInitState(int index, bool isSent)
+{
+ if (index < m_InitState.size())
+ {
+ m_InitState[index] = isSent;
+ NetworkInfo(NULL, "Initial state being sent to index %d", index);
+ }
+ else
+ {
+ if (isSent)
+ {
+ m_InitState.resize(index + 1, false);
+ m_InitState[index] = isSent;
+ }
+ }
+}
+
+bool NetworkView::GetInitStateStatus(int index)
+{
+ if (index < m_InitState.size())
+ {
+ return m_InitState[index];
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void NetworkView::ClearInitStateAndOwner()
+{
+ m_InitState.clear();
+ m_OwnerAddress.binaryAddress = 0;
+}
+
+// Get initIndex for player and set/unset according to bool
+bool NetworkView::SetPlayerScope(NetworkPlayer playerIndex, bool relevancy)
+{
+ std::vector<PlayerTable> players = GetNetworkManager().GetPlayerAddresses();
+ unsigned int initIndex = 0xFFFFFFFF;
+
+ // Find the players which need update or initial state
+ for (int i = 0; i != players.size(); i++)
+ {
+ if (playerIndex == players[i].playerIndex)
+ {
+ initIndex = players[i].initIndex;
+ break;
+ }
+ }
+
+ if (initIndex != 0xFFFFFFFF)
+ {
+ SetScope(initIndex, relevancy);
+ return true;
+ }
+ else
+ {
+ NetworkError(NULL, "Player index %d not found when setting scope in network view %s", playerIndex, m_ViewID.ToString().c_str());
+ return false;
+ }
+}
+
+void NetworkView::SetScope(unsigned int initIndex, bool relevancy)
+{
+ if (initIndex < m_Scope.size())
+ {
+ // Unset means this is in scope (so if relevancy==true => scope==0)
+ m_Scope[initIndex] = relevancy;
+ NetworkInfo(NULL, "Scope index %d is now %s scope for %s", initIndex, relevancy?"in":"out of", m_ViewID.ToString().c_str());
+ }
+ else
+ {
+ m_Scope.resize(initIndex + 1, false);
+ m_Scope[initIndex] = relevancy;
+ NetworkInfo(NULL, "New scope index %d is now %s scope for %s", initIndex, relevancy?"in":"out of", m_ViewID.ToString().c_str());
+ }
+}
+
+// Check if given index is in this network views scope
+bool NetworkView::CheckScope(int initIndex)
+{
+ if (initIndex < m_Scope.size())
+ {
+ return m_Scope[initIndex];
+ }
+ else
+ {
+ // Scope does not exist, create it with default value
+ SetScope(initIndex, true);
+ return true;
+ }
+}
+
+SystemAddress NetworkView::GetOwnerAddress ()
+{
+ return m_OwnerAddress;
+}
+
+NetworkView::~NetworkView ()
+{
+}
+
+
+
+template<class TransferFunc>
+void NetworkView::Transfer (TransferFunc& transfer) {
+ Super::Transfer (transfer);
+
+ TRANSFER(m_StateSynchronization);
+ TRANSFER(m_Observed);
+ transfer.Transfer(m_ViewID, "m_ViewID", kNotEditableMask);
+}
+
+void RegisterRPC (const char* name)
+{
+ GetNetworkManager().RegisterRPC(name, NetworkViewRPCCallScript);
+}
+
+void NetworkView::InitializeClass ()
+{
+ RegisterMonoRPC(RegisterRPC);
+}
+
+void NetworkView::CleanupClass ()
+{
+}
+
+
+IMPLEMENT_CLASS_HAS_INIT (NetworkView)
+IMPLEMENT_OBJECT_SERIALIZE (NetworkView)
+
+#endif // ENABLE_NETWORK
diff --git a/Runtime/Network/NetworkView.h b/Runtime/Network/NetworkView.h
new file mode 100644
index 0000000..6b9aae9
--- /dev/null
+++ b/Runtime/Network/NetworkView.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+
+#include "Runtime/GameCode/Behaviour.h"
+#include "Configuration/UnityConfigure.h"
+#include <deque>
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Quaternion.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Runtime/Utilities/dynamic_bitset.h"
+#include "NetworkViewID.h"
+#include "NetworkEnums.h"
+#include "External/RakNet/builds/include/BitStream.h"
+
+
+
+struct MonoArray;
+enum {
+ kNoStateSynch = 0,
+ kReliableDeltaCompressed = 1,
+ kUnreliableBruteForce = 2
+};
+
+class NetworkView : public Behaviour
+{
+public:
+ typedef std::set<Component*> Components; // Declare the NetworkView container
+ typedef std::vector<UInt8> PackState;
+
+ REGISTER_DERIVED_CLASS (NetworkView, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (NetworkView)
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ NetworkView (MemLabelId label, ObjectCreationMode mode);
+ // virtual ~NetworkView (); declared-by-macro
+
+ void Update();
+ void AwakeFromLoad (AwakeFromLoadMode mode);
+ void Reset ();
+
+ bool Pack(RakNet::BitStream &stream, PackState* writeStatePtr, UInt8* readData, int &readSize, int msgID);
+ void SendWithInitialState(std::vector<SystemAddress> &initAddresses, std::vector<SystemAddress> &normalAddresses, unsigned char msgID);
+ void Send(SystemAddress systemAddress, bool broadcast);
+ void SendToAllButOwner();
+
+ NetworkViewID GetViewID();
+
+ // Assign the given view ID to this view. Add the player address information
+ // to the view as appropriate (depening on owner/peer type)
+ void SetViewID(NetworkViewID viewID);
+
+ // Call the given user scripted RPC function. The mode defines if it should be
+ // buffered on the server and how it should be relayed. The array stores all the user
+ // defined variables.
+ void RPCCall (const std::string& function, int mode, MonoArray* args);
+
+ // Call the given user scripted RPC function. The mode defines if it should be
+ // buffered on the server and how it should be relayed. The array stores all the user
+ // defined variables.
+ void RPCCallSpecificTarget (const std::string &function, NetworkPlayer target, MonoArray* args);
+
+ SystemAddress GetOwnerAddress();
+
+ unsigned GetGroup() { return m_Group; }
+ void SetGroup (unsigned group);
+
+ void SetStateReliability(int reliability);
+ int GetStateReliability();
+
+ Unity::Component* GetObserved ();
+ void SetObserved (Unity::Component* component);
+
+ void Unpack (RakNet::BitStream& stream, NetworkMessageInfo& msgData, int msgType);
+ bool Pack (RakNet::BitStream& stream);
+
+ int GetStateSynchronization () { return m_StateSynchronization; }
+ void SetStateSynchronization (int sync);
+
+ void SetInitState(int index, bool isSent);
+ void GrowInitState();
+ bool GetInitStateStatus(int index);
+ void ClearInitStateAndOwner();
+
+ bool SetPlayerScope(NetworkPlayer playerIndex, bool relevancy);
+ void SetScope(unsigned int initIndex, bool relevancy);
+ bool CheckScope(int initIndex);
+
+private:
+ void SetupSceneViewID ();
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+
+ NetworkViewID m_ViewID;
+ PPtr<Unity::Component> m_Observed;
+ SystemAddress m_OwnerAddress; // The address of the player which owns this object. Used by server when relaying to other players.
+ int m_Group;
+ int m_StateSynchronization; ///< enum { Off = 0, Reliable Delta Compressed = 1, Unreliable = 2 }
+
+ // Pack state for delta compression
+ // We need seperate pack / unpack states because the server can receive & send at the same time.
+ PackState m_LastPackState;
+ PackState m_LastUnpackState;
+ dynamic_bitset m_InitState;
+ dynamic_bitset m_Scope;
+
+ ListNode<NetworkView> m_Node;
+ ListNode<NetworkView> m_AllNode;
+ bool m_HasReceivedInitialState;
+};
+
+#endif
diff --git a/Runtime/Network/NetworkViewID.cpp b/Runtime/Network/NetworkViewID.cpp
new file mode 100644
index 0000000..14356f1
--- /dev/null
+++ b/Runtime/Network/NetworkViewID.cpp
@@ -0,0 +1,193 @@
+#include "UnityPrefix.h"
+#include "NetworkViewID.h"
+
+#if ENABLE_NETWORK
+#include "Runtime/Serialize/SwapEndianBytes.h"
+
+enum {
+ k4Bits = (1 << 4) - 1,
+ k10Bits = (1 << 10) - 1,
+ k14Bits = (1 << 14) - 1,
+ k15Bits = (1 << 15) - 1,
+ k29Bits = (1 << 29) - 1
+};
+
+void NetworkViewID::Write (RakNet::BitStream& stream)
+{
+ UInt32 endianSceneID = m_ID;
+ UInt32 endianLevelPrefix = m_LevelPrefix;
+
+ #if UNITY_BIG_ENDIAN
+ SwapEndianBytes(endianSceneID);
+ SwapEndianBytes(endianLevelPrefix);
+ #endif
+
+ if (m_Type == kAllocatedID)
+ {
+ // 16 bit mode
+ if (m_ID <= k14Bits)
+ {
+ stream.Write0();// 16 bit mode
+ stream.Write1();// allocated
+ stream.WriteBits(reinterpret_cast<UInt8*>(&endianSceneID), 14);
+ }
+ // 32 bit mode
+ else if (m_ID <= k29Bits)
+ {
+ stream.Write1();// 32 bit mode
+ stream.Write0();// 32 bit mode
+ stream.Write1();// allocated
+ stream.WriteBits(reinterpret_cast<UInt8*>(&endianSceneID), 29);
+ }
+ // 64 bit mode
+ else
+ {
+ AssertString("64 bit mode not supported yet");
+ }
+ }
+ else
+ {
+ // 16 bit mode
+ if (m_ID <= k10Bits && m_LevelPrefix <= k4Bits)
+ {
+ stream.Write0();// 16 bit mode
+ stream.Write0();// allocated
+ stream.WriteBits(reinterpret_cast<UInt8*>(&endianLevelPrefix), 4);
+ stream.WriteBits(reinterpret_cast<UInt8*>(&endianSceneID), 10);
+ }
+ // 32 bit mode
+ else if (m_ID <= k14Bits && m_LevelPrefix <= k15Bits)
+ {
+ stream.Write1();// 32 bit mode
+ stream.Write0();// 32 bit mode
+ stream.Write0();// allocated
+ stream.WriteBits(reinterpret_cast<UInt8*>(&endianLevelPrefix), 15);
+ stream.WriteBits(reinterpret_cast<UInt8*>(&endianSceneID), 14);
+ }
+ // 64 bit mode
+ else
+ {
+ AssertString("64 bit mode not supported yet");
+ }
+ }
+}
+
+bool NetworkViewID::Read (RakNet::BitStream& stream)
+{
+ m_ID = 0;
+ m_LevelPrefix = 0;
+ m_Type = 0;
+
+ if (stream.GetNumberOfUnreadBits() < 16)
+ return false;
+
+ int size = 16;
+ int bitsRead = 1;
+ if (stream.ReadBit())
+ {
+ size = 32;
+ bitsRead++;
+ if (stream.ReadBit())
+ size = 64;
+
+ if (stream.GetNumberOfUnreadBits() < size-bitsRead)
+ {
+ AssertString(Format("Only %d bits left, but expected %d\n", stream.GetNumberOfUnreadBits(), size-bitsRead));
+ return false;
+ }
+ }
+
+ bool isAllocated = stream.ReadBit();
+
+ if (isAllocated)
+ {
+ m_Type = kAllocatedID;
+
+ if (size == 16)
+ stream.ReadBits(reinterpret_cast<UInt8*>(&m_ID), 14);
+ else if (size == 32)
+ stream.ReadBits(reinterpret_cast<UInt8*>(&m_ID), 29);
+ else
+ AssertString("Unsupported allocated view ID size");
+ }
+ else
+ {
+ m_Type = kSceneID;
+ if (size == 16)
+ {
+ stream.ReadBits(reinterpret_cast<UInt8*>(&m_LevelPrefix), 4);
+ stream.ReadBits(reinterpret_cast<UInt8*>(&m_ID), 10);
+ }
+ else if (size == 32)
+ {
+ stream.ReadBits(reinterpret_cast<UInt8*>(&m_LevelPrefix), 15);
+ stream.ReadBits(reinterpret_cast<UInt8*>(&m_ID), 14);
+ }
+ else
+ {
+ AssertString("Unsupported scene view ID size");
+ }
+ }
+
+ #if UNITY_BIG_ENDIAN
+ SwapEndianBytes(m_LevelPrefix);
+ SwapEndianBytes(m_ID);
+ #endif
+
+ return true;
+}
+
+void NetworkViewID::SetSceneID (UInt32 sceneID)
+{
+ m_Type = kSceneID;
+ m_LevelPrefix = 0;
+ m_ID = sceneID;
+}
+
+void NetworkViewID::SetAllocatedID (UInt32 sceneID)
+{
+ m_Type = kAllocatedID;
+ m_LevelPrefix = 0;
+ m_ID = sceneID;
+}
+
+void NetworkViewID::ReplaceLevelPrefix (UInt32 levelPrefix)
+{
+ AssertIf(m_Type != kSceneID);
+ m_LevelPrefix = levelPrefix;
+}
+
+bool operator < (const NetworkViewID& lhs, const NetworkViewID& rhs)
+{
+ if (lhs.m_ID != rhs.m_ID)
+ return lhs.m_ID < rhs.m_ID;
+ else
+ {
+ if (lhs.m_LevelPrefix != rhs.m_LevelPrefix)
+ return lhs.m_LevelPrefix < rhs.m_LevelPrefix;
+ else
+ return lhs.m_Type < rhs.m_Type;
+ }
+}
+
+std::string NetworkViewID::ToString () const
+{
+ char buffer[128];
+ if (m_Type == kSceneID)
+ sprintf(buffer, "SceneID: %lu Level Prefix: %lu", m_ID, m_LevelPrefix);
+ else
+ sprintf(buffer, "AllocatedID: %ld", m_ID);
+ return buffer;
+}
+
+bool operator == (const NetworkViewID& lhs, const NetworkViewID& rhs)
+{
+ return lhs.m_ID == rhs.m_ID && lhs.m_LevelPrefix == rhs.m_LevelPrefix && lhs.m_Type == rhs.m_Type;
+}
+
+bool operator != (const NetworkViewID& lhs, const NetworkViewID& rhs)
+{
+ return lhs.m_ID != rhs.m_ID || lhs.m_LevelPrefix != rhs.m_LevelPrefix || lhs.m_Type != rhs.m_Type;
+}
+
+#endif
diff --git a/Runtime/Network/NetworkViewID.h b/Runtime/Network/NetworkViewID.h
new file mode 100644
index 0000000..903a953
--- /dev/null
+++ b/Runtime/Network/NetworkViewID.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+#include "External/RakNet/builds/include/BitStream.h"
+#include "Runtime/Serialize/SerializeUtility.h"
+
+struct NetworkViewID
+{
+ private:
+
+ UInt32 m_LevelPrefix;
+ UInt32 m_ID;
+ UInt32 m_Type;///< enum { Allocated = 0, Scene = 1 }
+
+ public:
+
+ DECLARE_SERIALIZE(NetworkViewID)
+
+ NetworkViewID () :
+ m_LevelPrefix (0),
+ m_ID (0),
+ m_Type (0)
+ { }
+
+ enum { kAllocatedID = 0, kSceneID = 1 };
+
+ // Pack the view id into a 16 bit or 32 bit number based on how big the number is
+ void Write (RakNet::BitStream& stream);
+ bool Read (RakNet::BitStream& stream);
+
+ friend bool operator < (const NetworkViewID& lhs, const NetworkViewID& rhs);
+ friend bool operator != (const NetworkViewID& lhs, const NetworkViewID& rhs);
+ friend bool operator == (const NetworkViewID& lhs, const NetworkViewID& rhs);
+
+ void SetSceneID (UInt32 sceneID);
+ void SetAllocatedID (UInt32 sceneID);
+ bool IsSceneID() { return m_Type == kSceneID; }
+ void ReplaceLevelPrefix (UInt32 levelPrefix);
+
+ UInt32 GetIndex () const { return m_ID; }
+
+ std::string ToString() const;
+
+ static NetworkViewID GetUnassignedViewID() { NetworkViewID viewID; return viewID; }
+};
+
+template<class TransferFunc>
+void NetworkViewID::Transfer (TransferFunc& transfer)
+{
+ TRANSFER(m_ID);
+ TRANSFER(m_Type);
+}
+
+#endif
diff --git a/Runtime/Network/NetworkViewIDAllocator.cpp b/Runtime/Network/NetworkViewIDAllocator.cpp
new file mode 100644
index 0000000..95e0aaa
--- /dev/null
+++ b/Runtime/Network/NetworkViewIDAllocator.cpp
@@ -0,0 +1,130 @@
+#include "UnityPrefix.h"
+#include "NetworkViewIDAllocator.h"
+
+#if ENABLE_NETWORK
+
+NetworkViewIDAllocator::NetworkViewIDAllocator()
+{
+ Clear(kDefaultViewIDBatchSize, kMinimumViewIDs, 0, kUndefindedPlayerIndex);
+}
+
+UInt32 NetworkViewIDAllocator::AllocateBatch (NetworkPlayer player)
+{
+ UInt32 batchIndex = m_AllocatedViewIDBatches.size();
+ m_AllocatedViewIDBatches.push_back(player);
+ return batchIndex;
+}
+
+NetworkViewID NetworkViewIDAllocator::AllocateViewID ()
+{
+ if (!m_AvailableBatches.empty())
+ {
+ AvailableBatch& batch = m_AvailableBatches.front();
+
+ NetworkViewID viewID;
+ viewID.SetAllocatedID(batch.first);
+ batch.first++;
+ batch.count--;
+ if (batch.count == 0)
+ m_AvailableBatches.erase(m_AvailableBatches.begin());
+
+ return viewID;
+ }
+ else
+ {
+ return NetworkViewID();
+ }
+}
+
+
+NetworkPlayer NetworkViewIDAllocator::FindOwner (NetworkViewID viewID)
+{
+ if (viewID.IsSceneID())
+ {
+ return m_ServerPlayer;
+ }
+ else
+ {
+ UInt32 index = viewID.GetIndex();
+ index /= m_BatchSize;
+
+ // On Clients we use received batches. On the client we can only find out if the batch is ours.
+ // Otherwise it defaults to the server
+ if (!m_ReceivedBatches.empty())
+ {
+ for (ReceivedBatches::iterator i=m_ReceivedBatches.begin();i != m_ReceivedBatches.end();i++)
+ {
+ if (*i == index)
+ return m_ClientPlayer;
+ }
+
+ return m_ServerPlayer;
+ }
+ // The Server allocates all network view id's so he knows all players for all view ids
+ else
+ {
+ if (index < m_AllocatedViewIDBatches.size())
+ return m_AllocatedViewIDBatches[index];
+ else
+ return kUndefindedPlayerIndex;
+ }
+
+ return 0;
+ }
+}
+
+void NetworkViewIDAllocator::FeedAvailableBatchOnClient (UInt32 batchIndex)
+{
+ m_ReceivedBatches.push_back(batchIndex);
+
+ AvailableBatch batch;
+ batch.first = batchIndex * m_BatchSize;
+ batch.count = m_BatchSize;
+ m_AvailableBatches.push_back(batch);
+}
+
+void NetworkViewIDAllocator::FeedAvailableBatchOnServer (UInt32 batchIndex)
+{
+ AvailableBatch batch;
+ batch.first = batchIndex * m_BatchSize;
+ batch.count = m_BatchSize;
+ if (batchIndex == 0)
+ {
+ batch.first++;
+ batch.count--;
+ }
+ m_AvailableBatches.push_back(batch);
+}
+
+int NetworkViewIDAllocator::ShouldRequestMoreBatches ()
+{
+ // Count how many id's we have left
+ int viewIDsLeft = 0;
+ for (int i=0;i<m_AvailableBatches.size();i++)
+ viewIDsLeft += m_AvailableBatches[i].count;
+
+ // Do we need to request new id batches
+ viewIDsLeft += m_BatchSize * m_RequestedBatches;
+ if (viewIDsLeft < m_MinAvailableViewIDs)
+ {
+ int extraRequiredViewIDs = m_MinAvailableViewIDs - viewIDsLeft;
+ int requestedBatches = ((extraRequiredViewIDs -1) / m_BatchSize) + 1;
+ return requestedBatches;
+ }
+
+ return 0;
+}
+
+void NetworkViewIDAllocator::Clear (int batchSize, int minimumViewIDs, NetworkPlayer server, NetworkPlayer client)
+{
+ m_MinAvailableViewIDs = minimumViewIDs;
+ m_BatchSize = batchSize;
+ m_AllocatedViewIDBatches.clear();
+ m_AvailableBatches.clear();
+ m_ReceivedBatches.clear();
+ m_RequestedBatches = 0;
+ m_ClientPlayer = client;
+ m_ServerPlayer = server;
+}
+
+#endif \ No newline at end of file
diff --git a/Runtime/Network/NetworkViewIDAllocator.h b/Runtime/Network/NetworkViewIDAllocator.h
new file mode 100644
index 0000000..124feb5
--- /dev/null
+++ b/Runtime/Network/NetworkViewIDAllocator.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+#include "NetworkEnums.h"
+
+class NetworkViewIDAllocator
+{
+
+ struct AvailableBatch
+ {
+ UInt32 first;
+ UInt32 count;
+ };
+
+ typedef std::vector<NetworkPlayer> AllocatedViewIDBatches;
+ AllocatedViewIDBatches m_AllocatedViewIDBatches; // Used by the server to track who own which NetworkViewID's
+
+ typedef std::vector<UInt32> ReceivedBatches;
+ ReceivedBatches m_ReceivedBatches; // Used by the client to track which batches were received by him.
+
+ typedef std::vector<AvailableBatch> AvailableBatches;
+ AvailableBatches m_AvailableBatches; // Used by client/server to allocate ViewID's from
+
+ int m_BatchSize;
+ int m_MinAvailableViewIDs; // We always make sure that m_MinAvailableViewIDs are around. If not we request more view id's
+ int m_RequestedBatches;
+ NetworkPlayer m_ClientPlayer;
+ NetworkPlayer m_ServerPlayer;
+
+ public:
+
+ NetworkViewIDAllocator();
+
+ void Clear (int batchSize, int minimumViewIDs, NetworkPlayer server, NetworkPlayer client);
+
+ NetworkViewID AllocateViewID ();
+
+ UInt32 AllocateBatch (NetworkPlayer player);
+
+ void FeedAvailableBatchOnClient (UInt32 batchIndex);
+ void FeedAvailableBatchOnServer (UInt32 batchIndex);
+ UInt32 GetBatchSize () { return m_BatchSize; }
+
+ // How many more view id batches should be requested!
+ // You are expected to actually request or allocate those view id's
+ int ShouldRequestMoreBatches ();
+ void AddRequestedBatches (int requestedBatches) { m_RequestedBatches += requestedBatches; }
+
+ void SetMinAvailableViewIDs(int size) { m_MinAvailableViewIDs = size; }
+
+ /// On Server: Find Owner returns the player who allocated the view id. If it hasn't been allocated, it returns kUndefindedPlayerIndex.
+ /// On Clients: FindOwner returns the clients player ID for its own objects and otherwise the server, since he can't possibly know the owner.
+ NetworkPlayer FindOwner (NetworkViewID viewID);
+};
+
+#endif \ No newline at end of file
diff --git a/Runtime/Network/OnlineServices.h b/Runtime/Network/OnlineServices.h
new file mode 100644
index 0000000..6b18509
--- /dev/null
+++ b/Runtime/Network/OnlineServices.h
@@ -0,0 +1,10 @@
+#pragma once
+
+// Disable these features on xbox for now
+#if 0
+# define VOID_IMPL ;
+# define RET_IMPL(value) ;
+#else
+# define VOID_IMPL {}
+# define RET_IMPL(value) { return value; }
+#endif
diff --git a/Runtime/Network/PackMonoRPC.cpp b/Runtime/Network/PackMonoRPC.cpp
new file mode 100644
index 0000000..08b16e0
--- /dev/null
+++ b/Runtime/Network/PackMonoRPC.cpp
@@ -0,0 +1,488 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+#include "NetworkManager.h"
+#include "PackMonoRPC.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoManager.h"
+#include "Runtime/Allocator/LinearAllocator.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "External/RakNet/builds/include/StringCompressor.h"
+#include "BitStreamPacker.h"
+#include "Runtime/Scripting/Backend/ScriptingMethodRegistry.h"
+#include "Runtime/Scripting/Backend/ScriptingBackendApi.h"
+#if UNITY_WIN
+#include <malloc.h> // alloca
+#endif
+
+#include "Runtime/Scripting/Scripting.h"
+
+enum RPCFindResult { kRPCFailure = -1, kRPCNotFound = 0, kRPCFound = 1 };
+
+static RPCFindResult FindRPCMethod (Object* target, const char* function, MonoMethod** outMethod, Object* netview);
+
+static RPCFindResult FindRPCMethod (Object* target, const char* function, MonoMethod** outMethod, Object* netview)
+{
+ // We need the target to be a script
+ MonoBehaviour* behaviour = dynamic_pptr_cast<MonoBehaviour*> (target);
+ if (behaviour == NULL)
+ {
+ ErrorStringObject ("RPC call failed because the observed object is not a script.", netview);
+ return kRPCFailure;
+ }
+
+ // We need the rpc method to exist
+ ScriptingMethodPtr method = behaviour->FindMethod (function);
+ if (method == NULL)
+ {
+ if (behaviour->GetInstance() != NULL)
+ {
+ return kRPCNotFound;
+ }
+ else
+ {
+ ErrorStringObject (Format("RPC call failed because the script couldn't be loaded. The function was '%s'.", function), netview);
+ return kRPCFailure;
+ }
+ }
+
+ bool hasRPCAttribute = scripting_method_has_attribute (method, GetMonoManager().GetCommonClasses().RPC);
+ if (!hasRPCAttribute)
+ {
+ const char* className = mono_class_get_name(mono_method_get_class(method->monoMethod));
+ ErrorStringObject (Format("RPC call failed because the function '%s' in '%s' does not have the RPC attribute. You need to add the RPC attribute in front of the function declaration", function, className), netview);
+ return kRPCFailure;
+ }
+
+ *outMethod = method->monoMethod;
+
+ return kRPCFound;
+}
+
+template<class T>
+void UnpackBuiltinValue (BitstreamPacker& stream, ForwardLinearAllocator& fwdallocator, void*& outData)
+{
+ outData = fwdallocator.allocate(sizeof(T));
+ stream.Serialize (*reinterpret_cast<T*>(outData));
+}
+
+template<class T>
+void UnpackBuiltinMaxError (BitstreamPacker& stream, ForwardLinearAllocator& fwdallocator, void*& outData)
+{
+ outData = fwdallocator.allocate(sizeof(T));
+ stream.Serialize (*reinterpret_cast<T*>(outData), 0.0F);
+}
+
+
+bool UnpackString (RakNet::BitStream& stream, void*& outData)
+{
+ char rawOut[4096];
+
+ if (StringCompressor::Instance()->DecodeString(rawOut, 4096, &stream))
+ {
+ outData = MonoStringNew(rawOut);
+ return true;
+ }
+ else
+ {
+ outData = NULL;
+ return false;
+ }
+}
+
+void PackString (RakNet::BitStream& stream, MonoObject* str)
+{
+ std::string cppStr = MonoStringToCpp((MonoString*)str);
+ if (cppStr.size() >= 4096)
+ {
+ ErrorString("Strings sent via RPC calls may not be larger than 4096 UTF8 characters");
+ }
+
+ StringCompressor::Instance()->EncodeString(cppStr.c_str(), 4096, &stream);
+}
+
+bool UnpackAndInvokeRPCMethod (GameObject& target, const char* name, RakNet::BitStream& parameters, SystemAddress sender, NetworkViewID viewID, RakNetTime timestamp, Object* netview)
+{
+ int readoffset = parameters.GetReadOffset();
+ bool invokedAny = false;
+ for (int i=0;i<target.GetComponentCount();i++)
+ {
+ if (target.GetComponentClassIDAtIndex(i) != ClassID(MonoBehaviour))
+ continue;
+
+ MonoBehaviour* targetBehaviour = static_cast<MonoBehaviour*> (&target.GetComponentAtIndex(i));
+ MonoMethod* method;
+ RPCFindResult findResult = FindRPCMethod (targetBehaviour, name, &method, netview);
+ if (findResult == kRPCNotFound)
+ ;
+ else if (findResult == kRPCFound)
+ {
+ parameters.SetReadOffset(readoffset);
+ if (UnpackAndInvokeRPCMethod (*targetBehaviour, method, parameters, sender, viewID, timestamp, netview))
+ return false;
+
+ invokedAny = true;
+ }
+ else
+ return false;
+ }
+
+ if (!invokedAny)
+ {
+ ErrorStringObject (Format("RPC call failed because the function '%s' does not exist in any script attached to'%s'", name, target.GetName()), netview);
+ }
+
+ return invokedAny;
+}
+
+bool PackRPCParameters (GameObject& target, const char* name, RakNet::BitStream& inStream, MonoArray* data, Object* netview)
+{
+ bool invokedAny = false;
+ for (int i=0;i<target.GetComponentCount();i++)
+ {
+ if (target.GetComponentClassIDAtIndex(i) != ClassID(MonoBehaviour))
+ continue;
+
+ MonoBehaviour* targetBehaviour = static_cast<MonoBehaviour*> (&target.GetComponentAtIndex(i));
+ MonoMethod* method;
+ RPCFindResult findResult = FindRPCMethod (targetBehaviour, name, &method, netview);
+
+ if (findResult == kRPCNotFound)
+ ;
+ else if (findResult == kRPCFound)
+ {
+ if (!PackRPCParameters (*targetBehaviour, method, inStream, data, netview, !invokedAny))
+ return false;
+ invokedAny = true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ if (!invokedAny)
+ {
+ ErrorStringObject (Format("RPC call failed because the function '%s' does not exist in the any script attached to'%s'", name, target.GetName()), netview);
+ }
+
+ return invokedAny;
+}
+
+static bool IsValidRPCParameterArrayType(MonoClass* arrayClass)
+{
+ MonoClass* elementClass = mono_class_get_element_class(arrayClass);
+ MonoType* elementType = mono_class_get_type(elementClass);
+ int elementTypeCode = mono_type_get_type(elementType);
+
+ switch (elementTypeCode)
+ {
+ case MONO_TYPE_U1:
+ case MONO_TYPE_BOOLEAN:
+ case MONO_TYPE_I4:
+ case MONO_TYPE_R4:
+ case MONO_TYPE_STRING:
+ return true;
+ case MONO_TYPE_VALUETYPE:
+ {
+ const CommonScriptingClasses& commonClasses = MONO_COMMON;
+ return (elementClass == commonClasses.networkPlayer ||
+ elementClass == commonClasses.networkViewID ||
+ elementClass == commonClasses.vector3 ||
+ elementClass == commonClasses.quaternion);
+ }
+ break;
+ }
+ return false;
+}
+
+bool UnpackAndInvokeRPCMethod (MonoBehaviour& targetBehaviour, MonoMethod* method, RakNet::BitStream& parameters, SystemAddress sender, NetworkViewID viewID, RakNetTime timestamp, Object* netview)
+{
+ DebugAssertIf(method == NULL);
+ MonoMethodSignature* sig = mono_method_signature(method);
+ int paramCount = mono_signature_get_param_count(sig);
+ ForwardLinearAllocator fwdallocator (1024, kMemTempAlloc);
+ #if ENABLE_THREAD_CHECK_IN_ALLOCS
+ fwdallocator.SetThreadIDs(Thread::GetCurrentThreadID(), Thread::GetCurrentThreadID());
+ #endif
+
+ void** variables = (void** )alloca(paramCount * sizeof(void*));
+
+ const CommonScriptingClasses& commonClasses = MONO_COMMON;
+
+ BitstreamPacker stream(parameters, NULL, NULL, 0, true);
+
+ NetworkMessageInfo msgInfo;
+
+ // Loop through the parameters specified by the rpc function and extract it out of the param array
+ MonoType* methodType;
+ void* iter = NULL;
+ int i = 0;
+ bool success = true;
+ while ((methodType = mono_signature_get_params (sig, &iter)))
+ {
+ int expectedTypecode = mono_type_get_type (methodType);
+
+ switch (expectedTypecode)
+ {
+ // byte array
+ case MONO_TYPE_SZARRAY:
+ {
+ MonoClass* arrayClass = mono_class_from_mono_type (methodType);
+ if (IsValidRPCParameterArrayType(arrayClass))
+ {
+ MonoClass* elementClass = mono_class_get_element_class(arrayClass);
+ int elementSize = mono_class_array_element_size(elementClass);
+ int arrayLength;
+ stream.Serialize(reinterpret_cast<SInt32&>(arrayLength));
+ MonoArray* array = mono_array_new (mono_domain_get (), elementClass, arrayLength);
+ char* ptr = Scripting::GetScriptingArrayStart<char>(array);
+ int charLength = arrayLength * elementSize;
+ stream.Serialize (ptr, charLength);
+
+ variables[i] = array;
+ }
+ else
+ {
+ success = false;
+ }
+ }
+ break;
+ case MONO_TYPE_BOOLEAN:
+ {
+ bool extractedValue;
+ stream.Serialize (extractedValue);
+ char* writeHere = reinterpret_cast<char*>(fwdallocator.allocate(sizeof(char)));
+ *writeHere = extractedValue;
+ variables[i] = writeHere;
+ }
+ break;
+ case MONO_TYPE_I4:
+ UnpackBuiltinValue<SInt32>(stream, fwdallocator, variables[i]);
+ break;
+ case MONO_TYPE_R4:
+ UnpackBuiltinMaxError<float>(stream, fwdallocator, variables[i]);
+ break;
+ case MONO_TYPE_STRING:
+ UnpackString (parameters, variables[i]);
+ break;
+ case MONO_TYPE_VALUETYPE:
+ {
+ MonoClass* structClass = mono_type_get_class_or_element_class (methodType);
+ if (structClass == commonClasses.networkPlayer)
+ UnpackBuiltinValue<NetworkPlayer>(stream, fwdallocator, variables[i]);
+ else if (structClass == commonClasses.networkViewID)
+ UnpackBuiltinValue<NetworkViewID>(stream, fwdallocator, variables[i]);
+ else if (structClass == commonClasses.vector3)
+ UnpackBuiltinMaxError<Vector3f>(stream, fwdallocator, variables[i]);
+ else if (structClass == commonClasses.quaternion)
+ UnpackBuiltinMaxError<Quaternionf>(stream, fwdallocator, variables[i]);
+ else if (structClass == commonClasses.networkMessageInfo)
+ {
+ msgInfo.timestamp = TimestampToSeconds(timestamp);
+ msgInfo.sender = GetNetworkManager().GetIndexFromSystemAddress ( sender );
+ msgInfo.viewID = viewID;
+ AssertIf(mono_class_array_element_size(structClass) != sizeof(NetworkMessageInfo));
+ variables[i] = &msgInfo;
+ }
+ else
+ success = false;
+ }
+ break;
+ default:
+ success = false;
+ break;
+ }
+
+ i++;
+ }
+
+ if (stream.HasReadOutOfBounds())
+ success = false;
+
+ if (!success)
+ {
+ const char* classname = mono_class_get_name(mono_method_get_class(method));
+ ErrorStringObject(Format ("Failed to invoke arriving RPC method because the parameters didn't match the function declaration. '%s' of '%s'.", mono_method_get_name(method), classname), netview);
+
+ return false;
+ }
+
+ MonoObject* instance = targetBehaviour.GetInstance();
+ // Invoke the method
+ MonoException* exception;
+ MonoObject* returnValue = mono_runtime_invoke_profiled (method, instance, variables, &exception);
+ if (returnValue && exception == NULL)
+ {
+ ScriptingMethodPtr scriptingMethod = GetScriptingMethodRegistry().GetMethod(method);
+ targetBehaviour.HandleCoroutineReturnValue (scriptingMethod, returnValue);
+ }
+
+
+ if (exception == NULL)
+ return true;
+ else
+ {
+ Scripting::LogException(exception, Scripting::GetInstanceIDFromScriptingWrapper(instance));
+ return false;
+ }
+}
+
+bool PackRPCParameters (MonoBehaviour& targetBehaviour, MonoMethod* method, RakNet::BitStream& inStream, MonoArray* data, Object* netview, bool doPack)
+{
+ DebugAssertIf(method == NULL);
+ bool requireTimeStamp = false;
+
+ int arraySize = mono_array_length_safe(data);
+
+ BitstreamPacker stream(inStream, NULL, NULL, 0, false);
+
+ // Check the number of parameters
+ // Handle message info as last element case
+ MonoMethodSignature* sig = mono_method_signature(method);
+ if (arraySize != mono_signature_get_param_count(sig) && arraySize + 1 != mono_signature_get_param_count(sig))
+ {
+ ErrorStringObject(Format( "Sending RPC '%s' failed because the number of supplied parameters doesn't match the rpc declaration. Expected %d but got %d parameters.", mono_method_get_name(method), mono_signature_get_param_count(sig), mono_array_length_safe(data)), netview);
+ return false;
+ }
+
+ const CommonScriptingClasses& commonClasses = MONO_COMMON;
+
+ // Loop through the parameters specified by the rpc function and extract it out of the param array
+ MonoType* methodType;
+ void* iter = NULL;
+ int i = 0;
+ while ((methodType = mono_signature_get_params (sig, &iter)))
+ {
+ int expectedTypecode = mono_type_get_type (methodType);
+
+ // Handle message info as last element case
+ if (i == arraySize)
+ {
+ if (expectedTypecode == MONO_TYPE_VALUETYPE && mono_type_get_class_or_element_class(methodType) == commonClasses.networkMessageInfo)
+ {
+ requireTimeStamp = true;
+ return true;
+ }
+ else
+ {
+ ErrorStringObject(Format( "Sending RPC '%s' failed because the number of supplied parameters doesn't match the RPC declaration. Expected %d but got %d parameters.", mono_method_get_name(method), mono_signature_get_param_count(sig), mono_array_length_safe(data)), netview);
+ return false;
+ }
+ }
+
+ // @TODO check element against null
+ ///@TODO What if you intentionally want to pass in null. Eg. a null reference to a game object
+ MonoObject* element = GetMonoArrayElement<MonoObject*>(data, i);
+ if (element == NULL)
+ {
+ ErrorStringObject(Format("Sending RPC failed because '%s' parameter %d was null", mono_method_get_name(method), i), netview);
+ return false;
+ }
+ MonoClass* elementClass = mono_object_get_class(element);
+ MonoType* elementType = mono_class_get_type(elementClass);
+ int elementTypeCode = mono_type_get_type(elementType);
+ if (elementTypeCode == expectedTypecode)
+ {
+ switch (elementTypeCode)
+ {
+ // byte array
+ case MONO_TYPE_SZARRAY:
+ if (!IsValidRPCParameterArrayType(elementClass))
+ {
+ ErrorStringObject(Format("Sending RPC failed because '%s' parameter %d (%s) is not supported.", mono_method_get_name(method), i, mono_type_get_name(elementType)), netview);
+ return false;
+ }
+ if (doPack)
+ {
+ MonoArray* array = GetMonoArrayElement<MonoArray*>(data, i);
+ MonoClass* arrayElementClass = mono_class_get_element_class(elementClass);
+ int elementSize = mono_class_array_element_size(arrayElementClass);
+ int length = mono_array_length(array);
+ char* ptr = Scripting::GetScriptingArrayStart<char>(array);
+ stream.Serialize (reinterpret_cast<SInt32&>(length));
+ int charLength = length * elementSize;
+ stream.Serialize (ptr, charLength);
+ }
+ break;
+ case MONO_TYPE_BOOLEAN:
+ if (doPack)
+ {
+ bool boolValue = ExtractMonoObjectData<UInt8> (element);
+ stream.Serialize (boolValue);
+ }
+ break;
+ case MONO_TYPE_I4:
+ if (doPack)
+ stream.Serialize (ExtractMonoObjectData<SInt32> (element));
+ break;
+ case MONO_TYPE_R4:
+ if (doPack)
+ stream.Serialize (ExtractMonoObjectData<float> (element), 0);
+ break;
+ case MONO_TYPE_STRING:
+ if (doPack)
+ PackString (inStream, element);
+ break;
+ case MONO_TYPE_VALUETYPE:
+ {
+ MonoClass* methodClass = mono_type_get_class_or_element_class(methodType);
+ if (elementClass == methodClass)
+ {
+ if (elementClass == commonClasses.networkPlayer)
+ {
+ if (doPack)
+ stream.Serialize (ExtractMonoObjectData<NetworkPlayer> (element));
+ }
+ else if (elementClass == commonClasses.networkViewID)
+ {
+ if (doPack)
+ stream.Serialize (ExtractMonoObjectData<NetworkViewID> (element));
+ }
+ else if (elementClass == commonClasses.vector3)
+ {
+ if (doPack)
+ stream.Serialize (ExtractMonoObjectData<Vector3f> (element), 0.0F);
+ }
+ else if (elementClass == commonClasses.quaternion)
+ {
+ if (doPack)
+ stream.Serialize (ExtractMonoObjectData<Quaternionf> (element), 0.0F);
+ }
+ else
+ {
+ ErrorStringObject(Format("Sending RPC failed because '%s' parameter %d (%s) is not supported.", mono_method_get_name(method), i, mono_type_get_name(elementType)), netview);
+ return false;
+ }
+ }
+ else
+ {
+ const char* expectedTypeName = mono_type_get_name(methodType);
+ const char* gotTypeName = mono_type_get_name(elementType);
+ ErrorStringObject(Format("Sending RPC failed because '%s' parameter %d didn't match the RPC declaration. Expected '%s' but got '%s'", mono_method_get_name(method), i, expectedTypeName, gotTypeName), netview);
+ return false;
+ }
+ }
+ break;
+
+ default:
+ ErrorStringObject(Format("Sending RPC failed because '%s' parameter %d (%s) is not supported.", mono_method_get_name(method), i, mono_type_get_name(elementType)), netview);
+ return false;
+ break;
+ }
+ }
+ else
+ {
+ const char* expectedTypeName = mono_type_get_name(methodType);
+ const char* gotTypeName = mono_type_get_name(elementType);
+ ErrorStringObject(Format("Sending RPC failed because '%s' parameter %d didn't match the RPC declaration. Expected '%s' but got '%s'", mono_method_get_name(method), i, expectedTypeName, gotTypeName), netview);
+ return false;
+ }
+ i++;
+ }
+
+ return true;
+}
+
+#endif
diff --git a/Runtime/Network/PackMonoRPC.h b/Runtime/Network/PackMonoRPC.h
new file mode 100644
index 0000000..bd16900
--- /dev/null
+++ b/Runtime/Network/PackMonoRPC.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+
+#include "Runtime/Mono/MonoIncludes.h"
+#include "External/RakNet/builds/include/BitStream.h"
+#include "NetworkEnums.h"
+
+typedef void NetworkViewRpcFunc (RPCParameters *rpcParameters);
+
+bool UnpackAndInvokeRPCMethod (GameObject& target, const char* name, RakNet::BitStream& parameters, SystemAddress sender, NetworkViewID viewID, RakNetTime timestamp, Object* netview);
+bool PackRPCParameters (GameObject& target, const char* name, RakNet::BitStream& inStream, MonoArray* data, Object* netview);
+
+bool UnpackAndInvokeRPCMethod (MonoBehaviour& targetBehaviour, MonoMethod* method, RakNet::BitStream& parameters, SystemAddress sender, NetworkViewID viewID, RakNetTime timestamp, Object* netview);
+bool PackRPCParameters (MonoBehaviour& targetBehaviour, MonoMethod* method, RakNet::BitStream& inStream, MonoArray* data, Object* netview, bool doPack);
+
+#endif \ No newline at end of file
diff --git a/Runtime/Network/PackStateSpecialized.cpp b/Runtime/Network/PackStateSpecialized.cpp
new file mode 100644
index 0000000..9c6ca61
--- /dev/null
+++ b/Runtime/Network/PackStateSpecialized.cpp
@@ -0,0 +1,124 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+
+#include "PackStateSpecialized.h"
+#include "Runtime/Dynamics/RigidBody.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Animation/Animation.h"
+#include "Runtime/Animation/AnimationState.h"
+#include "BitStreamPacker.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoScriptCache.h"
+#include "Runtime/Mono/MonoManager.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Scripting/Scripting.h"
+#include "Runtime/Interfaces/IPhysics.h"
+#include "Runtime/Interfaces/IAnimationStateNetworkProvider.h"
+
+const float kPositionEpsilon = 0.00001F;
+const float kRotationEpsilon = 0.0001F;
+
+bool PackTransform (Transform& transform, BitstreamPacker& packer)
+{
+ Vector3f pos = transform.GetLocalPosition();
+ Quaternionf rot = transform.GetLocalRotation();
+ Vector3f scale = transform.GetLocalScale();
+
+ packer.Serialize(pos, kPositionEpsilon);
+ packer.Serialize(rot, kRotationEpsilon);
+ packer.Serialize(scale, kPositionEpsilon);
+ return packer.HasChanged();
+}
+
+void UnpackTransform (Transform& transform, BitstreamPacker& packer)
+{
+ Vector3f pos;
+ Quaternionf rot;
+ Vector3f scale;
+
+ packer.Serialize(pos, kPositionEpsilon);
+ packer.Serialize(rot, kRotationEpsilon);
+ packer.Serialize(scale, kPositionEpsilon);
+
+ transform.SetLocalTRS(pos, rot, scale);
+}
+
+bool SerializeAnimation (Animation& animation, BitstreamPacker& packer)
+{
+ IAnimationStateNetworkProvider* animationSystem = GetIAnimationStateNetworkProvider();
+ if (animationSystem == NULL)
+ return false;
+
+ int count = animationSystem->GetNetworkAnimationStateCount (animation);
+
+ AnimationStateForNetwork* serialized;
+ ALLOC_TEMP(serialized, AnimationStateForNetwork, count);
+
+ if (packer.IsWriting())
+ animationSystem->GetNetworkAnimationState (animation, serialized, count);
+
+ for (int i=0;i<count;i++)
+ {
+ packer.Serialize(serialized[i].enabled);
+ packer.Serialize(serialized[i].weight, 0.01F);
+ packer.Serialize(serialized[i].time, 0.01F);
+ }
+
+ if (packer.IsReading())
+ animationSystem->SetNetworkAnimationState (animation, serialized, count);
+
+ return packer.HasChanged();
+}
+
+
+/* Specialized function for packing rigid bodies. We want certain parameters
+ * from the Rigidbody object: position, rotation, anglar velocity and velocity.
+ */
+bool SerializeRigidbody (Rigidbody& rigidbody, BitstreamPacker& packer)
+{
+ IPhysics::RigidBodyState state;
+ IPhysics& module = *GetIPhysics();
+
+ if (packer.IsWriting())
+ module.GetRigidBodyState(rigidbody, &state);
+
+ packer.Serialize(state.position, kPositionEpsilon);
+ packer.Serialize(state.rotation, kRotationEpsilon);
+ packer.Serialize(state.velocity, kPositionEpsilon);
+ packer.Serialize(state.avelocity, kPositionEpsilon);
+
+ if (packer.IsReading())
+ module.SetRigidBodyState(rigidbody, state);
+
+ return packer.HasChanged();
+}
+
+bool SerializeMono (MonoBehaviour& mono, BitstreamPacker& deltaState, NetworkMessageInfo& timeStamp)
+{
+ if (mono.IsActive() && mono.GetInstance())
+ {
+ ScriptingMethodPtr method = mono.GetMethod(MonoScriptCache::kSerializeNetView);
+ if (method)
+ {
+ MonoObject* monoStream = mono_object_new(mono_domain_get(), GetMonoManager().GetCommonClasses().bitStream);
+ ExtractMonoObjectData<BitstreamPacker*> (monoStream) = &deltaState;
+ void* values[] = { monoStream, &timeStamp };
+ MonoException* exception;
+ MonoObject* instance = mono.GetInstance();
+ mono_runtime_invoke_profiled (method->monoMethod, mono.GetInstance(), values, &exception);
+
+ if (exception != NULL)
+ Scripting::LogException(exception, Scripting::GetInstanceIDFromScriptingWrapper(instance));
+
+ ExtractMonoObjectData<BitstreamPacker*> (monoStream) = NULL;
+ }
+ }
+
+ return deltaState.HasChanged();
+}
+
+
+#endif
diff --git a/Runtime/Network/PackStateSpecialized.h b/Runtime/Network/PackStateSpecialized.h
new file mode 100644
index 0000000..93e066e
--- /dev/null
+++ b/Runtime/Network/PackStateSpecialized.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_NETWORK
+
+class BitstreamPacker;
+class Rigidbody;
+class MonoBehaviour;
+class Transform;
+class Animation;
+struct NetworkMessageInfo;
+
+bool SerializeRigidbody (Rigidbody& rigidbody, BitstreamPacker& packer);
+
+void UnpackTransform (Transform& transform, BitstreamPacker& packer);
+bool PackTransform (Transform& transform, BitstreamPacker& packer);
+
+bool SerializeAnimation (Animation& animation, BitstreamPacker& packer);
+
+bool SerializeMono (MonoBehaviour& mono, BitstreamPacker& deltaState, NetworkMessageInfo& timeStamp);
+
+#endif \ No newline at end of file
diff --git a/Runtime/Network/PlayerCommunicator/EditorConnection.cpp b/Runtime/Network/PlayerCommunicator/EditorConnection.cpp
new file mode 100644
index 0000000..d8814fa
--- /dev/null
+++ b/Runtime/Network/PlayerCommunicator/EditorConnection.cpp
@@ -0,0 +1,335 @@
+#include "UnityPrefix.h"
+
+#if UNITY_EDITOR
+
+#include "EditorConnection.h"
+#include "Editor/Platform/Interface/EditorWindows.h"
+#include "Runtime/Misc/SystemInfo.h"
+
+#include "Runtime/Serialize/FileCache.h"
+#include "Runtime/Serialize/CacheWrap.h"
+
+#include "Runtime/Network/NetworkUtility.h"
+#include "Runtime/Profiler/TimeHelper.h"
+#include "Runtime/Math/Random/rand.h"
+#include "Runtime/Utilities/PathNameUtility.h"
+#include "Runtime/Utilities/PlayerPrefs.h"
+#include "Runtime/Threads/Thread.h"
+
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h"
+#include "Runtime/Network/PlayerCommunicator/PlayerConnection.h"
+
+static const char* kLastConnectedPlayer = "LastConnectedPlayer";
+
+EditorConnection* EditorConnection::ms_Instance = NULL;
+
+
+EditorConnection::EditorConnection(unsigned short multicastPort)
+: GeneralConnection()
+{
+ ABSOLUTE_TIME_INIT(m_LastCleanup);
+ if (!m_MulticastSocket.Initialize(PLAYER_MULTICAST_GROUP, multicastPort))
+ ErrorStringMsg("Failed to setup multicast socket for player connection.");
+ if (!m_MulticastSocket.Join())
+ ErrorStringMsg("Unable to join player connection multicast group.");
+}
+
+EditorConnection& EditorConnection::Get()
+{
+ return *ms_Instance;
+}
+
+void EditorConnection::Initialize ()
+{
+ GeneralConnection::Initialize();
+ Assert(ms_Instance == NULL);
+ ms_Instance = new EditorConnection();
+}
+void EditorConnection::Cleanup ()
+{
+ Assert(ms_Instance != NULL);
+ delete ms_Instance;
+ ms_Instance = NULL;
+ GeneralConnection::Cleanup();
+}
+
+int EditorConnection::ConnectPlayerDirectIP(const std::string& IP)
+{
+ Connection* cnx = GetConnection(PLAYER_DIRECT_IP_CONNECT_GUID);
+ if (cnx && cnx->IsValid())
+ return PLAYER_DIRECT_IP_CONNECT_GUID;
+
+ int socketHandle = -1;
+ int timeout = 1;
+ // Poll at 1 ms, 10 ms, and 100 ms before giving up
+ while (socketHandle == -1)
+ {
+ // A forced repaint is required, since this method is a blocking call
+ LogStringMsg("Attempting to connect to player ip: %s with a %d ms timeout", IP.c_str(), timeout);
+ ForceRepaintOnAllViews();
+
+ for(int i = 0; i <= PLAYER_PORT_MASK; i++)
+ {
+ socketHandle = ::Socket::Connect(IP.c_str(), PLAYER_LISTEN_PORT + i, timeout, true, false);
+ if(socketHandle != -1)
+ break;
+ }
+ if (socketHandle != -1)
+ break;
+
+ timeout *= 10;
+ if (timeout > 100)
+ break;
+ }
+ if(socketHandle == -1)
+ {
+ ErrorStringMsg("Failed to connect to player ip: %s", IP.c_str());
+ return -1;
+ }
+
+ RegisterConnection(PLAYER_DIRECT_IP_CONNECT_GUID, socketHandle);
+ if (!ms_RunningUnitTests)
+ EditorPrefs::SetString(kLastConnectedPlayer, "");
+
+ return PLAYER_DIRECT_IP_CONNECT_GUID;
+}
+
+int EditorConnection::ConnectPlayer (UInt32 guid)
+{
+ AvailablePlayerMap::iterator found = m_AvailablePlayers.find(guid);
+ if (found == m_AvailablePlayers.end())
+ return -1;
+
+ MulticastInfo& multicastInfo = found->second.m_MulticastInfo;
+ if(!multicastInfo.IsValid())
+ return -1;
+
+ Assert(multicastInfo.GetGuid() == guid);
+ Connection* cnx = GetConnection(guid);
+ if (cnx && cnx->IsValid())
+ return guid;
+
+ int socketHandle;
+ if (SOCK_ERROR(socketHandle = ::Socket::Connect(multicastInfo.GetIP().c_str(), multicastInfo.GetPort(), 500 /*timeout*/)))
+ {
+ ErrorStringMsg("Failed to connect to player ip: %s, port: %d", multicastInfo.GetIP().c_str(), multicastInfo.GetPort());
+ return 0;
+ }
+
+ RegisterConnection(guid, socketHandle);
+ if (!ms_RunningUnitTests)
+ EditorPrefs::SetString(kLastConnectedPlayer, multicastInfo.GetIdentifier());
+
+ return guid;
+}
+
+UInt32 EditorConnection::AddPlayer(std::string hostName, std::string localIP, unsigned short port, UInt32 guid, int flags)
+{
+ std::string buffer = Format(SERVER_IDENTIFICATION_FORMAT, localIP.c_str(), port, flags, guid, -1, ms_Version, hostName.c_str(), 0 );
+ MulticastInfo multicastInfo;
+ if(multicastInfo.Parse(buffer.c_str()))
+ {
+ AvailablePlayerMap::iterator found = m_AvailablePlayers.find(multicastInfo.GetGuid());
+ if(found != m_AvailablePlayers.end())
+ {
+ // entry already in the list - renew the timestamp
+ found->second.m_LastPing = START_TIME;
+ }
+ else
+ {
+ m_AvailablePlayers.insert (std::make_pair(multicastInfo.GetGuid(), AvailablePlayer(START_TIME, multicastInfo))).first;
+ }
+ return multicastInfo.GetGuid();
+ }
+ return 0;
+}
+
+void EditorConnection::RemovePlayer(UInt32 guid)
+{
+ Disconnect(guid);
+ AvailablePlayerMap::iterator found = m_AvailablePlayers.find(guid);
+ if(found != m_AvailablePlayers.end())
+ m_AvailablePlayers.erase(found);
+}
+
+void EditorConnection::EnablePlayer(UInt32 guid, bool enable)
+{
+ AvailablePlayerMap::iterator player = m_AvailablePlayers.find(guid);
+ if (player != m_AvailablePlayers.end())
+ player->second.m_Enabled = enable;
+}
+
+void
+EditorConnection::ResetLastPlayer()
+{
+ EditorPrefs::SetString(kLastConnectedPlayer, "");
+}
+
+void EditorConnection::GetAvailablePlayers( std::vector<UInt32>& values )
+{
+ AvailablePlayerMap::iterator it = m_AvailablePlayers.begin();
+ for( ; it != m_AvailablePlayers.end(); ++it){
+ if (it->second.m_Enabled && it->second.m_MulticastInfo.GetIP() == m_LocalIP)
+ values.push_back(it->second.m_MulticastInfo.GetGuid());
+ }
+ it = m_AvailablePlayers.begin();
+ for( ; it != m_AvailablePlayers.end(); ++it){
+ if (it->second.m_Enabled && it->second.m_MulticastInfo.GetIP() != m_LocalIP)
+ values.push_back(it->second.m_MulticastInfo.GetGuid());
+ }
+}
+
+void EditorConnection::GetAvailablePlayers(RuntimePlatform platform, vector<UInt32>& values)
+{
+ string const id = systeminfo::GetRuntimePlatformString(platform);
+
+ for (AvailablePlayerMap::const_iterator it = m_AvailablePlayers.begin() ; it != m_AvailablePlayers.end(); ++it)
+ {
+ AvailablePlayer const& player = it->second;
+ if (player.m_Enabled && BeginsWith(player.m_MulticastInfo.GetIdentifier(), id) && (player.m_MulticastInfo.GetIP() == m_LocalIP))
+ values.push_back(player.m_MulticastInfo.GetGuid());
+ }
+
+ for (AvailablePlayerMap::const_iterator it = m_AvailablePlayers.begin() ; it != m_AvailablePlayers.end(); ++it)
+ {
+ AvailablePlayer const& player = it->second;
+ if (player.m_Enabled && BeginsWith(player.m_MulticastInfo.GetIdentifier(), id) && (player.m_MulticastInfo.GetIP() != m_LocalIP))
+ values.push_back(player.m_MulticastInfo.GetGuid());
+ }
+}
+
+std::string EditorConnection::GetConnectionIdentifier( UInt32 guid )
+{
+ AvailablePlayerMap::iterator it = m_AvailablePlayers.find(guid);
+ if (it != m_AvailablePlayers.end())
+ return it->second.m_MulticastInfo.GetIdentifier();
+ return "None";
+}
+
+bool EditorConnection::IsIdentifierConnectable( UInt32 guid )
+{
+ AvailablePlayerMap::iterator it = m_AvailablePlayers.find(guid);
+ if (it != m_AvailablePlayers.end())
+ return it->second.m_MulticastInfo.IsValid();
+ return false;
+}
+
+bool EditorConnection::IsIdentifierOnLocalhost( UInt32 guid )
+{
+ AvailablePlayerMap::iterator it = m_AvailablePlayers.find(guid);
+ if (it != m_AvailablePlayers.end())
+ return it->second.m_MulticastInfo.IsLocalhost();
+ return false;
+}
+
+GeneralConnection::MulticastInfo EditorConnection::PollWithCustomMessage()
+{
+ MulticastInfo multicastInfo;
+
+ sockaddr_in srcaddr;
+ socklen_t srcaddr_len = sizeof(srcaddr);
+ char buffer[kMulticastBufferSize];
+ RecvUserData recvData;
+ recvData.srcAddr = (struct sockaddr*)&srcaddr;
+ recvData.srcLen = srcaddr_len;
+ int size = m_MulticastSocket.Recv(buffer, kMulticastBufferSize, &recvData);
+ if (!SOCK_ERROR(size))
+ {
+ // Ensure that buffer is null terminated string
+ buffer[size] = 0;
+ if(multicastInfo.Parse(buffer, &srcaddr))
+ {
+ AvailablePlayerMap::iterator found = m_AvailablePlayers.find(multicastInfo.GetGuid());
+ if(found != m_AvailablePlayers.end())
+ {
+ // entry already in the list - renew the timestamp
+ found->second.m_LastPing = START_TIME;
+ }
+ else
+ {
+ // remove old player if new one has connected
+ for (AvailablePlayerMap::const_iterator it = m_AvailablePlayers.begin(); it != m_AvailablePlayers.end(); ++it)
+ if (!it->second.m_Enabled && !StrCmp(it->second.m_MulticastInfo.GetIdentifier(), multicastInfo.GetIdentifier()))
+ {
+ RemovePlayer(it->first);
+ break;
+ }
+
+ m_AvailablePlayers.insert (std::make_pair(multicastInfo.GetGuid(), AvailablePlayer(START_TIME, multicastInfo)));
+ if (multicastInfo.IsValid() && multicastInfo.ImmediateConnect())
+ {
+ bool atemptAutoConnect;
+ if (ms_RunningUnitTests)
+ {
+ atemptAutoConnect = false;
+ if (!IsConnected())
+ {
+ const char* connectingIp = multicastInfo.GetIP().c_str();
+
+ const int maxNumberOfIps = 10;
+ char localIps[maxNumberOfIps][16];
+ GetIPs(localIps);
+ for (int i = 0; i < maxNumberOfIps; ++i)
+ {
+ if (strcmp(localIps[i], connectingIp) == 0)
+ {
+ atemptAutoConnect = true;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ string lastPlayerId = EditorPrefs::GetString(kLastConnectedPlayer);
+
+ // No prior player are known and new player is built by us and requires autoconnection
+ if (lastPlayerId.empty() && multicastInfo.GetEditorGuid() == GetLocalGuid())
+ atemptAutoConnect = true;
+ else
+ atemptAutoConnect = (lastPlayerId == multicastInfo.GetIdentifier());
+ }
+
+ if (atemptAutoConnect)
+ ConnectPlayer(multicastInfo.GetGuid());
+ }
+ }
+ }
+ else
+ {
+ // can not connect to this player (version mismatch)
+
+ }
+ }
+
+ // clean up the list every 2 seconds ( players not pinging in 10 seconds are removed from the map )
+ if(GetProfileTime(ELAPSED_TIME(m_LastCleanup)) > 2*kTimeSecond)
+ {
+ m_LastCleanup = START_TIME;
+ AvailablePlayerMap::iterator it = m_AvailablePlayers.begin();
+ while (it != m_AvailablePlayers.end())
+ {
+ UInt32 guid = it->first;
+ AvailablePlayer& player = it->second;
+ if (player.m_MulticastInfo.GetGuid() == PLAYER_DIRECTCONNECT_GUID)
+ {
+ ++it;
+ continue;
+ }
+ if (!GetConnection(guid) && (GetProfileTime(ELAPSED_TIME(player.m_LastPing)) > 10*kTimeSecond))
+ {
+ AvailablePlayerMap::iterator erase = it++;
+ m_AvailablePlayers.erase(erase);
+ }
+ else
+ ++it;
+ }
+ }
+
+ GeneralConnection::Poll();
+
+ return multicastInfo;
+}
+
+#endif // ENABLE_PLAYERCONNECTION
diff --git a/Runtime/Network/PlayerCommunicator/EditorConnection.h b/Runtime/Network/PlayerCommunicator/EditorConnection.h
new file mode 100644
index 0000000..c2f020d
--- /dev/null
+++ b/Runtime/Network/PlayerCommunicator/EditorConnection.h
@@ -0,0 +1,77 @@
+#pragma once
+
+#if UNITY_EDITOR
+
+#include "GeneralConnection.h"
+#include "Runtime/Misc/SystemInfo.h"
+#include "Runtime/Serialize/SwapEndianBytes.h"
+
+
+#if UNITY_WIN
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <errno.h>
+#endif
+#include "Runtime/Profiler/TimeHelper.h"
+#include "Runtime/Threads/Mutex.h"
+
+
+class EditorConnection : public GeneralConnection
+{
+public:
+ EditorConnection (unsigned short multicastPort = PLAYER_MULTICAST_PORT);
+
+ static void Initialize ();
+ static void Cleanup ();
+
+ void ResetLastPlayer();
+
+ void GetAvailablePlayers( std::vector<UInt32>& values );
+ void GetAvailablePlayers( RuntimePlatform platform, std::vector<UInt32>& values );
+ std::string GetConnectionIdentifier( UInt32 guid );
+ bool IsIdentifierConnectable( UInt32 guid );
+ bool IsIdentifierOnLocalhost( UInt32 guid );
+
+ int ConnectPlayer (UInt32 guid);
+ int ConnectPlayerDirectIP(const std::string& IP);
+
+ UInt32 AddPlayer(std::string hostName, std::string localIP, unsigned short port, UInt32 guid, int flags);
+ void RemovePlayer(UInt32 guid);
+ void EnablePlayer(UInt32 guid, bool enable);
+
+ // Singleton accessor for editorconnection
+ static EditorConnection& Get ();
+ static EditorConnection* ms_Instance;
+
+ MulticastInfo PollWithCustomMessage ();
+
+private:
+ virtual bool IsServer () { return false; }
+
+ struct AvailablePlayer
+ {
+ AvailablePlayer(ABSOLUTE_TIME time, const MulticastInfo& info)
+ : m_LastPing (time)
+ , m_MulticastInfo(info)
+ , m_Enabled(true)
+ {}
+
+ ABSOLUTE_TIME m_LastPing;
+ MulticastInfo m_MulticastInfo;
+ bool m_Enabled;
+ };
+
+private:
+ typedef std::map< UInt32, AvailablePlayer > AvailablePlayerMap;
+ AvailablePlayerMap m_AvailablePlayers;
+
+ ABSOLUTE_TIME m_LastCleanup;
+};
+
+#endif
diff --git a/Runtime/Network/PlayerCommunicator/GeneralConnection.cpp b/Runtime/Network/PlayerCommunicator/GeneralConnection.cpp
new file mode 100644
index 0000000..dc4c89d
--- /dev/null
+++ b/Runtime/Network/PlayerCommunicator/GeneralConnection.cpp
@@ -0,0 +1,666 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_PLAYERCONNECTION
+
+#include "GeneralConnection.h"
+#include "GeneralConnectionInternals.h"
+#include "Runtime/Math/Random/rand.h"
+#include "Runtime/Network/NetworkUtility.h"
+#include "Runtime/Profiler/TimeHelper.h"
+
+bool GeneralConnection::ms_RunningUnitTests = false;
+int GeneralConnection::ms_Version = 0x00100100;
+
+GeneralConnection::GeneralConnection ()
+: m_LogEnabled(true)
+{
+ char ips[10][16];
+ int count = GetIPs(ips);
+ if (count != 0)
+ m_LocalIP = ips[0];
+ else
+ m_LocalIP = "0.0.0.0";
+
+#if UNITY_BB10
+ for(int x = 0; x < sizeof(ips); x++)
+ {
+ if(std::string(ips[x]).find("169.254") == 0)
+ {
+ m_LocalIP = ips[x];
+ break;
+ }
+ }
+#endif
+
+ m_LocalGuid = Rand((int)GetProfileTime(START_TIME)).Get();
+ // We reserve ANY_PLAYERCONNECTION guid for special use
+ if (m_LocalGuid == ANY_PLAYERCONNECTION) m_LocalGuid = ANY_PLAYERCONNECTION ^ 1;
+}
+
+UInt32 GeneralConnection::GetLocalGuid() const
+{
+ return m_LocalGuid;
+}
+
+void GeneralConnection::WaitForFinish()
+{
+ int msgWait = 60;
+
+ while (HasBytesToSend())
+ {
+ if (msgWait++ == 60)
+ {
+ printf_console("Waiting for finish\n");
+ msgWait = 0;
+ }
+
+ Poll();
+ Thread::Sleep(0.05);
+ }
+}
+
+bool GeneralConnection::HasBytesToSend() const
+{
+ ConnectionMap::const_iterator it = m_Connections.begin();
+ for ( ; it != m_Connections.end(); ++it)
+ if (it->second->HasBytesToSend())
+ return true;
+
+ return false;
+}
+
+GeneralConnection::~GeneralConnection()
+{
+ DisconnectAll();
+}
+
+
+void GeneralConnection::DisconnectAll()
+{
+ ConnectionMap::iterator it = m_Connections.begin();
+ std::vector< int > disconnectGuids;
+ for( ; it != m_Connections.end(); ++it)
+ disconnectGuids.push_back(it->first);
+
+ for (int i = 0; i < disconnectGuids.size(); i++)
+ Disconnect(disconnectGuids[i]);
+
+ Assert(m_Connections.empty());
+}
+
+
+void GeneralConnection::Initialize ()
+{
+ NetworkInitialize();
+}
+
+void GeneralConnection::Cleanup ()
+{
+ NetworkCleanup();
+}
+
+
+GeneralConnection::Connection* GeneralConnection::GetConnection(UInt32 guid)
+{
+ ConnectionMap::iterator it = m_Connections.find(guid);
+ if (it == m_Connections.end())
+ return NULL;
+ return it->second;
+}
+
+void GeneralConnection::RegisterConnection(UInt32 guid, TSocketHandle socketHandle)
+{
+ if (GetConnection(guid))
+ Disconnect(guid);
+
+ LOG_PLAYER_CONNECTION(Format("PlayerConnection registered: %d", guid));
+ m_Connections[guid] = new Connection(socketHandle);
+ for(int i = 0; i < m_ConnectionHandlers.size(); i++)
+ (m_ConnectionHandlers[i])(guid);
+}
+
+void GeneralConnection::Disconnect(UInt32 guid)
+{
+ ConnectionMap::iterator it = m_Connections.find(guid);
+ if (it == m_Connections.end())
+ return;
+
+ LOG_PLAYER_CONNECTION(Format("PlayerConnection disconnecting: %d", guid));
+ for (int i = 0; i < m_DisconnectionHandlers.size(); i++)
+ (m_DisconnectionHandlers[i])(guid);
+
+#if ENABLE_PLAYER_CONNECTION_DEBUG_LOG
+ Connection& connection = *it->second;
+ LOG_PLAYER_CONNECTION(Format("GeneralConnection::Connection send buffer mem usage %d\n", connection.GetSendBufferSize()));
+ LOG_PLAYER_CONNECTION(Format("GeneralConnection::Connection recv buffer mem usage %d\n", connection.GetRecvBufferSize()));
+#endif
+
+ delete it->second;
+
+ m_Connections.erase(it);
+}
+
+
+void GeneralConnection::SendMessage(UInt32 guid, MessageID id, const void* data, UInt32 size)
+{
+ NetworkMessage message;
+ message.InitializeMessage();
+ message.SetID(id);
+ message.SetDataSize(size);
+
+ bool oldlog = IsLogEnabled();
+ SetLogEnabled(false);
+
+ if (guid == ANY_PLAYERCONNECTION)
+ {
+ LOG_PLAYER_CONNECTION(Format("PlayerConnection send message to ALL, id '%d', size '%d'", id, size));
+ ConnectionMap::iterator it = m_Connections.begin();
+ for ( ; it != m_Connections.end(); ++it)
+ it->second->SendMessage(message, data);
+ }
+ else
+ {
+ LOG_PLAYER_CONNECTION(Format("PlayerConnection send message to '%d', id '%d', size '%d'", guid, id, size));
+ ConnectionMap::iterator it = m_Connections.find(guid);
+ if (it != m_Connections.end())
+ it->second->SendMessage(message, data);
+ }
+ SetLogEnabled(oldlog);
+}
+
+void GeneralConnection::Poll()
+{
+ ABSOLUTE_TIME start = START_TIME;
+ ConnectionMap::iterator it = m_Connections.begin();
+ UNITY_TEMP_VECTOR(int) disconnectGuids;
+ for ( ; it != m_Connections.end(); ++it)
+ {
+ Connection& connection = *(it->second);
+ connection.Poll();
+ const void* messageDataPtr;
+ NetworkMessage messageHeader;
+ while ( (GetProfileTime(ELAPSED_TIME(start)) < 20*kTimeMillisecond) && ((messageDataPtr = connection.ReceiveMessage(&messageHeader)) != NULL) )
+ {
+ LOG_PLAYER_CONNECTION(Format("PlayerConnection recv message id '%d', size '%d'", messageHeader.GetID(), messageHeader.GetDataSize()));
+ std::map< MessageID, MessageHandlerFunc >::iterator handler = m_HandlerMap.find(messageHeader.GetID());
+ if (handler != m_HandlerMap.end())
+ (handler->second)(messageDataPtr, messageHeader.GetDataSize(), it->first);
+ connection.ReleaseReceivedMessage();
+ }
+ if (!connection.IsValid())
+ disconnectGuids.push_back(it->first);
+ }
+
+ for (int i = 0; i < disconnectGuids.size(); i++)
+ Disconnect(disconnectGuids[i]);
+}
+
+// Handlers
+void GeneralConnection::RegisterMessageHandler( MessageID messageID, MessageHandlerFunc func )
+{
+ if(m_HandlerMap.find(messageID) != m_HandlerMap.end())
+ ErrorString("MessageHandler already registered");
+ m_HandlerMap[messageID] = func;
+}
+
+void GeneralConnection::RegisterConnectionHandler( ConnectionHandlerFunc func )
+{
+ for (int i = 0; i < m_ConnectionHandlers.size(); i++)
+ Assert(m_ConnectionHandlers[i] != func);
+
+ m_ConnectionHandlers.push_back(func);
+
+ // call the connect handler on already connected sockets
+ ConnectionMap::iterator it = m_Connections.begin();
+ for( ; it != m_Connections.end(); ++it)
+ (func)(it->first);
+}
+
+void GeneralConnection::RegisterDisconnectionHandler( ConnectionHandlerFunc func )
+{
+ for (int i = 0; i < m_DisconnectionHandlers.size(); i++)
+ Assert(m_DisconnectionHandlers[i] != func);
+
+ m_DisconnectionHandlers.push_back(func);
+}
+
+void GeneralConnection::UnregisterMessageHandler( MessageID messageID, MessageHandlerFunc func )
+{
+ std::map< MessageID, MessageHandlerFunc >::iterator found = m_HandlerMap.find(messageID);
+ if(found == m_HandlerMap.end())
+ ErrorString("MessageHandler not registered");
+ Assert(found->second == func);
+ m_HandlerMap.erase(found);
+}
+
+void GeneralConnection::UnregisterConnectionHandler( ConnectionHandlerFunc func )
+{
+ std::vector< ConnectionHandlerFunc >::iterator handlerIt = m_ConnectionHandlers.begin();
+ for ( ; handlerIt != m_ConnectionHandlers.end(); ++handlerIt)
+ {
+ if (*handlerIt == func)
+ {
+ m_ConnectionHandlers.erase(handlerIt);
+ return;
+ }
+ }
+}
+
+void GeneralConnection::UnregisterDisconnectionHandler( ConnectionHandlerFunc func )
+{
+ // call the disconnect handler on already connected sockets
+ ConnectionMap::iterator it = m_Connections.begin();
+ for( ; it != m_Connections.end(); ++it)
+ (func)(it->first);
+
+ std::vector< ConnectionHandlerFunc >::iterator handlerIt = m_DisconnectionHandlers.begin();
+ for ( ; handlerIt != m_DisconnectionHandlers.end(); ++handlerIt)
+ {
+ if (*handlerIt == func)
+ {
+ m_DisconnectionHandlers.erase(handlerIt);
+ return;
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------
+// Connection
+// ------------------------------------------------------------------------------
+GeneralConnection::Connection::Connection(TSocketHandle socketHandle)
+: m_SocketStream(socketHandle, kDataBufferMaxSize, kDataBufferMaxSize)
+, m_PendingMessageData(NULL)
+, m_HasPendingMessage(false)
+{
+}
+
+GeneralConnection::Connection::~Connection()
+{
+ if (m_PendingMessageData)
+ ReleaseReceivedMessage();
+}
+
+const void* GeneralConnection::Connection::ReceiveMessage(NetworkMessage* message)
+{
+ AssertBreak(!m_PendingMessageData);
+ Mutex::AutoLock lock (m_RecvMutex);
+
+ ExtendedGrowingRingbuffer& recvBuffer = m_SocketStream.RecvBuffer();
+ UInt32 bytesAvailable = recvBuffer.GetAvailableSize();
+ if (!m_HasPendingMessage && bytesAvailable >= sizeof(NetworkMessage))
+ {
+ m_SocketStream.RecvAll(&m_PendingMessage, sizeof(NetworkMessage));
+ m_HasPendingMessage = true;
+ bytesAvailable = recvBuffer.GetAvailableSize();
+ Assert(recvBuffer.GetSize() >= m_PendingMessage.GetDataSize() && "Buffer is not large enough for the message.");
+
+ }
+
+ if (m_HasPendingMessage)
+ {
+ UInt32 messageSize = m_PendingMessage.GetDataSize();
+ if (m_HasPendingMessage && bytesAvailable >= messageSize)
+ {
+ m_HasPendingMessage = false;
+ UInt32 linearData = messageSize;
+ m_PendingMessageData = (void*) recvBuffer.ReadPtr(&linearData);
+ m_LocallyAllocatedMessageData = linearData < messageSize;
+ if (m_LocallyAllocatedMessageData)
+ {
+ m_PendingMessageData = UNITY_MALLOC(kMemNetwork, messageSize);
+ m_SocketStream.RecvAll(m_PendingMessageData, messageSize);
+ }
+ memcpy(message, &m_PendingMessage, sizeof(NetworkMessage));
+ return m_PendingMessageData;
+ }
+ }
+ return NULL;
+}
+
+void GeneralConnection::Connection::ReleaseReceivedMessage()
+{
+ AssertBreak(m_PendingMessageData);
+ Mutex::AutoLock lock (m_RecvMutex);
+
+ if (m_LocallyAllocatedMessageData)
+ UNITY_FREE(kMemNetwork, m_PendingMessageData);
+ else
+ m_SocketStream.RecvBuffer().ReadPtrUpdate(m_PendingMessageData, m_PendingMessage.GetDataSize());
+ m_PendingMessageData = NULL;
+}
+
+void GeneralConnection::Connection::SendMessage(NetworkMessage& message, const void* data)
+{
+ Mutex::AutoLock lock (m_SendMutex);
+ if(message.AllowSkip() && !m_SocketStream.CanSendNonblocking(sizeof(NetworkMessage) + message.GetDataSize()))
+ {
+ WarningString("Skipping profile frame. Reciever can not keep up with the amount of data sent");
+ return;
+ }
+ m_SocketStream.SendAll(&message, sizeof(NetworkMessage));
+ m_SocketStream.SendAll(data, message.GetDataSize());
+}
+
+// ------------------------------------------------------------------------------
+// NetworkMessage
+// ------------------------------------------------------------------------------
+void GeneralConnection::NetworkMessage::InitializeMessage()
+{
+ UInt16 magicNumber = PLAYER_MESSAGE_MAGIC_NUMBER;
+ SwapEndianBytesLittleToNative(magicNumber);
+ m_MagicNumber = magicNumber;
+}
+
+bool GeneralConnection::NetworkMessage::CheckMessageValidity()
+{
+ UInt16 magicNumber = m_MagicNumber;
+ SwapEndianBytesLittleToNative(magicNumber);
+ return magicNumber == PLAYER_MESSAGE_MAGIC_NUMBER;
+}
+
+void GeneralConnection::NetworkMessage::SetID( GeneralConnection::MessageID messageID )
+{
+ UInt16 id = messageID;
+ SwapEndianBytesLittleToNative(id);
+ m_ID = id;
+}
+
+GeneralConnection::MessageID GeneralConnection::NetworkMessage::GetID()
+{
+ UInt16 id = m_ID;
+ SwapEndianBytesLittleToNative(id);
+ return (MessageID) id;
+}
+
+void GeneralConnection::NetworkMessage::SetDataSize( UInt32 size )
+{
+ SwapEndianBytesLittleToNative(size);
+ m_Size = size;
+}
+
+UInt32 GeneralConnection::NetworkMessage::GetDataSize()
+{
+ UInt32 size = m_Size;
+ SwapEndianBytesLittleToNative(size);
+ return size;
+}
+
+// ------------------------------------------------------------------------------
+// MulticastInfo
+// ------------------------------------------------------------------------------
+#if ENABLE_MULTICAST
+GeneralConnection::MulticastInfo::MulticastInfo()
+{
+ Clear();
+}
+
+void GeneralConnection::MulticastInfo::Clear()
+{
+ m_Valid = false;
+ m_Flags = 0;
+ m_Port = 0;
+ m_Guid = 0;
+ m_EditorGuid = 0;
+}
+bool GeneralConnection::MulticastInfo::Parse( const char* buffer, void* in)
+{
+ char ip[kMulticastBufferSize];
+ char id[kMulticastBufferSize];
+ int version;
+ int debug;
+ if(sscanf(buffer, SERVER_IDENTIFICATION_FORMAT, ip, &m_Port, &m_Flags, &m_Guid, &m_EditorGuid, &version, id, &debug) != 8)
+ {
+ Clear();
+ m_Valid = false;
+ m_IsLocalhost = false;
+ return false;
+ //ErrorString(Format("MulticastInfo should be in this format: '%s' but got: '%s'", SERVER_IDENTIFICATION_FORMAT, buffer));
+ }
+#if !UNITY_WINRT
+ m_Ip = in ? InAddrToIP((sockaddr_in*)in) : std::string(ip);
+#endif
+
+ m_Identifier = std::string(id);
+ m_Valid = (version == ms_Version);
+ SetLocalhostFlag();
+ return true;
+}
+
+void GeneralConnection::MulticastInfo::SetLocalhostFlag()
+{
+ std::string localIP;
+ char ips[10][16];
+ int count = GetIPs(ips);
+ if (count != 0)
+ localIP = ips[0];
+ else
+ localIP = "0.0.0.0";
+ m_IsLocalhost = (m_Ip.compare("0.0.0.0") == 0) || (m_Ip.compare("127.0.0.1") == 0) || (m_Ip.compare(localIP) == 0);
+}
+
+#endif //ENABLE_MULTICAST
+
+
+/// These tests are unstable and run for too long?
+// ---------------------------------------------------------------------------
+#if ENABLE_UNIT_TESTS && 0
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "EditorConnection.h"
+#include "PlayerConnection.h"
+
+SUITE (GeneralConnectionTests)
+{
+ bool isClientConnected = false;
+ UInt32 clientConnectionGuid = -1;
+ void ClientConnectionHandler (UInt32 guid) { isClientConnected = true; clientConnectionGuid = guid; }
+ void ClientDisconnectionHandler (UInt32 guid) { isClientConnected = false; clientConnectionGuid = -1;}
+
+ bool isServerConnected = false;
+ UInt32 serverConnectionGuid = -1;
+ void ServerConnectionHandler (UInt32 guid) { isServerConnected = true; serverConnectionGuid = guid;}
+ void ServerDisconnectionHandler (UInt32 guid) { isServerConnected = false; serverConnectionGuid = -1;}
+
+ struct GeneralConnectionFixture
+ {
+ GeneralConnectionFixture ()
+ {
+ Initialize();
+ }
+ ~GeneralConnectionFixture ()
+ {
+ Destroy();
+ }
+
+ void Initialize ()
+ {
+ GeneralConnection::RunningUnitTest();
+ Rand r (GetProfileTime(START_TIME));
+ int multicastport = PLAYER_UNITTEST_MULTICAST_PORT + (r.Get() & PLAYER_PORT_MASK);
+ player = new PlayerConnection("", multicastport);
+ editor = new EditorConnection(multicastport);
+ editor->RegisterConnectionHandler( ClientConnectionHandler );
+ player->RegisterConnectionHandler( ServerConnectionHandler );
+ editor->RegisterDisconnectionHandler( ClientDisconnectionHandler );
+ player->RegisterDisconnectionHandler( ServerDisconnectionHandler );
+ }
+
+ void Destroy ()
+ {
+ delete player;
+ delete editor;
+ player = NULL;
+ editor = NULL;
+ }
+
+ void Connect ()
+ {
+ //record time
+ ABSOLUTE_TIME start = START_TIME;
+ while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond)
+ {
+ player->Poll();
+ editor->PollWithCustomMessage();
+ if (player->IsConnected() && editor->IsConnected()) break;
+ }
+ }
+
+ void CheckConnectionState(bool state)
+ {
+ // is internal connection state
+ CHECK_EQUAL (state, player->IsConnected() );
+ CHECK_EQUAL (state, editor->IsConnected() );
+ // is custom connect handler called
+ CHECK_EQUAL (state, isClientConnected);
+ CHECK_EQUAL (state, isServerConnected);
+ }
+
+ PlayerConnection* player;
+ EditorConnection* editor;
+ };
+
+ std::string message;
+ void HandleGeneralConnectionMessage (const void* data, UInt32 size, UInt32 guid)
+ {
+ message = std::string((char*)data);
+ }
+
+ int* largeMessage;
+ void HandleLargeMessage(const void* data, UInt32 size, UInt32 guid)
+ {
+ largeMessage = new int[size/sizeof(int)];
+ memcpy(largeMessage, data, size);
+ }
+
+ int messageCount = 0;
+ void HandleManyMessages(const void* data, UInt32 size, UInt32 guid)
+ {
+ CHECK_EQUAL(((char*)data)[10],messageCount);
+ messageCount++;
+ }
+
+ TEST_FIXTURE(GeneralConnectionFixture, CanDisconnectServer)
+ {
+ Connect();
+ CheckConnectionState(true);
+
+ player->DisconnectAll();
+
+ ABSOLUTE_TIME start = START_TIME;
+ while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond)
+ {
+ editor->PollWithCustomMessage();
+ if (!editor->IsConnected()) break;
+ }
+ CheckConnectionState(false);
+ }
+
+ TEST_FIXTURE(GeneralConnectionFixture, CanConnect)
+ {
+ Connect();
+ CheckConnectionState(true);
+ }
+
+ TEST_FIXTURE(GeneralConnectionFixture, CanDisconnectClient)
+ {
+ Connect();
+ CheckConnectionState(true);
+
+ editor->DisconnectAll();
+
+ ABSOLUTE_TIME start = START_TIME;
+ while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond)
+ {
+ player->Poll();
+ if (!player->IsConnected()) break;
+ }
+ CheckConnectionState(false);
+ }
+
+ TEST_FIXTURE(GeneralConnectionFixture, CanSendMessage)
+ {
+ Connect();
+ editor->RegisterMessageHandler(GeneralConnection::kLastMessageID,HandleGeneralConnectionMessage);
+ player->RegisterMessageHandler(GeneralConnection::kLastMessageID,HandleGeneralConnectionMessage);
+
+ player->SendMessage(serverConnectionGuid, GeneralConnection::kLastMessageID, "Hello World", 12);
+ ABSOLUTE_TIME start = START_TIME;
+ while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond)
+ {
+ player->Poll();
+ editor->PollWithCustomMessage();
+ if (!message.empty()) break;
+ }
+ CHECK_EQUAL (std::string("Hello World"), message);
+
+ message = std::string("");
+ editor->SendMessage(clientConnectionGuid, GeneralConnection::kLastMessageID, "Are you there", 14);
+ start = START_TIME;
+ while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond)
+ {
+ player->Poll();
+ editor->PollWithCustomMessage();
+ if (!message.empty()) break;
+ }
+ CHECK_EQUAL (std::string("Are you there"), message);
+
+ }
+
+ TEST_FIXTURE(GeneralConnectionFixture, SendLargeMessage)
+ {
+ Connect();
+ editor->RegisterMessageHandler(GeneralConnection::kLastMessageID,HandleLargeMessage);
+
+ int msgSize = 400*1024;
+ int* buffer = new int[msgSize];
+ for(int i = 0; i < msgSize; i++)
+ buffer[i] = i;
+ largeMessage = NULL;
+ player->SendMessage(serverConnectionGuid, GeneralConnection::kLastMessageID, (char*)buffer, msgSize*sizeof(int));
+ ABSOLUTE_TIME start = START_TIME;
+ while (GetProfileTime(ELAPSED_TIME(start)) < 2*kTimeSecond)
+ {
+ player->Poll();
+ editor->PollWithCustomMessage();
+ if (largeMessage != NULL) break;
+ }
+ if (largeMessage != NULL)
+ {
+ CHECK_EQUAL (1, largeMessage[1]);
+ CHECK_EQUAL (1024, largeMessage[1024]);
+ CHECK_EQUAL (10000, largeMessage[10000]);
+ CHECK_EQUAL (msgSize -1, largeMessage[msgSize-1]);
+ }
+ else
+ CHECK(largeMessage != NULL);
+
+ delete[] largeMessage; largeMessage = NULL;
+ }
+
+ TEST_FIXTURE(GeneralConnectionFixture, SendMultipleMessage)
+ {
+ Connect();
+ editor->RegisterMessageHandler(GeneralConnection::kLastMessageID,HandleManyMessages);
+
+ char message[20];
+
+ for(int i = 0; i < 100; i++)
+ {
+ message[10] = (char)i;
+ player->SendMessage(serverConnectionGuid, GeneralConnection::kLastMessageID, message, 20);
+ }
+
+ ABSOLUTE_TIME start = START_TIME;
+ while (GetProfileTime(ELAPSED_TIME(start)) < 1*kTimeSecond)
+ {
+ player->Poll();
+ editor->PollWithCustomMessage();
+ if (messageCount == 100) break;
+ }
+ CHECK_EQUAL (100, messageCount);
+ }
+}
+
+#endif
+
+#endif // ENABLE_PLAYERCONNECTION
diff --git a/Runtime/Network/PlayerCommunicator/GeneralConnection.h b/Runtime/Network/PlayerCommunicator/GeneralConnection.h
new file mode 100644
index 0000000..1f253d8
--- /dev/null
+++ b/Runtime/Network/PlayerCommunicator/GeneralConnection.h
@@ -0,0 +1,266 @@
+#pragma once
+
+#if ENABLE_PLAYERCONNECTION
+#define ENABLE_MULTICAST (!UNITY_FLASH)
+
+#include "Runtime/Serialize/SwapEndianBytes.h"
+#include "Runtime/Network/MulticastSocket.h"
+#include "Runtime/Network/ServerSocket.h"
+#include "Runtime/Network/SocketStreams.h"
+
+
+#if UNITY_WIN
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#elif UNITY_XENON
+#include <xtl.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <errno.h>
+#endif
+#include "Runtime/Profiler/TimeHelper.h"
+#include "Runtime/Threads/Mutex.h"
+
+
+// used ports:
+// MulticastPort : 54998
+// ListenPorts : 55000 - 55511
+// Multicast(unittests) : 55512 - 56023
+
+#define ENABLE_PLAYER_CONNECTION_DEBUG_LOG 0
+
+#if ENABLE_PLAYER_CONNECTION_DEBUG_LOG
+#define LOG_PLAYER_CONNECTION(str) { \
+ bool oldlog = IsLogEnabled(); \
+ SetLogEnabled(false); \
+ printf_console ("[%04x] %s\n", Thread::GetCurrentThreadID(), (str).c_str()); \
+ SetLogEnabled(oldlog); \
+}
+#else
+#define LOG_PLAYER_CONNECTION(str)
+#endif
+
+#if SUPPORT_THREADS
+#define LOG_PLAYER_CONNECTION_CRITICAL(str) printf_console ("[%04x] %s\n", Thread::GetCurrentThreadID(), (str).c_str())
+#else
+#define LOG_PLAYER_CONNECTION_CRITICAL(str) printf_console ("[] %s\n", (str).c_str())
+#endif
+
+#define PLAYER_MULTICAST_GROUP "225.0.0.222"
+#define PLAYER_MULTICAST_PORT 54997
+
+#define PLAYER_DIRECTCONNECT_PORT 54999
+#define PLAYER_DIRECTCONNECT_GUID 1337
+
+#define PLAYER_DIRECT_IP_CONNECT_GUID 0xFEED
+
+#define PLAYER_LISTEN_PORT 55000
+#define PLAYER_UNITTEST_MULTICAST_PORT 55512
+#define PLAYER_PORT_MASK 511
+#define PLAYER_MESSAGE_MAGIC_NUMBER 0x4E8F
+
+#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_LISTEN "listen %u %d %d %d"
+#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_LISTEN_PRINT "listen <guid> <debugging> <waitonstartup> <startprofiler>"
+#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT_LIST "connect %s %s %s %s %s %s %s %s %s %s"
+#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT "connect %s"
+#define PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT_PRINT "connect <ip>"
+
+#define SERVER_IDENTIFICATION_FORMAT "[IP] %s [Port] %lu [Flags] %lu [Guid] %lu [EditorId] %lu [Version] %d [Id] %s [Debug] %d"
+
+
+#define ANY_PLAYERCONNECTION 0
+
+bool SocketCallSucceeded(int res);
+
+class GeneralConnection
+{
+public:
+ enum MulticastFlags
+ {
+ kRequestImmediateConnect = 1<<0,
+ kSupportsProfile = 1<<1,
+ kCustomMessage = 1<<2
+ };
+
+ enum { kMulticastBufferSize = 256 };
+ enum { kDataBufferMaxSize = 8*1024*1024 };
+ enum { kDataBufferFlushThreshold = 7*1024*1024 };
+
+#if ENABLE_MULTICAST
+ // Parsed struct from a buffer that contains all the information sent by the player to the editor
+ // Used for showing a list of all available profilers and performing autoconnect on the profiler
+ struct MulticastInfo
+ {
+ MulticastInfo();
+
+ bool Parse (const char* buffer, void* in = NULL);
+
+ bool IsValid () const { return m_Valid; }
+ bool IsLocalhost() const { return m_IsLocalhost; }
+
+ UInt32 GetGuid () const { return m_Guid; }
+ UInt32 GetEditorGuid () const { return m_EditorGuid; }
+
+ UInt32 GetPort () const { return m_Port; }
+ std::string const& GetIP () const { return m_Ip; }
+
+ bool ImmediateConnect() const { return (m_Flags & kRequestImmediateConnect) != 0; }
+ bool HasCustomMessage() const { return (m_Flags & kCustomMessage) != 0; }
+ bool HasProfiler() const { return (m_Flags & kSupportsProfile) != 0; }
+
+ std::string const& GetIdentifier() const { return m_Identifier; }
+ private:
+
+ void Clear ();
+ void SetLocalhostFlag();
+
+ std::string m_Buffer;
+ std::string m_Ip;
+ UInt32 m_Port;
+ UInt32 m_Flags;
+ UInt32 m_Guid;
+ UInt32 m_EditorGuid;
+ std::string m_Identifier;
+ bool m_Valid;
+ bool m_IsLocalhost;
+ };
+#endif
+
+ enum MessageID{
+ // messageID 1 - 31 reserved for (future) internal use
+ kProfileDataMessage = 32,
+ kProfileStartupInformation = 33,
+
+ kObjectMemoryProfileSnapshot = 40, //request the memory profile
+ kObjectMemoryProfileDataMessage = 41, //send the object memory profile
+
+ kLogMessage = 100,
+ kCleanLogMessage = 101,
+
+ kFileTransferMessage = 200,
+ kFileReadyMessage = 201,
+ kCaptureHeaphshotMessage = 202,
+
+ kPingAliveMessage = 300,
+ kApplicationQuitMessage = 301,
+ kLastMessageID,
+ };
+
+ struct NetworkMessage
+ {
+ void InitializeMessage();
+ bool CheckMessageValidity();
+ void SetID (MessageID id);
+ MessageID GetID ();
+ void SetDataSize (UInt32 size);
+ UInt32 GetDataSize ();
+
+ bool AllowSkip(){return m_ID == kProfileDataMessage;}
+
+ private:
+ UInt16 m_MagicNumber; // in little endian
+ UInt16 m_ID; // in little endian
+ UInt32 m_Size; // in little endian
+ };
+
+ struct Connection
+ {
+ public:
+ Connection(TSocketHandle socketHandle);
+ ~Connection();
+
+ bool IsValid() const { return m_SocketStream.IsConnected(); };
+ bool Poll() { return m_SocketStream.Poll(5); }
+ bool HasBytesToSend() const { return m_SocketStream.SendBuffer().GetAvailableSize() > 0; }
+ const void* ReceiveMessage(NetworkMessage* message);
+ void ReleaseReceivedMessage();
+ void SendMessage(NetworkMessage& message, const void* data);
+
+ UInt32 GetSendBufferSize() const { return m_SocketStream.SendBuffer().GetAllocatedSize(); }
+ UInt32 GetRecvBufferSize() const { return m_SocketStream.RecvBuffer().GetAllocatedSize(); }
+
+ private:
+ Mutex m_SendMutex;
+ Mutex m_RecvMutex;
+ NetworkMessage m_PendingMessage;
+ void* m_PendingMessageData;
+ bool m_LocallyAllocatedMessageData;
+ bool m_HasPendingMessage;
+#if UNITY_FLASH
+ BufferedSocketStream m_SocketStream;
+#elif UNITY_WINRT
+ BufferedSocketStream m_SocketStream;
+#else
+ ThreadedSocketStream m_SocketStream;
+#endif
+ };
+
+ virtual ~GeneralConnection ();
+
+ bool IsConnected () { return !m_Connections.empty(); }
+ void SetLogEnabled (bool v) { m_LogEnabled = v; }
+ bool IsLogEnabled () const { return m_LogEnabled; }
+ void DisconnectAll ();
+
+ void SendMessage (UInt32 guid, MessageID id, const void* data, UInt32 size);
+
+ typedef void (*MessageHandlerFunc) (const void* data, UInt32 size, UInt32 guid);
+ void RegisterMessageHandler (MessageID messageID, MessageHandlerFunc func);
+ void UnregisterMessageHandler (MessageID messageID, MessageHandlerFunc func);
+
+ typedef void (*ConnectionHandlerFunc) (UInt32 guid);
+ void RegisterDisconnectionHandler( ConnectionHandlerFunc func );
+ void RegisterConnectionHandler( ConnectionHandlerFunc func );
+ void UnregisterDisconnectionHandler( ConnectionHandlerFunc func );
+ void UnregisterConnectionHandler( ConnectionHandlerFunc func );
+
+ virtual void WaitForFinish();
+
+ UInt32 GetLocalGuid() const;
+
+ static void RunningUnitTest() { ms_RunningUnitTests = true;}
+
+protected:
+ GeneralConnection ();
+
+ bool HasBytesToSend() const;
+
+ static void Initialize ();
+ static void Cleanup ();
+
+ void RegisterConnection (UInt32 guid, TSocketHandle socketHandle);
+ void Disconnect (UInt32 guid);
+
+ Connection* GetConnection(UInt32 guid);
+
+ virtual bool IsServer () = 0;
+
+ void Poll ();
+
+protected:
+ std::string m_LocalIP;
+
+#if ENABLE_MULTICAST
+ MulticastSocket m_MulticastSocket;
+#endif
+
+ typedef std::map< int, Connection* > ConnectionMap;
+ ConnectionMap m_Connections;
+
+ std::map< MessageID, MessageHandlerFunc > m_HandlerMap;
+ std::vector< ConnectionHandlerFunc > m_ConnectionHandlers;
+ std::vector< ConnectionHandlerFunc > m_DisconnectionHandlers;
+
+ UInt32 m_LocalGuid;
+
+ bool m_LogEnabled;
+
+ static bool ms_RunningUnitTests;
+ static int ms_Version;
+};
+
+#endif // ENABLE_PLAYERCONNECTION
diff --git a/Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h b/Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h
new file mode 100644
index 0000000..15f5714
--- /dev/null
+++ b/Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "Runtime/Threads/AtomicOps.h"
+#include "Runtime/Network/SocketConsts.h"
+#if UNITY_WIN
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#elif UNITY_XENON
+#include <xtl.h>
+typedef int socklen_t;
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <errno.h>
+#endif
+#if UNITY_PS3
+#include <sys/time.h>
+#include <netex/net.h>
+#include <netex/errno.h>
+#define SOCK_ERROR(s) ((s) < 0)
+#elif UNITY_WINRT
+#define SOCK_ERROR(s) ((s) == nullptr)
+#else
+#define SOCK_ERROR(s) ((s) == -1)
+#endif
+
+
+static const ProfileTimeFormat kTimeMillisecond = 1000000ULL;
+static const ProfileTimeFormat kTimeSecond = 1000000000ULL;
+static const ProfileTimeFormat kPlayerConnectionInitialWaitTimeout = 5*kTimeSecond;
+
+#if UNITY_FLASH
+extern "C" void Ext_GetSocketPolicyFile(const char* ipString);
+extern int g_AlchemySocketErrno;
+extern "C" int flash_socket(int domain, int type, int protocol);
+extern "C" int flash_connect(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
+extern "C" int flash_close(int sockfd);
+extern "C" ssize_t flash_recvfrom (int sockfd, void* buffer, size_t length, int flags, struct sockaddr* address, socklen_t* address_len);
+extern "C" ssize_t flash_sendto(int, const void *, size_t, int, const struct sockaddr*, socklen_t);
+extern "C" int flash_setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t option_len);
+#define setsockopt flash_setsockopt
+#define recvfrom flash_recvfrom
+#define sendto flash_sendto
+#define socket flash_socket
+#define connect flash_connect
+#define close flash_close
+#endif
+
+static UInt32 NextGUID()
+{
+ static volatile int guid_counter = 0;
+ return (UInt32) AtomicIncrement(&guid_counter);
+}
diff --git a/Runtime/Network/PlayerCommunicator/PlayerConnection.cpp b/Runtime/Network/PlayerCommunicator/PlayerConnection.cpp
new file mode 100644
index 0000000..04beece
--- /dev/null
+++ b/Runtime/Network/PlayerCommunicator/PlayerConnection.cpp
@@ -0,0 +1,535 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_PLAYERCONNECTION
+
+#include "PlayerConnection.h"
+#include "Runtime/Misc/SystemInfo.h"
+
+#include "Runtime/Serialize/FileCache.h"
+#include "Runtime/Serialize/CacheWrap.h"
+
+#include "Runtime/Network/NetworkUtility.h"
+#include "Runtime/Profiler/TimeHelper.h"
+#include "Runtime/Math/Random/rand.h"
+#include "Runtime/Utilities/PathNameUtility.h"
+#include "Runtime/Utilities/PlayerPrefs.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Network/PlayerCommunicator/GeneralConnectionInternals.h"
+#include "Runtime/Network/SocketConsts.h"
+
+#if UNITY_ANDROID
+#include "PlatformDependent/AndroidPlayer/EntryPoint.h"
+#include "PlatformDependent/AndroidPlayer/DVMCalls.h"
+#endif
+
+#define ALL_INTERFACES_IP "0.0.0.0"
+
+const char* kPlayerConnectionConfigFile = "PlayerConnectionConfigFile";
+
+PlayerConnection* PlayerConnection::ms_Instance = NULL;
+
+PlayerConnection::PlayerConnection(const std::string& dataPath, unsigned short multicastPort, bool enableDebugging)
+: GeneralConnection()
+, m_WaitingForPlayerConnectionBeforeStartingPlayback(false)
+#if ENABLE_LISTEN_SOCKET
+ , m_ListenSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
+#endif
+#if UNITY_ANDROID
+, m_UnixSocket(AF_LOCAL, SOCK_STREAM, 0)
+#endif
+{
+ ABSOLUTE_TIME_INIT(m_LastMulticast);
+
+ m_IsPlayerConnectionEnabled = false;
+
+ bool hasConfigFile = ReadConfigFile(dataPath);
+ if (!hasConfigFile)
+ m_AllowDebugging = (enableDebugging? 1: 0);
+
+ if (!PLATFORM_SUPPORTS_PLAYERCONNECTION_LISTENING && !hasConfigFile)
+ {
+ printf_console("PlayerConnection disabled - listening mode not supported\n");
+ return;
+ }
+
+ m_IsPlayerConnectionEnabled = true;
+
+ if (m_InitiateMode == kPlayerConnectionInitiateByConnecting)
+ {
+ for (int i = 0; i < m_NumIPs; ++i)
+ {
+ m_ConnectToIP = m_ConnectToIPList[i];
+#if UNITY_FLASH
+ Ext_GetSocketPolicyFile(m_ConnectToIP.c_str());
+#endif
+ printf_console("Connecting directly to [%s]...\n", m_ConnectToIP.c_str());
+ // Try to connect to next IP
+ Poll();
+ if (IsConnected())
+ {
+ break;
+ }
+ }
+
+ if (!IsConnected())
+ {
+ ErrorString("Connecting to host failed, aborting playback");
+#if UNITY_XENON
+ XLaunchNewImage(XLAUNCH_KEYWORD_DEFAULT_APP, 0);
+#elif !UNITY_FLASH // Cant handle exit
+ exit(1);
+#endif
+ }
+
+ return;
+ }
+
+ // so we are in listening mode.
+
+ CreateListenSocket ();
+#if UNITY_ANDROID
+ CreateUnixSocket();
+#endif
+ m_HostName = GetHostName ();
+ std::replace (m_HostName.begin (), m_HostName.end (), ' ', '_');
+ m_WhoAmI = ConstructWhoamiString ();
+ InitializeMulticastAddress (multicastPort);
+
+ if (m_WaitingForPlayerConnectionBeforeStartingPlayback)
+ {
+ ABSOLUTE_TIME startTime = START_TIME;
+ printf_console("Waiting for connection from host on [%s:%i]...\n", m_LocalIP.c_str(), (int)m_ListenPort);
+
+ // Try to connect for some time
+ while((GetProfileTime(ELAPSED_TIME(startTime)) < kPlayerConnectionInitialWaitTimeout) && (!IsConnected()))
+ {
+ Poll();
+ Thread::Sleep(0.05);
+ }
+ }
+
+ if (!IsConnected() && m_WaitingForPlayerConnectionBeforeStartingPlayback)
+ printf_console("Timed out. Continuing without host connection.\n");
+}
+
+std::string PlayerConnection::ConstructWhoamiString ()
+{
+ std::string runtimeAndHostName = Format ("%s(%s)",
+ systeminfo::GetRuntimePlatformString ().c_str (),
+ m_HostName.c_str ());
+ UInt32 flags = (ImmediateConnect () ? kRequestImmediateConnect : 0);
+ flags |= (ENABLE_PROFILER ? kSupportsProfile : 0);
+ std::string whoAmI = Format (SERVER_IDENTIFICATION_FORMAT,
+ m_LocalIP.c_str (), (UInt32)m_ListenPort,
+ flags, m_LocalGuid,
+ m_EditorGuid, ms_Version,
+ runtimeAndHostName.c_str (),
+ m_AllowDebugging);
+ return whoAmI;
+}
+
+void PlayerConnection::InitializeMulticastAddress (UInt16 multicastPort)
+{
+ Assert (m_InitiateMode == kPlayerConnectionInitiateByListening);
+
+#if !UNITY_FLASH
+ // We use broadcast in case of XBOX360 or AdHoc connection
+ // For AdHoc connections we need to specify Auto IP broadcast address instead of 255.255.255.255
+ if (UNITY_XENON || m_LocalIP.find("169.254") == 0)
+ {
+ const char* broadcastAddress = (UNITY_XENON || UNITY_BB10) ? "255.255.255.255" : "169.254.255.255";
+ if (!m_MulticastSocket.Initialize(broadcastAddress, multicastPort))
+ ErrorString("Unable to setup multicast socket for player connection.");
+ if (!m_MulticastSocket.SetBroadcast(true))
+ ErrorString("Unable to set broadcast mode for player connection socket.");
+ printf_console("Broadcasting \"%s\" to [%s:%i]...\n", m_WhoAmI.c_str(), broadcastAddress, (int)multicastPort);
+ }
+ // For all other cases we use multicast address
+ else
+ {
+ if (!m_MulticastSocket.Initialize(PLAYER_MULTICAST_GROUP, multicastPort))
+ ErrorString("Unable to setup multicast socket for player connection.");
+ printf_console("Multi-casting \"%s\" to [%s:%i]...\n", m_WhoAmI.c_str(), PLAYER_MULTICAST_GROUP, (int)multicastPort);
+ #if UNITY_EDITOR
+ m_MulticastSocket.SetTTL(ms_RunningUnitTests ? 0 : 31);
+ #else
+ m_MulticastSocket.SetTTL(31);
+ #endif
+ m_MulticastSocket.SetLoop(true);
+ }
+
+
+#endif
+}
+
+
+void PlayerConnection::CreateListenSocket ()
+{
+ Assert (m_InitiateMode == kPlayerConnectionInitiateByListening);
+
+ // create a random listen port (will be send out with the multicast ping)
+ Rand r (GetProfileTime(START_TIME));
+ m_ListenPort = PLAYER_LISTEN_PORT + (r.Get() & PLAYER_PORT_MASK);
+
+#if ENABLE_LISTEN_SOCKET
+ InitializeListenSocket(m_ListenSocket, ALL_INTERFACES_IP, m_ListenPort);
+#endif
+}
+
+#if ENABLE_LISTEN_SOCKET
+void PlayerConnection::InitializeListenSocket(ServerSocket& socket, const std::string& localIP, int listenPort)
+{
+ printf_console("PlayerConnection initialized network socket : %s %i\n", localIP.c_str(), listenPort);
+ socket.StartListening(localIP.c_str(), listenPort, false);
+}
+#endif
+
+#if UNITY_ANDROID
+void PlayerConnection::CreateUnixSocket ()
+{
+ Assert (m_InitiateMode == kPlayerConnectionInitiateByListening);
+ InitializeUnixSocket (m_UnixSocket, Format("Unity-%s", DVM::GetPackageName()));
+}
+#endif
+
+#if UNITY_ANDROID
+void PlayerConnection::InitializeUnixSocket (ServerSocket& socket, const std::string& name)
+{
+ printf_console("PlayerConnection initialized unix socket : %s\n", name.c_str());
+ size_t len = name.length();
+
+ struct sockaddr_un address;
+ Assert (len < sizeof(address.sun_path));
+ memset(&address, 0, sizeof(sockaddr_un));
+ memcpy(address.sun_path + 1, name.data(), len);
+ address.sun_path[0] = 0;
+ address.sun_family = AF_LOCAL;
+
+ socklen_t address_len = offsetof(struct sockaddr_un, sun_path) + len + 1;
+ socket.StartListening((const sockaddr *) &address, address_len, false);
+}
+#endif
+
+bool PlayerConnection::ReadConfigFile (const std::string& dataPath)
+{
+ m_InitiateMode = kPlayerConnectionInitiateByListening;
+ m_EditorGuid = -1;
+ m_AllowDebugging = 0;
+ m_EnableProfiler = 0;
+ m_WaitingForPlayerConnectionBeforeStartingPlayback = 0;
+ int tmpWaiting = 0;
+
+ std::string configFile = AppendPathName(dataPath, kPlayerConnectionConfigFile);
+ if (!IsFileCreated(configFile))
+ return false;
+
+ InputString confData;
+ ReadStringFromFile(&confData, configFile);
+ char tmp[100];
+
+ if (sscanf(confData.c_str(), PLAYER_CONNECTION_CONFIG_DATA_FORMAT_LISTEN, (unsigned*)&m_EditorGuid, &m_AllowDebugging, &tmpWaiting, &m_EnableProfiler) == 4)
+ {
+ m_WaitingForPlayerConnectionBeforeStartingPlayback = tmpWaiting;
+ m_InitiateMode = kPlayerConnectionInitiateByListening;
+ return true;
+ }
+
+ m_NumIPs = sscanf(confData.c_str(), PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT_LIST, m_ConnectToIPList[0], m_ConnectToIPList[1], m_ConnectToIPList[2],
+ m_ConnectToIPList[3], m_ConnectToIPList[5], m_ConnectToIPList[6], m_ConnectToIPList[7], m_ConnectToIPList[8], m_ConnectToIPList[9]);
+
+ if (m_NumIPs > 0)
+ {
+ m_InitiateMode = kPlayerConnectionInitiateByConnecting;
+ return true;
+ }
+
+ ErrorString(Format("PlayerConnection config should be in the format: \"%s\" or \"%s\"", PLAYER_CONNECTION_CONFIG_DATA_FORMAT_LISTEN_PRINT, PLAYER_CONNECTION_CONFIG_DATA_FORMAT_CONNECT_PRINT));
+ return false;
+}
+
+PlayerConnection& PlayerConnection::Get()
+{
+ return *ms_Instance;
+}
+
+void PlayerConnection::Initialize (const std::string& dataPath, bool enableDebugging)
+{
+ if (ms_Instance == NULL)
+ {
+ SET_ALLOC_OWNER(NULL);
+ printf_console("PlayerConnection initialized from %s (debug = %i)\n", dataPath.c_str(), enableDebugging);
+ GeneralConnection::Initialize();
+ ms_Instance = new PlayerConnection(dataPath, PLAYER_MULTICAST_PORT, enableDebugging);
+ }
+ else
+ {
+ if (ms_Instance->m_IsPlayerConnectionEnabled)
+ {
+ switch (ms_Instance->m_InitiateMode)
+ {
+ case kPlayerConnectionInitiateByListening:
+ printf_console("PlayerConnection already initialized - listening to [%s:%i]\n", ms_Instance->m_LocalIP.c_str(), (int)ms_Instance->m_ListenPort);
+ break;
+ case kPlayerConnectionInitiateByConnecting:
+ printf_console("PlayerConnection already initialized - connecting to [%s:%i]\n", ms_Instance->m_ConnectToIP.c_str(), PLAYER_DIRECTCONNECT_PORT);
+ break;
+ default:
+ printf_console("PlayerConnection already initialized - unknown mode\n");
+ break;
+ }
+ }
+ else
+ {
+ printf_console("PlayerConnection already initialized, but disabled\n");
+ }
+ }
+}
+
+void PlayerConnection::Cleanup ()
+{
+ Assert(ms_Instance != NULL);
+ delete ms_Instance;
+ ms_Instance = NULL;
+ GeneralConnection::Cleanup();
+}
+
+void PlayerConnection::PollListenMode()
+{
+ Assert (m_InitiateMode == kPlayerConnectionInitiateByListening);
+#if ENABLE_LISTEN_SOCKET
+ if(!m_IsPlayerConnectionEnabled )
+ return;
+
+ if (!IsConnected() || GetProfileTime(ELAPSED_TIME(m_LastMulticast)) > 1*kTimeSecond)
+ {
+ TSocketHandle socketHandle;
+#if UNITY_WINRT
+ if(m_ListenSocket.IsListening() && !SOCK_ERROR(socketHandle = m_ListenSocket.Accept()))
+ {
+ printf_console("PlayerConnection accepted from WinRT socket\n");
+ CreateAndReportConnection(socketHandle);
+ }
+#endif
+#if UNITY_ANDROID
+ if (m_UnixSocket.IsListening() && !SOCK_ERROR(socketHandle = m_UnixSocket.Accept()))
+ {
+ printf_console("PlayerConnection accepted from unix socket\n");
+ CreateAndReportConnection(socketHandle);
+ }
+#endif
+#if !UNITY_WINRT
+ // player looking for connections
+ struct sockaddr_in remoteAddr;
+ socklen_t remoteAddrLen = sizeof(remoteAddr);
+ if(m_ListenSocket.IsListening() && !SOCK_ERROR(socketHandle = m_ListenSocket.Accept((sockaddr*)&remoteAddr, &remoteAddrLen)))
+ {
+ printf_console("PlayerConnection accepted from [%s]\n", InAddrToIP(&remoteAddr).c_str());
+ CreateAndReportConnection(socketHandle);
+ }
+#endif
+
+ // broadcast ip and port with 1 sec interval
+ // 10ms interval if immediate connect is set
+ UInt64 interval = 1*kTimeSecond;
+ if (!IsConnected() && ImmediateConnect())
+ interval = 10*kTimeMillisecond;
+
+ if (GetProfileTime(ELAPSED_TIME(m_LastMulticast)) > interval)
+ {
+ m_LastMulticast = START_TIME;
+ m_MulticastSocket.Send(m_WhoAmI.c_str (), m_WhoAmI.length () + 1);
+ }
+ }
+#endif
+}
+
+void PlayerConnection::CreateAndReportConnection(TSocketHandle socketHandle)
+{
+ RegisterConnection(NextGUID(), socketHandle);
+}
+
+void PlayerConnection::PollConnectMode()
+{
+ Assert (m_InitiateMode == kPlayerConnectionInitiateByConnecting);
+
+ if(!m_IsPlayerConnectionEnabled )
+ return;
+
+ if (IsConnected())
+ return;
+
+ int port = PLAYER_DIRECTCONNECT_PORT;
+ TSocketHandle socketHandle;
+ if (SOCK_ERROR(socketHandle = ::Socket::Connect(m_ConnectToIP.c_str(), port)))
+ {
+ ErrorStringMsg("Connect failed for direct socket. Ip=%s, port=%d", m_ConnectToIP.c_str(), port);
+ return;
+ }
+ CreateAndReportConnection(socketHandle);
+}
+
+
+void PlayerConnection::Poll()
+{
+ GeneralConnection::Poll();
+
+ switch(m_InitiateMode)
+ {
+ case kPlayerConnectionInitiateByListening:
+ PollListenMode ();
+ break;
+ case kPlayerConnectionInitiateByConnecting:
+ PollConnectMode ();
+ break;
+ }
+}
+
+static int custom_asprintf(char** buffer, const char* log, va_list alist)
+{
+ va_list list, sizelist;
+ va_copy (list, alist);
+ va_copy (sizelist, alist);
+ int result = 0;
+
+#if USE_WINSOCK_APIS || UNITY_WINRT
+ int bufferSize = _vscprintf(log, list) + 1;
+ *buffer = (char *)UNITY_MALLOC_ALIGNED(kMemUtility, bufferSize, 4);
+ result = vsnprintf(*buffer, bufferSize, log, list);
+#elif UNITY_PS3 || defined(__GNUC__)
+ int bufferSize = vsnprintf(0, 0, log, sizelist) + 1;
+ *buffer = (char *)UNITY_MALLOC_ALIGNED(kMemUtility, bufferSize, 4);;
+ result = vsnprintf(*buffer, bufferSize, log, list);
+#else
+#error "Not implemented"
+#endif
+
+ va_end (sizelist);
+ va_end (list);
+ return result;
+}
+
+void LogToPlayerConnectionMessage(LogType logType, PlayerConnection::MessageID msgId, const char* log, va_list alist)
+{
+ va_list list;
+ va_copy (list, alist);
+
+ PlayerConnection& pc = PlayerConnection::Get();
+ if (pc.IsConnected() && pc.IsLogEnabled())
+ {
+
+ // don't try to recursively sent logs from inside player connection over player connection
+ pc.SetLogEnabled (false);
+
+ char* buffer = NULL;
+ int len = custom_asprintf(&buffer, log, list);
+
+ if (len >= 0 && buffer && buffer[0] != 0)
+ PlayerConnection::Get().SendMessage(ANY_PLAYERCONNECTION, msgId, buffer, len);
+
+ if (buffer)
+ UNITY_FREE (kMemUtility, buffer);
+
+ pc.SetLogEnabled (true);
+ }
+ va_end (list);
+}
+
+bool PlainLogToPlayerConnection (LogType logType, const char* log, va_list alist)
+{
+ va_list list;
+ va_copy (list, alist);
+ LogToPlayerConnectionMessage(logType, PlayerConnection::kLogMessage, log, list);
+ va_end (list);
+ return true;
+}
+
+bool CleanLogToPlayerConnection (LogType logType, const char* log, va_list alist)
+{
+ va_list list;
+ va_copy (list, alist);
+ LogToPlayerConnectionMessage(logType, PlayerConnection::kCleanLogMessage, log, list);
+ va_end (list);
+ return true;
+}
+
+void InstallPlayerConnectionLogging (bool install)
+{
+ if (install)
+ {
+ SetLogEntryHandler(&PlainLogToPlayerConnection);
+ AddCleanLogEntryHandler(&CleanLogToPlayerConnection);
+ }
+ else
+ {
+ SetLogEntryHandler(NULL);
+ }
+}
+
+void TransferFileOverPlayerConnection(const std::string& fname, void* body, unsigned int length, void* header, unsigned int headerLength)
+{
+#if !UNITY_EDITOR
+ printf_console("about to send file over playerconnection %s with length %d\n",fname.c_str(),length);
+
+ dynamic_array<UInt8> buffer;
+
+ MemoryCacheWriter memoryCache (buffer);
+ CachedWriter writeCache;
+
+ unsigned int fnameLength = fname.length();
+
+ unsigned int fnameLengthLE = fnameLength;
+ unsigned int lengthLE = length + headerLength;
+ SwapEndianBytesNativeToLittle(fnameLengthLE);
+ SwapEndianBytesNativeToLittle(lengthLE);
+
+ writeCache.InitWrite (memoryCache);
+ writeCache.Write(&fnameLengthLE, sizeof(fnameLengthLE));
+ writeCache.Write((void*)fname.c_str(), fnameLength);
+ writeCache.Write(&lengthLE, sizeof(lengthLE));
+ if (headerLength > 0)
+ writeCache.Write(header, headerLength);
+ writeCache.Write(body, length);
+
+ writeCache.CompleteWriting();
+
+ PlayerConnection::Get().SendMessage(ANY_PLAYERCONNECTION, GeneralConnection::kFileTransferMessage, &buffer[0], buffer.size());
+
+ // ugly hack to fix gfx tests
+ PlayerConnection& playercnx = PlayerConnection::Get();
+ while (playercnx.IsTestrigMode())
+ {
+ playercnx.Poll();
+ if (!playercnx.HasBytesToSend())
+ break;
+
+ Thread::Sleep(0.005);
+ }
+
+#endif //!UNITY_EDITOR
+}
+
+void NotifyFileReadyOverPlayerConnection(const std::string& fname)
+{
+#if !UNITY_EDITOR
+ dynamic_array<UInt8> buffer;
+
+ MemoryCacheWriter memoryCache (buffer);
+ CachedWriter writeCache;
+
+ unsigned int fnameLength = fname.length();
+ unsigned int fnameLengthLE = fnameLength;
+ SwapEndianBytesNativeToLittle(fnameLengthLE);
+
+ writeCache.InitWrite (memoryCache);
+ writeCache.Write(&fnameLengthLE, sizeof(fnameLengthLE));
+ writeCache.Write((void*)fname.c_str(), fnameLength);
+
+ writeCache.CompleteWriting();
+
+ PlayerConnection::Get().SendMessage(ANY_PLAYERCONNECTION, GeneralConnection::kFileReadyMessage, &buffer[0], buffer.size());
+#endif //!UNITY_EDITOR
+}
+
+
+#endif // ENABLE_PLAYERCONNECTION
diff --git a/Runtime/Network/PlayerCommunicator/PlayerConnection.h b/Runtime/Network/PlayerCommunicator/PlayerConnection.h
new file mode 100644
index 0000000..07e2159
--- /dev/null
+++ b/Runtime/Network/PlayerCommunicator/PlayerConnection.h
@@ -0,0 +1,111 @@
+#pragma once
+
+#if ENABLE_PLAYERCONNECTION
+#define ENABLE_LISTEN_SOCKET (!UNITY_FLASH)
+
+#include "GeneralConnection.h"
+#include "Runtime/Serialize/SwapEndianBytes.h"
+
+
+#if UNITY_WIN
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#elif UNITY_XENON
+#include <xtl.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <errno.h>
+#endif
+#include "Runtime/Profiler/TimeHelper.h"
+#include "Runtime/Threads/Mutex.h"
+#if UNITY_ANDROID
+#include <sys/un.h>
+#endif
+
+// flags are bits denoting capabilities of the broadcaster
+enum PlayerConnectionInitiateMode
+ {
+ kPlayerConnectionInitiateByListening,
+ kPlayerConnectionInitiateByConnecting
+ };
+
+class PlayerConnection : public GeneralConnection
+{
+public:
+ PlayerConnection (const std::string& dataPath = "", unsigned short multicastPort = PLAYER_MULTICAST_PORT, bool enableDebugging=false);
+
+ static void Initialize (const std::string& dataPath, bool enableDebugging=false);
+ static void Cleanup ();
+
+ // Singleton accessor for playerconnection
+ static PlayerConnection& Get ();
+ static PlayerConnection* ms_Instance;
+
+ void Poll ();
+ inline bool AllowDebugging () { return (0 != m_AllowDebugging); }
+ bool ShouldEnableProfiler() { return m_EnableProfiler != 0; }
+
+ // ugly hack to fix gfx tests
+ inline bool IsTestrigMode() { return (m_InitiateMode == kPlayerConnectionInitiateByConnecting); }
+ inline bool HasBytesToSend() { return GeneralConnection::HasBytesToSend(); }
+
+private:
+ virtual bool IsServer() { return true; }
+ bool ReadConfigFile (const std::string& dataPath);
+ void CreateListenSocket ();
+ void CreateUnixSocket();
+ void InitializeMulticastAddress (UInt16 multicastPort);
+ void PollListenMode ();
+ void PollConnectMode ();
+ void CreateAndReportConnection(TSocketHandle socketHandle);
+ std::string ConstructWhoamiString ();
+
+ bool ImmediateConnect () const
+ {
+ return ms_RunningUnitTests
+ || m_WaitingForPlayerConnectionBeforeStartingPlayback;
+ }
+
+#if ENABLE_LISTEN_SOCKET
+ static void InitializeListenSocket(ServerSocket& socket, const std::string& localIP, int listenPort);
+#endif
+#if UNITY_ANDROID
+ static void InitializeUnixSocket(ServerSocket& socket, const std::string& socketname);
+#endif
+
+
+private:
+ bool m_IsPlayerConnectionEnabled;
+ PlayerConnectionInitiateMode m_InitiateMode;
+ bool m_WaitingForPlayerConnectionBeforeStartingPlayback;
+
+ // player specific
+ unsigned short m_ListenPort;
+ std::string m_HostName;
+ std::string m_WhoAmI;
+#if ENABLE_LISTEN_SOCKET
+ ServerSocket m_ListenSocket; // player only
+#endif
+#if UNITY_ANDROID
+ ServerSocket m_UnixSocket; // local
+#endif
+
+ UInt32 m_EditorGuid;
+ int m_AllowDebugging;
+ int m_EnableProfiler;
+ int m_NumIPs;
+ std::string m_ConnectToIP;
+ char m_ConnectToIPList[10][16];
+
+ ABSOLUTE_TIME m_LastMulticast;
+};
+
+void InstallPlayerConnectionLogging (bool install);
+void TransferFileOverPlayerConnection(const std::string& fname, void* body, unsigned int length, void* header = 0, unsigned int headerLength = 0);
+void NotifyFileReadyOverPlayerConnection(const std::string& fname);
+
+#endif // ENABLE_PLAYERCONNECTION
diff --git a/Runtime/Network/ServerSocket.cpp b/Runtime/Network/ServerSocket.cpp
new file mode 100644
index 0000000..a89782f
--- /dev/null
+++ b/Runtime/Network/ServerSocket.cpp
@@ -0,0 +1,117 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_SOCKETS
+#include "ServerSocket.h"
+#include "SocketUtils.h"
+
+ServerSocket::ServerSocket(int domain, int type, int protocol)
+: Socket(domain, type, protocol)
+, m_IsListening(false)
+{
+ SetReuseAddress(true);
+}
+
+#if !UNITY_WINRT
+
+// Metro does not support BSD sockets, so these methods are reimplemented
+// in Metro platform specific codebase
+
+bool ServerSocket::StartListening(unsigned short port, bool block)
+{
+ struct sockaddr_in addr;
+ SetupAddress(htonl(INADDR_ANY), htons(port), &addr);
+ return StartListening((const sockaddr*) &addr, sizeof(addr), block);
+}
+
+bool ServerSocket::StartListening(const char* ip, unsigned short port, bool block)
+{
+ struct sockaddr_in addr;
+ SetupAddress(inet_addr(ip), htons(port), &addr);
+ return StartListening((const sockaddr*) &addr, sizeof(addr), block);
+}
+
+bool ServerSocket::StartListening(const sockaddr* addr, socklen_t addr_len, bool block)
+{
+ if (!m_IsListening)
+ {
+ if (!SetBlocking(block))
+ return false;
+
+ if (CheckError(bind(m_SocketHandle, addr, addr_len), "bind failed"))
+ return false;
+
+ if (CheckError(listen(m_SocketHandle, 5), "listen failed"))
+ return false;
+
+ m_IsListening = true;
+ return true;
+ }
+ ErrorStringMsg("already listening");
+ return false;
+}
+
+int ServerSocket::GetPort()
+{
+ sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ if (CheckError(getsockname(m_SocketHandle, (struct sockaddr *) &addr, &addr_len)))
+ return -1;
+ return ntohs(addr.sin_port);
+}
+
+TSocketHandle ServerSocket::Accept()
+{
+ return Accept(NULL, NULL);
+}
+
+TSocketHandle ServerSocket::Accept(sockaddr* addr, socklen_t* addr_len)
+{
+ int socketHandle = accept(m_SocketHandle, addr, addr_len);
+ if (CheckError(socketHandle, "accept failed", kPlatformAcceptWouldBlock))
+ return socketHandle; // Shutdown?
+ return socketHandle;
+}
+
+
+#undef Error
+#undef SocketError
+
+// ---------------------------------------------------------------------------
+#if ENABLE_UNIT_TESTS && !UNITY_XENON
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "NetworkUtility.h"
+SUITE (ServerSocketTests)
+{
+ struct SocketFixture
+ {
+ SocketFixture()
+ {
+ NetworkInitialize();
+ };
+
+ ~SocketFixture()
+ {
+ NetworkCleanup();
+ }
+ };
+
+ TEST_FIXTURE(SocketFixture, ServerSocket_Connect)
+ {
+ int socketHandle, port;
+
+ ServerSocket socket;
+ CHECK((socket.StartListening("127.0.0.1", 0, false)) == true);
+ CHECK((port = socket.GetPort()) > 0);
+ CHECK((socketHandle = Socket::Connect("127.0.0.1", port)) >= 0);
+
+ Socket::Close(socketHandle);
+ CHECK(socket.IsListening());
+ }
+}
+
+#endif //ENABLE_UNIT_TESTS
+
+#endif // UNITY_WINRT
+
+#endif // ENABLE_SOCKETS
diff --git a/Runtime/Network/ServerSocket.h b/Runtime/Network/ServerSocket.h
new file mode 100644
index 0000000..89fd375
--- /dev/null
+++ b/Runtime/Network/ServerSocket.h
@@ -0,0 +1,31 @@
+#ifndef SERVERSOCKET_H
+#define SERVERSOCKET_H
+
+#if ENABLE_SOCKETS
+#include "Sockets.h"
+#include "SocketConsts.h"
+
+class ServerSocket : protected Socket
+{
+public:
+ ServerSocket(int domain = AF_INET, int type = SOCK_STREAM, int protocol = IPPROTO_TCP);
+
+ bool StartListening(unsigned short port, bool block);
+ bool StartListening(const char* ip, unsigned short port, bool block);
+#if !UNITY_WINRT
+ bool StartListening(const sockaddr* addr, socklen_t addr_len, bool block);
+#endif
+
+ int GetPort();
+ bool IsListening() const { return m_IsListening; }
+
+ TSocketHandle Accept();
+#if !UNITY_WINRT
+ TSocketHandle Accept(sockaddr* addr, socklen_t* addr_len);
+#endif
+private:
+ bool m_IsListening;
+};
+
+#endif // ENABLE_SOCKETS
+#endif // SERVERSOCKET_H
diff --git a/Runtime/Network/SocketConsts.h b/Runtime/Network/SocketConsts.h
new file mode 100644
index 0000000..d2b2691
--- /dev/null
+++ b/Runtime/Network/SocketConsts.h
@@ -0,0 +1,89 @@
+#ifndef SOCKETCONSTS_H
+#define SOCKETCONSTS_H
+
+#if UNITY_WINRT
+
+ #include <winsock2.h>
+
+#if UNITY_METRO
+ // WP8's winsock2.h is newer with full socket support.
+ // TEMP use local defines for Metro, until Microsoft updates Metro API.
+ #define AF_INET 2
+ #define SOCK_STREAM 1
+ #define IPPROTO_TCP 6
+ #define IPPROTO_UDP 17
+ #define SOCK_DGRAM 2
+ #define SOL_SOCKET 0xffff
+ #define SO_BROADCAST 0x0020
+ #define IPPROTO_IP 0
+ #define IP_MULTICAST_TTL 10
+ typedef USHORT ADDRESS_FAMILY;
+ typedef struct sockaddr {
+ ADDRESS_FAMILY sa_family;
+ CHAR sa_data[14];
+ } SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;
+ typedef struct sockaddr_in {
+ ADDRESS_FAMILY sin_family;
+ USHORT sin_port;
+ char sin_addr[32];
+ CHAR sin_zero[8];
+ } SOCKADDR_IN, *PSOCKADDR_IN;
+
+#endif
+
+ struct SocketWrapper;
+ typedef SocketWrapper* TSocketHandle;
+ typedef int socklen_t;
+
+// typedef void SendUserData;
+// typedef void RecvUserData;
+
+#else
+
+ #if UNITY_WIN
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+ #elif UNITY_XENON
+ #include <xtl.h>
+ typedef int socklen_t;
+ #else
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <sys/select.h>
+ #include <netinet/in.h>
+ #include <arpa/inet.h>
+ #include <fcntl.h>
+ #include <errno.h>
+ #include <netdb.h>
+ #endif
+ #if UNITY_PS3
+ #include <sys/time.h>
+ #include <netex/net.h>
+ #include <netex/errno.h>
+ #endif
+
+
+ typedef int TSocketHandle;
+#endif
+
+struct SendUserData
+{
+ int flags;
+ struct sockaddr* dstAddr;
+ socklen_t dstLen;
+ SendUserData() : flags(0) {}
+};
+struct RecvUserData
+{
+ int flags;
+ struct sockaddr* srcAddr;
+ socklen_t srcLen;
+ RecvUserData() : flags(0) {}
+};
+
+enum { kDefaultBufferSize = 16*1024 };
+enum { kDefaultPollTime = 1 };
+
+#define USE_WINSOCK_APIS ((UNITY_WIN && !UNITY_WINRT) || UNITY_XENON)
+
+#endif
diff --git a/Runtime/Network/SocketStreams.cpp b/Runtime/Network/SocketStreams.cpp
new file mode 100644
index 0000000..83abf16
--- /dev/null
+++ b/Runtime/Network/SocketStreams.cpp
@@ -0,0 +1,411 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_SOCKETS //|| UNITY_WINRT
+
+#include "SocketStreams.h"
+#include "ServerSocket.h"
+#include "Runtime/Profiler/TimeHelper.h"
+
+static const ProfileTimeFormat kTimeMillisecond = 1000000ULL;
+
+// ---------------------------------------------------------------
+// SocketStream
+// ---------------------------------------------------------------
+SocketStream::SocketStream(TSocketHandle socketHandle, bool block)
+: Socket(socketHandle)
+, m_IsConnected(true)
+, m_IsBlocking(block)
+{
+ if (!SetBlocking(block))
+ {
+ ErrorStringMsg("Unable to set blocking mode for socket stream, shutting down socket!");
+ Shutdown(); // Shutdown if unable to switch mode
+ }
+}
+
+int SocketStream::Send(const void* data, UInt32 data_len)
+{
+ if (data_len == 0)
+ return data_len;
+
+ int result = Socket::Send(data, data_len);
+ if (result < 0 && !Socket::WouldBlockError())
+ OnSocketError();
+
+ return result;
+}
+
+int SocketStream::Recv(void* data, UInt32 data_len)
+{
+ if (data_len == 0)
+ return data_len;
+
+ int result = Socket::Recv(data, data_len);
+ if (result == 0 || (result < 0 && !Socket::WouldBlockError()))
+ OnSocketError();
+
+ return result;
+}
+
+bool SocketStream::SendAll(const void* data, UInt32 data_len)
+{
+ while (data_len > 0)
+ {
+ int nBytes = Send(data, data_len);
+ if (nBytes <= 0 && WouldBlockError())
+ {
+ if(!Poll())
+ return false;
+ continue;
+ }
+ if (nBytes < 0)
+ return false;
+
+ data_len -= nBytes;
+ data = (char*)data + nBytes;
+ }
+ return true;
+}
+
+bool SocketStream::RecvAll(void* data, UInt32 data_len)
+{
+ while (data_len > 0)
+ {
+ int nBytes = Recv(data, data_len);
+ if (nBytes < 0 && WouldBlockError())
+ {
+ if(!Poll())
+ return false;
+ continue;
+ }
+ if (nBytes <= 0)
+ return false;
+
+ data_len -= nBytes;
+ data = (char*)data + nBytes;
+ }
+ return true;
+}
+
+bool SocketStream::Shutdown()
+{
+ if (!m_IsConnected)
+ return true;
+
+#if USE_WINSOCK_APIS
+ if (CheckError(shutdown(m_SocketHandle, SD_BOTH), "failed to shutdown stream", WSAENOTCONN))
+#elif UNITY_WINRT
+ if (CheckError(false, "failed to shutdown stream", ENOTCONN))
+#else
+ if (CheckError(shutdown(m_SocketHandle, SHUT_RDWR), "failed to shutdown stream", ENOTCONN))
+#endif
+ {
+ m_IsConnected = false; // always tag as disconnected to avoid loops
+ return false;
+ }
+ m_IsConnected = false;
+ return true;
+}
+
+void SocketStream::OnSocketError()
+{
+ Shutdown();
+}
+
+bool SocketStream::CanSendNonblocking( UInt32 data_len )
+{
+ return true;
+}
+
+
+// ---------------------------------------------------------------
+// BufferedSocketStream
+// ---------------------------------------------------------------
+BufferedSocketStream::BufferedSocketStream(TSocketHandle socketHandle, UInt32 sendbufferMaxSize, UInt32 recvbufferMaxSize)
+#if UNITY_FLASH
+ // This is like the worst hack ever, flash can't read data so it never blocks on recv
+: SocketStream(socketHandle, true)
+#else
+: SocketStream(socketHandle, false)
+#endif
+, m_IsArtificiallyConnected(false)
+, m_Sendbuffer(kMemNetwork, sendbufferMaxSize)
+, m_Recvbuffer(kMemNetwork, recvbufferMaxSize)
+{}
+
+BufferedSocketStream::BufferedSocketStream(TSocketHandle socketHandle, UInt32 sendbufferMaxSize, UInt32 recvbufferMaxSize, bool block)
+: SocketStream(socketHandle, block)
+, m_IsArtificiallyConnected(false)
+, m_Sendbuffer(kMemNetwork, sendbufferMaxSize)
+, m_Recvbuffer(kMemNetwork, recvbufferMaxSize)
+{}
+
+bool BufferedSocketStream::FillRecvbuffer()
+{
+ UInt32 recvBufferFree = m_Recvbuffer.GetFreeSize();
+ if (!recvBufferFree)
+ {
+ if (!SocketStream::IsBlocking())
+ return false;
+ m_Recvbuffer.BlockUntilFree();
+ }
+
+ void* recvBuffer = m_Recvbuffer.WritePtr(&recvBufferFree);
+ int nRecvBytes = SocketStream::Recv(recvBuffer, recvBufferFree);
+ if (nRecvBytes <= 0)
+ return false;
+
+ m_Recvbuffer.WritePtrUpdate(recvBuffer, nRecvBytes);
+ return true;
+}
+
+bool BufferedSocketStream::FlushSendbuffer()
+{
+ UInt32 sendBufferAvail = m_Sendbuffer.GetAvailableSize();
+ if (!sendBufferAvail)
+ {
+ if (!SocketStream::IsBlocking())
+ return false;
+ m_Sendbuffer.BlockUntilAvailable();
+ }
+
+ const void* sendBuffer = m_Sendbuffer.ReadPtr(&sendBufferAvail);
+ int nSentBytes = SocketStream::Send(sendBuffer, sendBufferAvail);
+ if (nSentBytes < 0)
+ return false;
+
+ m_Sendbuffer.ReadPtrUpdate(sendBuffer, nSentBytes);
+ return true;
+}
+
+bool BufferedSocketStream::Poll(UInt64 timeoutMS)
+{
+ if (!m_IsConnected)
+ return false;
+
+ Mutex::AutoLock lock(m_PollMutex);
+
+ ABSOLUTE_TIME start = START_TIME;
+
+ bool notBlocked = true;
+ while (notBlocked && GetProfileTime(ELAPSED_TIME(start)) < timeoutMS * kTimeMillisecond)
+ {
+ notBlocked = FlushSendbuffer();
+ notBlocked = FillRecvbuffer() || notBlocked;
+ notBlocked = m_IsConnected && notBlocked;
+ }
+
+ return m_IsConnected;
+}
+
+int BufferedSocketStream::Send(const void* data, UInt32 data_len)
+{
+ if (!m_IsConnected)
+ return -1;
+
+ void* sendBuffer = m_Sendbuffer.WritePtr(&data_len);
+ memcpy(sendBuffer, data, data_len);
+ m_Sendbuffer.WritePtrUpdate(sendBuffer, data_len);
+
+ return data_len;
+}
+
+int BufferedSocketStream::Recv(void* data, UInt32 data_len)
+{
+ if (!m_IsConnected && !m_IsArtificiallyConnected)
+ return 0;
+
+ const void* recvBuffer = m_Recvbuffer.ReadPtr(&data_len);
+ memcpy(data, recvBuffer, data_len);
+ m_Recvbuffer.ReadPtrUpdate(recvBuffer, data_len);
+
+ if (data_len == 0)
+ {
+ if (m_IsArtificiallyConnected)
+ Shutdown();
+ else
+ return -1;
+ }
+
+ return data_len;
+}
+
+void BufferedSocketStream::OnSocketError()
+{
+ m_IsArtificiallyConnected = 0 < m_Recvbuffer.GetAvailableSize();
+ SocketStream::Shutdown();
+}
+
+bool BufferedSocketStream::Shutdown()
+{
+ bool result = SocketStream::Shutdown();
+ m_IsArtificiallyConnected = false;
+ m_Sendbuffer.ReleaseBlockedThreads();
+ m_Recvbuffer.ReleaseBlockedThreads();
+ return result;
+}
+
+bool BufferedSocketStream::CanSendNonblocking( UInt32 data_len )
+{
+ UInt32 sendBufferFree = m_Sendbuffer.GetFreeSize();
+ return sendBufferFree >= data_len;
+}
+
+// ---------------------------------------------------------------
+// ThreadedSocketStream
+// ---------------------------------------------------------------
+#if SUPPORT_THREADS
+ThreadedSocketStream::ThreadedSocketStream(TSocketHandle socketHandle, UInt32 sendbufferSize, UInt32 recvbufferSize)
+: BufferedSocketStream(socketHandle, sendbufferSize, recvbufferSize, false)
+{
+ m_Reader.SetName("UnitySocketReader");
+ m_Writer.SetName("UnitySocketWriter");
+ m_Reader.Run(ReaderLoop, this);
+ m_Writer.Run(WriterLoop, this);
+}
+
+ThreadedSocketStream::~ThreadedSocketStream()
+{
+ Shutdown();
+ m_Reader.WaitForExit();
+ m_Writer.WaitForExit();
+}
+
+void* ThreadedSocketStream::ReaderLoop(void* _arg)
+{
+ ThreadedSocketStream* _this = reinterpret_cast<ThreadedSocketStream*>(_arg);
+
+
+ while(_this->m_IsConnected)
+ {
+ if(_this->WaitForAvailableRecvData(10))
+ _this->FillRecvbuffer();
+ }
+ return NULL;
+}
+
+void* ThreadedSocketStream::WriterLoop(void* _arg)
+{
+ ThreadedSocketStream* _this = reinterpret_cast<ThreadedSocketStream*>(_arg);
+ while(_this->m_IsConnected)
+ {
+ _this->SendBuffer().BlockUntilAvailable();
+ if(_this->WaitForAvailableSendBuffer(10))
+ _this->FlushSendbuffer();
+ }
+ return NULL;
+}
+#endif // SUPPORT_THREADS
+
+// ---------------------------------------------------------------------------
+#if ENABLE_UNIT_TESTS && !UNITY_XENON
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "Runtime/Network/NetworkUtility.h"
+SUITE (SocketStreamTests)
+{
+ struct SocketStreamFixture
+ {
+ SocketStreamFixture()
+ {
+ NetworkInitialize();
+ m_Socket = new ServerSocket();
+ CHECK((m_Socket->StartListening("127.0.0.1", 0, true)) == true);
+ CHECK((m_Port = m_Socket->GetPort()) > 0);
+ };
+
+ ~SocketStreamFixture()
+ {
+ delete m_Socket;
+ NetworkCleanup();
+ }
+
+ int Accept()
+ {
+ return m_Socket->Accept();
+ };
+
+ int Connect()
+ {
+ return Socket::Connect("127.0.0.1", m_Port);
+ };
+
+ int m_Port;
+ ServerSocket* m_Socket;
+ };
+
+ void TestNonBlockingSendAndRecv(SocketStream& server, SocketStream& client)
+ {
+ char buffer[4096];
+ int nBytesToSend = sizeof(buffer);
+ int nBytesToRecv = sizeof(buffer);
+ while (nBytesToRecv)
+ {
+ int nBytesSent = client.Send(buffer, nBytesToSend);
+ if (nBytesSent > 0)
+ nBytesToSend -= nBytesSent;
+ AssertBreak(nBytesSent >= 0 || client.WouldBlockError());
+
+ int nBytesRecv = server.Recv(buffer, nBytesToRecv);
+ if (nBytesRecv > 0)
+ nBytesToRecv -= nBytesRecv;
+ AssertBreak(nBytesRecv > 0 || (nBytesRecv < 0 && server.WouldBlockError()));
+ }
+ CHECK_EQUAL(nBytesToSend, nBytesToRecv);
+ }
+
+ TEST_FIXTURE(SocketStreamFixture, SocketStreamNB_SendRecv)
+ {
+ SocketStream client(Connect(), false);
+ SocketStream server(Accept(), false);
+ TestNonBlockingSendAndRecv(server, client);
+ }
+
+#if SUPPORT_THREADS
+ static void* PollBufferedStream(void* arg)
+ {
+ BufferedSocketStream* stream = reinterpret_cast<BufferedSocketStream*>(arg);
+ while (stream->Poll(1000)) { /* spin */ }
+ return NULL;
+ }
+
+ TEST_FIXTURE(SocketStreamFixture, BufferedSocketStreamNB_SendRecvNonBlocking)
+ {
+ BufferedSocketStream client(Connect(), 1024, 1024);
+ BufferedSocketStream server(Accept(), 1024, 1024);
+
+ Thread clientPoller, serverPoller;
+ clientPoller.Run(PollBufferedStream, &client);
+ serverPoller.Run(PollBufferedStream, &server);
+
+ TestNonBlockingSendAndRecv(server, client);
+
+ char buffer[4096];
+ CHECK(client.SendAll(buffer, sizeof(buffer)));
+ CHECK(server.RecvAll(buffer, sizeof(buffer)));
+
+ server.Shutdown();
+ client.Shutdown();
+ serverPoller.WaitForExit();
+ clientPoller.WaitForExit();
+ }
+
+ TEST_FIXTURE(SocketStreamFixture, ThreadedSocketStreamNB_SendRecvNonBlocking)
+ {
+ ThreadedSocketStream client(Connect(), 1024, 1024);
+ ThreadedSocketStream server(Accept(), 1024, 1024);
+ TestNonBlockingSendAndRecv(server, client);
+
+ char buffer[4096];
+ CHECK(client.SendAll(buffer, sizeof(buffer)));
+ CHECK(server.RecvAll(buffer, sizeof(buffer)));
+
+ server.Shutdown();
+ client.Shutdown();
+ }
+#endif // SUPPORT_THREADS
+}
+
+#endif //ENABLE_UNIT_TESTS
+
+#endif // ENABLE_SOCKETS
diff --git a/Runtime/Network/SocketStreams.h b/Runtime/Network/SocketStreams.h
new file mode 100644
index 0000000..6132b98
--- /dev/null
+++ b/Runtime/Network/SocketStreams.h
@@ -0,0 +1,94 @@
+#ifndef RUNTIME_NETWORK_SOCKETSTREAMS_H
+#define RUNTIME_NETWORK_SOCKETSTREAMS_H
+
+#if ENABLE_SOCKETS //|| UNITY_WINRT
+
+#include "Sockets.h"
+#include "Runtime/Containers/ExtendedRingbuffer.h"
+#include "Runtime/Threads/Mutex.h"
+#include "Runtime/Threads/Thread.h"
+
+class SocketStream : public Socket
+{
+public:
+ SocketStream(TSocketHandle socketHandle, bool block);
+ virtual ~SocketStream() {};
+
+ virtual int Send(const void* data, UInt32 data_len);
+ virtual int Recv(void* data, UInt32 data_len);
+ virtual bool IsBlocking() const { return m_IsBlocking; };
+ virtual bool IsConnected() const { return m_IsConnected; };
+ virtual bool WouldBlockError() { return Socket::WouldBlockError(); }
+ virtual bool Shutdown();
+
+ bool SendAll(const void* data, UInt32 data_len);
+ bool RecvAll(void* data, UInt32 data_len);
+
+ virtual bool CanSendNonblocking(UInt32 data_len);
+
+protected:
+ virtual bool Poll(UInt64 /*timeoutMS*/ = kDefaultPollTime) { return false; }
+ virtual void OnSocketError();
+
+protected:
+ bool m_IsBlocking;
+ volatile bool m_IsConnected;
+};
+
+class BufferedSocketStream : public SocketStream
+{
+public:
+ BufferedSocketStream(TSocketHandle socketHandle, UInt32 sendbufferMaxSize = kDefaultBufferSize, UInt32 recvbufferMaxSize = kDefaultBufferSize);
+ virtual ~BufferedSocketStream() {};
+
+ virtual int Send(const void* data, UInt32 data_len);
+ virtual int Recv(void* data, UInt32 data_len);
+ virtual bool IsBlocking() const { return false; };
+ virtual bool IsConnected() const { return (m_IsArtificiallyConnected && m_Recvbuffer.GetAvailableSize() != 0) || m_IsConnected; };
+ virtual bool WouldBlockError() { return IsConnected(); }
+ virtual bool Shutdown();
+
+ virtual bool Poll(UInt64 timeoutMS = kDefaultPollTime);
+
+ ExtendedGrowingRingbuffer& SendBuffer() { return m_Sendbuffer; };
+ ExtendedGrowingRingbuffer& RecvBuffer() { return m_Recvbuffer; };
+
+ const ExtendedGrowingRingbuffer& SendBuffer() const { return m_Sendbuffer; };
+ const ExtendedGrowingRingbuffer& RecvBuffer() const { return m_Recvbuffer; };
+
+ virtual bool CanSendNonblocking(UInt32 data_len);
+
+protected:
+ BufferedSocketStream(TSocketHandle socketHandle, UInt32 sendbufferMaxSize, UInt32 recvbufferMaxSize, bool block);
+
+ void OnSocketError();
+ bool FlushSendbuffer();
+ bool FillRecvbuffer();
+
+private:
+ volatile bool m_IsArtificiallyConnected;
+ ExtendedGrowingRingbuffer m_Sendbuffer;
+ ExtendedGrowingRingbuffer m_Recvbuffer;
+ Mutex m_PollMutex;
+};
+
+#if SUPPORT_THREADS
+class ThreadedSocketStream : public BufferedSocketStream
+{
+public:
+ ThreadedSocketStream(TSocketHandle socketHandle, UInt32 sendbufferMaxSize = kDefaultBufferSize, UInt32 recvbufferMaxSize = kDefaultBufferSize);
+ virtual ~ThreadedSocketStream();
+
+ virtual bool Poll(UInt64 /*timeoutMS*/ = kDefaultPollTime) { return IsConnected(); }
+
+private:
+ static void* WriterLoop(void* _arg);
+ static void* ReaderLoop(void* _arg);
+
+ Thread m_Reader;
+ Thread m_Writer;
+};
+#endif // SUPPORT_THREADS
+
+#endif // ENABLE_SOCKETS
+#endif // RUNTIME_NETWORK_SOCKETSTREAMS_H
diff --git a/Runtime/Network/SocketUtils.h b/Runtime/Network/SocketUtils.h
new file mode 100644
index 0000000..1edf54a
--- /dev/null
+++ b/Runtime/Network/SocketUtils.h
@@ -0,0 +1,56 @@
+#ifndef SOCKETUTILS_H
+#define SOCKETUTILS_H
+
+#include "Sockets.h"
+#if !UNITY_WINRT
+inline void SetupAddress(unsigned long addr, unsigned short port, sockaddr_in* sock_addr)
+{
+ memset(sock_addr, 0, sizeof(sockaddr_in));
+ sock_addr->sin_family = AF_INET;
+ sock_addr->sin_addr.s_addr = addr;
+ sock_addr->sin_port = port;
+}
+#endif
+enum
+{
+#if UNITY_WINRT
+ kPlatformConnectWouldBlock = E_PENDING,
+ kPlatformStreamWouldBlock = E_PENDING,
+ kPlatformAcceptWouldBlock = E_PENDING,
+#elif USE_WINSOCK_APIS
+ kPlatformConnectWouldBlock = WSAEWOULDBLOCK,
+ kPlatformStreamWouldBlock = WSAEWOULDBLOCK,
+ kPlatformAcceptWouldBlock = WSAEWOULDBLOCK
+#elif UNITY_PS3
+ kPlatformConnectWouldBlock = SYS_NET_EINPROGRESS,
+ kPlatformStreamWouldBlock = SYS_NET_EAGAIN,
+ kPlatformAcceptWouldBlock = SYS_NET_EWOULDBLOCK
+#else
+ kPlatformConnectWouldBlock = EINPROGRESS,
+ kPlatformStreamWouldBlock = EAGAIN,
+ kPlatformAcceptWouldBlock = EWOULDBLOCK
+#endif
+};
+
+#if UNITY_FLASH
+#include "Runtime/Scripting/ScriptingUtility.h"
+
+extern "C" int flash_errno();
+extern "C" int flash_set_errno(int error);
+extern "C" int flash_socket(int domain, int type, int protocol);
+extern "C" int flash_connect(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
+extern "C" int flash_close(int sockfd);
+extern "C" ssize_t flash_recvfrom (int sockfd, void* buffer, size_t length, int flags, struct sockaddr* address, socklen_t* address_len);
+extern "C" ssize_t flash_sendto(int, const void *, size_t, int, const struct sockaddr*, socklen_t);
+extern "C" int flash_setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t option_len);
+
+#define setsockopt flash_setsockopt
+#define recvfrom flash_recvfrom
+#define sendto flash_sendto
+#define socket flash_socket
+#define connect flash_connect
+#define close flash_close
+#endif
+
+
+#endif
diff --git a/Runtime/Network/Sockets.cpp b/Runtime/Network/Sockets.cpp
new file mode 100644
index 0000000..cdbc15f
--- /dev/null
+++ b/Runtime/Network/Sockets.cpp
@@ -0,0 +1,384 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_SOCKETS
+#include "Runtime/Network/Sockets.h"
+#include "Runtime/Profiler/TimeHelper.h"
+#include "PlatformDependent/CommonWebPlugin/Verification.h"
+
+#if UNITY_WIN
+#include "PlatformDependent/Win/GetFormattedErrorString.h"
+#endif
+
+#define SUPPORT_NON_BLOCKING !UNITY_FLASH
+
+#include "SocketUtils.h"
+
+#define SocketError(msg, identifier) { DebugStringToFile(Format("Socket: %s, error: %s(%d)", msg, GetSocketErrorMsg(Socket::GetError()).c_str(), Socket::GetError()), 0, __FILE_STRIPPED__, __LINE__, kError, 0, identifier); }
+
+#if !UNITY_WINRT
+
+int Socket::PollAsyncConnection(int socketHandle, time_t timeoutMS)
+{
+#if SUPPORT_NON_BLOCKING
+ // Set timeout on socket connection
+ struct timeval tv;
+ tv.tv_sec = timeoutMS / 1000;
+ tv.tv_usec = (timeoutMS - tv.tv_sec * 1000) * 1000;
+
+ if (timeoutMS == 0)
+ tv.tv_usec = 10;
+
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(socketHandle, &fdset);
+
+ fd_set exceptfds;
+ FD_ZERO(&exceptfds);
+ FD_SET(socketHandle, &exceptfds);
+
+#if UNITY_PS3
+ int selectedDescriptors = socketselect(socketHandle + 1, NULL, &fdset, &exceptfds, &tv);
+#else
+ int selectedDescriptors = select(socketHandle + 1, NULL, &fdset, &exceptfds, &tv);
+#endif
+ // we need one selected descriptor to be able to determine socket state
+ if (selectedDescriptors == 1)
+ {
+ #if USE_WINSOCK_APIS
+ if (FD_ISSET(socketHandle, &fdset) && !FD_ISSET(socketHandle, &exceptfds))
+ {
+ Socket::SetError(0);
+ return 0;
+ }
+ Socket::SetError(WSAECONNREFUSED);
+ #else
+ int so_error = 0;
+ socklen_t len = sizeof (so_error);
+ if (getsockopt(socketHandle, SOL_SOCKET, SO_ERROR, &so_error, &len) > -1)
+ if (Socket::SetError(so_error) == 0)
+ return 0;
+ #endif
+ }
+ // we don't have any writeable or errornous descriptors yet
+ else if (selectedDescriptors == 0)
+ {
+ Socket::SetError(kPlatformConnectWouldBlock);
+ }
+
+ return -1;
+#endif // SUPPORT_NON_BLOCKING
+ return 0;
+}
+
+// ---------------------------------------------------------------
+// Socket::Statics
+// ---------------------------------------------------------------
+int Socket::Close(int socketHandle)
+{
+#if USE_WINSOCK_APIS
+ return closesocket(socketHandle);
+#elif UNITY_PS3
+ return socketclose(socketHandle);
+#else
+ return close(socketHandle);
+#endif
+}
+
+int Socket::Connect(const char* ip, unsigned short port, time_t timeoutMS, bool polling, bool logConnectError)
+{
+ struct sockaddr_in addr;
+ SetupAddress(inet_addr(ip), htons(port), &addr);
+ return Connect((const sockaddr*) &addr, sizeof(addr), timeoutMS, polling, logConnectError);
+}
+
+UInt32 ComputeIdentifier(const sockaddr* addr)
+{
+ if (addr == NULL)
+ return 0;
+
+ UInt32 identifier = CRCBegin();
+ identifier = CRCFeed( identifier, (const UInt8*)&( ((const sockaddr_in*)addr)->sin_addr.s_addr ), sizeof(unsigned long) );
+ identifier = CRCFeed( identifier, (const UInt8*)&( ((const sockaddr_in*)addr)->sin_port ), sizeof(unsigned short) );
+ identifier = CRCDone(identifier);
+
+ return identifier;
+}
+
+int Socket::Connect(const sockaddr* addr, socklen_t addr_len, time_t timeoutMS, bool polling, bool logConnectError)
+{
+ int socketHandle;
+ int identifier = (int)ComputeIdentifier(addr);
+ CheckError(socketHandle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), polling ? NULL : "failed to create socket", 0, identifier);
+ int so_error = 0;
+#if SUPPORT_NON_BLOCKING
+ if (!SetBlocking(socketHandle, false) && !polling)
+ ErrorStringMsg("unable to set blocking mode");
+ if (CheckError(connect(socketHandle, addr, addr_len), (logConnectError && !polling) ? "connect failed" : NULL, kPlatformConnectWouldBlock, identifier))
+ so_error = -1;
+
+ // Ensure that the connection is alive
+ if (so_error == 0 && timeoutMS != -1)
+ {
+ if (CheckError(PollAsyncConnection(socketHandle, timeoutMS), (logConnectError && !polling) ? "connect failed" : NULL, 0, identifier))
+ so_error = -1;
+ }
+#else
+ if (CheckError(connect(socketHandle, addr, addr_len), (logConnectError && !polling) ? "connect failed" : NULL, 0, identifier))
+ so_error = -1;
+#endif
+ // Handle connection error
+ if (so_error != 0)
+ {
+ if(logConnectError && !polling)
+ DebugStringToFile(Format("connect failed"), 0, __FILE_STRIPPED__, __LINE__, kError, 0, identifier);
+ Close(socketHandle);
+ return -1;
+ }
+
+ RemoveErrorWithIdentifierFromConsole(identifier);
+
+ return socketHandle;
+}
+
+bool Socket::SetBlocking(int socketHandle, bool blocking)
+{
+ bool success = true;
+
+#if !SUPPORT_NON_BLOCKING
+ success = blocking;
+#elif UNITY_PS3
+ int nonBlockingValue = blocking ? 0 : 1;
+ if (setsockopt(socketHandle, SOL_SOCKET, SO_NBIO, &nonBlockingValue, sizeof(nonBlockingValue)) != 0)
+ success = false;
+#elif USE_WINSOCK_APIS
+ u_long nonBlocking = blocking ? 0 : 1;
+ if (ioctlsocket(socketHandle, FIONBIO, &nonBlocking) != 0)
+ success = false;
+#else // unix
+ if ((fcntl(socketHandle, F_SETFL, blocking ? 0 : O_NONBLOCK) == -1))
+ success = false;
+#endif
+ return success;
+}
+
+// ---------------------------------------------------------------
+// Base Socket
+// ---------------------------------------------------------------
+Socket::Socket(int domain, int type, int protocol)
+: m_SocketError(0)
+, m_SendRecvFlags(0)
+{
+ if (!CheckError(m_SocketHandle = socket(domain, type, protocol), "unable to create socket"))
+ SetIgnoreSIGPIPE(true);
+}
+
+int Socket::SetSocketOption(int level, int option, void* value, size_t value_len)
+{
+ return setsockopt(m_SocketHandle, level, option, (char*) value, value_len);
+}
+
+int Socket::SetSocketOption(int level, int option, bool optionValue)
+{
+#if USE_WINSOCK_APIS
+ BOOL val = optionValue ? TRUE : FALSE;
+#else
+ int val = !!optionValue;
+#endif
+ return SetSocketOption(level, option, &val, sizeof(val));
+}
+
+bool Socket::SetReuseAddress(bool reuse)
+{
+ if (CheckError(SetSocketOption(SOL_SOCKET, SO_REUSEADDR, reuse), "set reusable addr failed"))
+ return false;
+#ifdef SO_REUSEPORT
+ if (CheckError(SetSocketOption(SOL_SOCKET, SO_REUSEPORT, reuse), "set reusable port failed"))
+ return false;
+#endif
+ return true;
+}
+
+int Socket::Send(const void* data, size_t data_len, SendUserData* userData)
+{
+ int result;
+ if (userData != NULL)
+ {
+ result = sendto(m_SocketHandle, (char*) data, data_len, userData->flags | m_SendRecvFlags, userData->dstAddr, userData->dstLen);
+ }
+ else
+ {
+ result = sendto(m_SocketHandle, (char*) data, data_len, m_SendRecvFlags, NULL, 0);
+ }
+ CheckError(result);
+ return result;
+}
+
+int Socket::Recv(void* data, size_t data_len, RecvUserData* userData)
+{
+ int result;
+ if (userData != NULL)
+ {
+ result = recvfrom(m_SocketHandle, (char*) data, data_len, userData->flags | m_SendRecvFlags, userData->srcAddr, &userData->srcLen);
+ }
+ else
+ {
+ result = recvfrom(m_SocketHandle, (char*) data, data_len, m_SendRecvFlags, NULL, NULL);
+ }
+ CheckError(result);
+ return result;
+}
+
+bool Socket::WaitForAvailableSendBuffer( time_t timeoutMS )
+{
+ // Set timeout on socket connection
+ struct timeval tv;
+ tv.tv_sec = timeoutMS / 1000;
+ tv.tv_usec = (timeoutMS - tv.tv_sec * 1000) * 1000;
+
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(m_SocketHandle, &fdset);
+
+#if UNITY_PS3
+ return socketselect(m_SocketHandle + 1, NULL, &fdset, NULL, &tv) == 1;
+#else
+ return select(m_SocketHandle + 1, NULL, &fdset, NULL, &tv) == 1;
+#endif
+}
+
+bool Socket::WaitForAvailableRecvData( time_t timeoutMS )
+{
+ // Set timeout on socket connection
+ struct timeval tv;
+ tv.tv_sec = timeoutMS / 1000;
+ tv.tv_usec = (timeoutMS - tv.tv_sec * 1000) * 1000;
+
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(m_SocketHandle, &fdset);
+
+#if UNITY_PS3
+ return socketselect(m_SocketHandle + 1, &fdset, NULL, NULL, &tv) == 1;
+#else
+ return select(m_SocketHandle + 1, &fdset, NULL, NULL, &tv) == 1;
+#endif
+}
+
+#endif // !UNITY_WINRT
+
+std::string GetSocketErrorMsg(int error)
+{
+#if UNITY_WIN
+ return WideToUtf8( GetHResultErrorMessage(error) );
+#elif UNITY_FLASH || UNITY_XENON
+ return "Unknown network error";
+#else
+ return strerror(error);
+#endif
+}
+
+Socket::Socket(TSocketHandle socketHandle)
+: m_SocketError(0)
+, m_SendRecvFlags(0)
+, m_SocketHandle(socketHandle)
+{
+#if UNITY_WINRT
+ AssertBreak(nullptr != m_SocketHandle);
+#else
+ AssertBreak(m_SocketHandle >= 0);
+#endif
+ SetIgnoreSIGPIPE(true);
+}
+
+Socket::~Socket()
+{
+ Close(m_SocketHandle);
+}
+
+bool Socket::WouldBlockError()
+{
+ int error = Socket::GetError();
+ return error == kPlatformConnectWouldBlock
+ || error == kPlatformStreamWouldBlock
+ || error == kPlatformAcceptWouldBlock;
+}
+
+bool Socket::SetIgnoreSIGPIPE(bool ignore)
+{
+#if USE_WINSOCK_APIS || UNITY_FLASH || UNITY_PS3 || UNITY_WINRT
+ return true;
+#elif UNITY_OSX || UNITY_IPHONE
+ return !CheckError(SetSocketOption(SOL_SOCKET, SO_NOSIGPIPE, ignore), "failed to install NOSIGPIPE");
+#else // linux
+ m_SendRecvFlags = MSG_NOSIGNAL;
+ return true;
+#endif
+}
+
+bool Socket::SetBlocking(bool blocking)
+{
+ if (!SetBlocking(m_SocketHandle, blocking))
+ {
+ ErrorStringMsg("failed to set blocking mode");
+ return false;
+ }
+ return true;
+}
+
+#if UNITY_WINRT
+// Implemented in MetroSockets.cpp
+int SocketWrapperErrorState(TSocketHandle socketHandle);
+int SocketWrapperErrorState(TSocketHandle socketHandle, int error);
+#endif // UNITY_WINRT
+
+int Socket::GetError()
+{
+#if UNITY_WINRT
+ return SocketWrapperErrorState(m_SocketHandle);
+#elif USE_WINSOCK_APIS
+ return WSAGetLastError();
+#elif UNITY_FLASH
+ return flash_errno();
+#elif UNITY_PS3
+ return sys_net_errno;
+#else
+ return errno;
+#endif
+}
+
+int Socket::SetError(int error)
+{
+#if UNITY_WINRT
+ SocketWrapperErrorState(m_SocketHandle, error);
+#elif USE_WINSOCK_APIS
+ WSASetLastError(error);
+#elif UNITY_FLASH
+ flash_set_errno(error);
+#elif UNITY_PS3
+ sys_net_errno = error;
+#else
+ errno = error;
+#endif
+ return error;
+}
+
+bool Socket::CheckError(int result, const char* msg, int validState, int identifier)
+{
+ if (result < 0)
+ {
+ int error = Socket::GetError();
+ if (error != validState)
+ {
+ if (msg)
+ SocketError(msg, identifier);
+ return true;
+ }
+ }
+ else
+ {
+ Socket::SetError(0);
+ }
+ return false;
+}
+
+#endif // ENABLE_SOCKETS
diff --git a/Runtime/Network/Sockets.h b/Runtime/Network/Sockets.h
new file mode 100644
index 0000000..6a4f5d7
--- /dev/null
+++ b/Runtime/Network/Sockets.h
@@ -0,0 +1,92 @@
+#ifndef RUNTIME_NETWORK_SOCKETS_H
+#define RUNTIME_NETWORK_SOCKETS_H
+
+#include "SocketConsts.h"
+
+#if ENABLE_SOCKETS
+
+#include "Runtime/Utilities/NonCopyable.h"
+
+//
+// Socket implementations, all sockets are by default non-blocking.
+// Metro: the implementation of this is located in PlatformDependent\MetroPlayer\MetroSocket.cpp
+//
+class Socket : public NonCopyable
+{
+#if UNITY_WINRT
+# define UNITY_SOCKET_STATIC_ERROR_STATE
+#else
+# define UNITY_SOCKET_STATIC_ERROR_STATE static
+#endif // UNITY_WINRT
+
+public:
+ static TSocketHandle Connect(const char* ip, unsigned short port, time_t timeoutMS = 4000, bool polling = false, bool logConnectError = true);
+#if !UNITY_WINRT
+ static int Connect(const sockaddr* addr, socklen_t addr_len, time_t timeoutMS = 4000, bool polling = false, bool logConnectError = true);
+#endif
+ static int Close(TSocketHandle socketHandle);
+
+ static int PollAsyncConnection(TSocketHandle socketHandle, time_t timeoutMS = 0);
+ UNITY_SOCKET_STATIC_ERROR_STATE bool CheckError(int result, const char* msg = NULL, int valid_state = 0, int identifier = 0);
+ UNITY_SOCKET_STATIC_ERROR_STATE bool WouldBlockError();
+
+ bool WaitForAvailableSendBuffer(time_t timeoutMS);
+ bool WaitForAvailableRecvData(time_t timeoutMS);
+
+protected:
+ Socket(TSocketHandle socketHandle);
+ Socket(int domain, int type, int protocol);
+ virtual ~Socket();
+
+ int SetSocketOption(int level, int option, void* value, size_t value_len);
+ int SetSocketOption(int level, int option, bool value);
+
+ bool SetReuseAddress(bool reuse);
+
+ int Send(const void* data, size_t data_len, SendUserData* userData = NULL);
+ int Recv(void* data, size_t data_len, RecvUserData* userData = NULL);
+
+ bool SetIgnoreSIGPIPE(bool ignore);
+ bool SetBlocking(bool block);
+
+protected:
+ TSocketHandle m_SocketHandle;
+ int m_SendRecvFlags;
+ volatile int m_SocketError;
+
+private:
+ static bool SetBlocking(TSocketHandle socketHandle, bool block);
+
+ UNITY_SOCKET_STATIC_ERROR_STATE int GetError();
+ UNITY_SOCKET_STATIC_ERROR_STATE int SetError(int error);
+
+#undef UNITY_SOCKET_STATIC_ERROR_STATE
+};
+
+
+#if UNITY_WINRT
+namespace UnityPlayer
+{
+ [Windows::Foundation::Metadata::WebHostHidden]
+ public ref class StreamListenerContext sealed
+ {
+ public:
+ StreamListenerContext( Windows::Networking::HostName^ hostname, Platform::String^ serviceName );
+ void OnConnection( Windows::Networking::Sockets::StreamSocketListener^ streamSocket, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs^ args);
+ Windows::Networking::Sockets::StreamSocketListener^ GetStreamSocket() { return m_listener; }
+ Windows::Networking::Sockets::StreamSocket^ GetConnectionSocket() { return m_connectionSocket; }
+ void Bind();
+
+ private:
+ Windows::Networking::Sockets::StreamSocketListener^ m_listener;
+ Windows::Networking::Sockets::StreamSocket^ m_connectionSocket;
+ Windows::Networking::HostName^ m_hostname;
+ Platform::String^ m_port;
+ };
+}
+#endif // UNITY_WINRT
+
+
+#endif // ENABLE_SOCKETS
+
+#endif // RUNTIME_NETWORK_SOCKETS_H