summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Bindings/CMakeLists.txt9
-rw-r--r--src/Bindings/LuaNameLookup.cpp88
-rw-r--r--src/Bindings/LuaNameLookup.h46
-rw-r--r--src/Bindings/LuaServerHandle.cpp209
-rw-r--r--src/Bindings/LuaServerHandle.h89
-rw-r--r--src/Bindings/LuaState.cpp69
-rw-r--r--src/Bindings/LuaState.h15
-rw-r--r--src/Bindings/LuaTCPLink.cpp610
-rw-r--r--src/Bindings/LuaTCPLink.h181
-rw-r--r--src/Bindings/LuaUDPEndpoint.cpp221
-rw-r--r--src/Bindings/LuaUDPEndpoint.h86
-rw-r--r--src/Bindings/ManualBindings.cpp295
-rw-r--r--src/Bindings/ManualBindings.h10
-rw-r--r--src/Bindings/ManualBindings_Network.cpp942
-rw-r--r--src/Bindings/Plugin.h1
-rw-r--r--src/Bindings/PluginLua.cpp21
-rw-r--r--src/Bindings/PluginLua.h1
-rw-r--r--src/Bindings/PluginManager.cpp18
-rw-r--r--src/Bindings/PluginManager.h2
-rw-r--r--src/BiomeDef.cpp127
-rw-r--r--src/BiomeDef.h3
-rw-r--r--src/Blocks/BlockDirt.h14
-rw-r--r--src/Blocks/BlockOre.h59
-rw-r--r--src/Chunk.cpp141
-rw-r--r--src/ClientHandle.cpp8
-rw-r--r--src/Entities/Entity.cpp22
-rw-r--r--src/Entities/Entity.h3
-rw-r--r--src/Entities/Player.cpp105
-rw-r--r--src/Entities/Player.h20
-rw-r--r--src/Generating/BioGen.cpp2
-rw-r--r--src/Generating/CompoGenBiomal.cpp14
-rw-r--r--src/Generating/ComposableGenerator.cpp5
-rw-r--r--src/Generating/FinishGen.cpp175
-rw-r--r--src/Generating/FinishGen.h23
-rw-r--r--src/Generating/HeiGen.cpp64
-rw-r--r--src/Generating/ProtIntGen.h344
-rw-r--r--src/Generating/Trees.cpp16
-rw-r--r--src/Globals.h4
-rw-r--r--src/HTTPServer/SslHTTPConnection.cpp9
-rw-r--r--src/HTTPServer/SslHTTPConnection.h2
-rw-r--r--src/Items/ItemDoor.h33
-rw-r--r--src/MobSpawner.cpp3
-rw-r--r--src/OSSupport/CMakeLists.txt2
-rw-r--r--src/OSSupport/HostnameLookup.cpp12
-rw-r--r--src/OSSupport/Network.h81
-rw-r--r--src/OSSupport/NetworkSingleton.cpp6
-rw-r--r--src/OSSupport/NetworkSingleton.h6
-rw-r--r--src/OSSupport/ServerHandleImpl.cpp35
-rw-r--r--src/OSSupport/TCPLinkImpl.cpp69
-rw-r--r--src/OSSupport/TCPLinkImpl.h12
-rw-r--r--src/OSSupport/UDPEndpointImpl.cpp608
-rw-r--r--src/OSSupport/UDPEndpointImpl.h81
-rw-r--r--src/PolarSSL++/CryptoKey.cpp2
-rw-r--r--src/PolarSSL++/SslContext.cpp3
-rw-r--r--src/Protocol/Protocol17x.cpp11
-rw-r--r--src/Protocol/Protocol18x.cpp26
-rwxr-xr-xsrc/WorldStorage/WSSAnvil.cpp24
57 files changed, 4828 insertions, 259 deletions
diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt
index d47579cd6..4cc73b350 100644
--- a/src/Bindings/CMakeLists.txt
+++ b/src/Bindings/CMakeLists.txt
@@ -8,9 +8,14 @@ SET (SRCS
Bindings.cpp
DeprecatedBindings.cpp
LuaChunkStay.cpp
+ LuaNameLookup.cpp
+ LuaServerHandle.cpp
LuaState.cpp
+ LuaTCPLink.cpp
+ LuaUDPEndpoint.cpp
LuaWindow.cpp
ManualBindings.cpp
+ ManualBindings_Network.cpp
ManualBindings_RankManager.cpp
Plugin.cpp
PluginLua.cpp
@@ -23,7 +28,11 @@ SET (HDRS
DeprecatedBindings.h
LuaChunkStay.h
LuaFunctions.h
+ LuaNameLookup.h
+ LuaServerHandle.h
LuaState.h
+ LuaTCPLink.h
+ LuaUDPEndpoint.h
LuaWindow.h
ManualBindings.h
Plugin.h
diff --git a/src/Bindings/LuaNameLookup.cpp b/src/Bindings/LuaNameLookup.cpp
new file mode 100644
index 000000000..e52d8dbdc
--- /dev/null
+++ b/src/Bindings/LuaNameLookup.cpp
@@ -0,0 +1,88 @@
+
+// LuaNameLookup.cpp
+
+// Implements the cLuaNameLookup class used as the cNetwork API callbacks for name and IP lookups from Lua
+
+#include "Globals.h"
+#include "LuaNameLookup.h"
+
+
+
+
+
+cLuaNameLookup::cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
+ m_Plugin(a_Plugin),
+ m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos),
+ m_Query(a_Query)
+{
+}
+
+
+
+
+
+void cLuaNameLookup::OnNameResolved(const AString & a_Name, const AString & a_IP)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnNameResolved"), a_Name, a_IP))
+ {
+ LOGINFO("cNetwork name lookup OnNameResolved callback failed in plugin %s looking up %s. %s resolves to %s.",
+ m_Plugin.GetName().c_str(), m_Query.c_str(), a_Name.c_str(), a_IP.c_str()
+ );
+ }
+}
+
+
+
+
+
+void cLuaNameLookup::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), m_Query, a_ErrorCode, a_ErrorMsg))
+ {
+ LOGINFO("cNetwork name lookup OnError callback failed in plugin %s looking up %s. The error is %d (%s)",
+ m_Plugin.GetName().c_str(), m_Query.c_str(), a_ErrorCode, a_ErrorMsg.c_str()
+ );
+ }
+}
+
+
+
+
+
+void cLuaNameLookup::OnFinished(void)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnFinished"), m_Query))
+ {
+ LOGINFO("cNetwork name lookup OnFinished callback failed in plugin %s, looking up %s.",
+ m_Plugin.GetName().c_str(), m_Query.c_str()
+ );
+ }
+}
+
+
+
+
diff --git a/src/Bindings/LuaNameLookup.h b/src/Bindings/LuaNameLookup.h
new file mode 100644
index 000000000..e4cdb9f53
--- /dev/null
+++ b/src/Bindings/LuaNameLookup.h
@@ -0,0 +1,46 @@
+
+// LuaNameLookup.h
+
+// Declares the cLuaNameLookup class used as the cNetwork API callbacks for name and IP lookups from Lua
+
+
+
+
+
+#pragma once
+
+#include "../OSSupport/Network.h"
+#include "PluginLua.h"
+
+
+
+
+
+class cLuaNameLookup:
+ public cNetwork::cResolveNameCallbacks
+{
+public:
+ /** Creates a new instance of the lookup callbacks for the specified query,
+ attached to the specified lua plugin and wrapping the callbacks that are in a table at the specified stack pos. */
+ cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos);
+
+protected:
+ /** The plugin for which the query is created. */
+ cPluginLua & m_Plugin;
+
+ /** The Lua table that holds the callbacks to be invoked. */
+ cLuaState::cRef m_Callbacks;
+
+ /** The query used to start the lookup (either hostname or IP). */
+ AString m_Query;
+
+
+ // cNetwork::cResolveNameCallbacks overrides:
+ virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) override;
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
+ virtual void OnFinished(void) override;
+};
+
+
+
+
diff --git a/src/Bindings/LuaServerHandle.cpp b/src/Bindings/LuaServerHandle.cpp
new file mode 100644
index 000000000..a84f894b5
--- /dev/null
+++ b/src/Bindings/LuaServerHandle.cpp
@@ -0,0 +1,209 @@
+
+// LuaServerHandle.cpp
+
+// Implements the cLuaServerHandle class wrapping the cServerHandle functionality for Lua API
+
+#include "Globals.h"
+#include "LuaServerHandle.h"
+#include "LuaTCPLink.h"
+#include "tolua++/include/tolua++.h"
+
+
+
+
+
+cLuaServerHandle::cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
+ m_Plugin(a_Plugin),
+ m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos),
+ m_Port(a_Port)
+{
+}
+
+
+
+
+
+
+cLuaServerHandle::~cLuaServerHandle()
+{
+ // If the server handle is still open, close it explicitly:
+ Close();
+}
+
+
+
+
+
+void cLuaServerHandle::SetServerHandle(cServerHandlePtr a_ServerHandle, cLuaServerHandlePtr a_Self)
+{
+ ASSERT(m_ServerHandle == nullptr); // The handle can be set only once
+
+ m_ServerHandle = a_ServerHandle;
+ m_Self = a_Self;
+}
+
+
+
+
+
+void cLuaServerHandle::Close(void)
+{
+ // Safely grab a copy of the server handle:
+ cServerHandlePtr ServerHandle = m_ServerHandle;
+ if (ServerHandle == nullptr)
+ {
+ return;
+ }
+
+ // Close the underlying server handle:
+ ServerHandle->Close();
+
+ // Close all connections at this server:
+ cLuaTCPLinkPtrs Connections;
+ {
+ cCSLock Lock(m_CSConnections);
+ std::swap(Connections, m_Connections);
+ }
+ for (auto & conn: Connections)
+ {
+ conn->Close();
+ }
+ Connections.clear();
+
+ // Allow the internal server handle to be deallocated:
+ m_ServerHandle.reset();
+}
+
+
+
+
+
+bool cLuaServerHandle::IsListening(void)
+{
+ // Safely grab a copy of the server handle:
+ cServerHandlePtr ServerHandle = m_ServerHandle;
+ if (ServerHandle == nullptr)
+ {
+ return false;
+ }
+
+ // Query the underlying server handle:
+ return ServerHandle->IsListening();
+}
+
+
+
+
+
+void cLuaServerHandle::RemoveLink(cLuaTCPLink * a_Link)
+{
+ cCSLock Lock(m_CSConnections);
+ for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
+ {
+ if (itr->get() == a_Link)
+ {
+ m_Connections.erase(itr);
+ break;
+ }
+ } // for itr - m_Connections[]
+}
+
+
+
+
+
+void cLuaServerHandle::Release(void)
+{
+ // Close the server, if it isn't closed yet:
+ Close();
+
+ // Allow self to deallocate:
+ m_Self.reset();
+}
+
+
+
+
+
+cTCPLink::cCallbacksPtr cLuaServerHandle::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort)
+{
+ // If not valid anymore, drop the connection:
+ if (!m_Callbacks.IsValid())
+ {
+ return nullptr;
+ }
+
+ // Ask the plugin for link callbacks:
+ cPluginLua::cOperation Op(m_Plugin);
+ cLuaState::cRef LinkCallbacks;
+ if (
+ !Op().Call(cLuaState::cTableRef(m_Callbacks, "OnIncomingConnection"), a_RemoteIPAddress, a_RemotePort, m_Port, cLuaState::Return, LinkCallbacks) ||
+ !LinkCallbacks.IsValid()
+ )
+ {
+ LOGINFO("cNetwork server (port %d) OnIncomingConnection callback failed in plugin %s. Dropping connection.",
+ m_Port, m_Plugin.GetName().c_str()
+ );
+ return nullptr;
+ }
+
+ // Create the link wrapper to use with the callbacks:
+ auto res = std::make_shared<cLuaTCPLink>(m_Plugin, std::move(LinkCallbacks), m_Self);
+
+ // Add the link to the list of our connections:
+ cCSLock Lock(m_CSConnections);
+ m_Connections.push_back(res);
+
+ return res;
+}
+
+
+
+
+
+void cLuaServerHandle::OnAccepted(cTCPLink & a_Link)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Notify the plugin:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnAccepted"), static_cast<cLuaTCPLink *>(a_Link.GetCallbacks().get())))
+ {
+ LOGINFO("cNetwork server (port %d) OnAccepted callback failed in plugin %s, connection to %s:%d.",
+ m_Port, m_Plugin.GetName().c_str(), a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort()
+ );
+ return;
+ }
+}
+
+
+
+
+
+void cLuaServerHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Notify the plugin:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), a_ErrorCode, a_ErrorMsg))
+ {
+ LOGINFO("cNetwork server (port %d) OnError callback failed in plugin %s. The error is %d (%s).",
+ m_Port, m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str()
+ );
+ return;
+ }
+}
+
+
+
+
+
diff --git a/src/Bindings/LuaServerHandle.h b/src/Bindings/LuaServerHandle.h
new file mode 100644
index 000000000..9325bca3e
--- /dev/null
+++ b/src/Bindings/LuaServerHandle.h
@@ -0,0 +1,89 @@
+
+// LuaServerHandle.h
+
+// Declares the cLuaServerHandle class wrapping the cServerHandle functionality for Lua API
+
+
+
+
+
+#pragma once
+
+#include "../OSSupport/Network.h"
+#include "PluginLua.h"
+
+
+
+
+
+// fwd:
+class cLuaTCPLink;
+typedef SharedPtr<cLuaTCPLink> cLuaTCPLinkPtr;
+typedef std::vector<cLuaTCPLinkPtr> cLuaTCPLinkPtrs;
+class cLuaServerHandle;
+typedef SharedPtr<cLuaServerHandle> cLuaServerHandlePtr;
+
+
+
+
+class cLuaServerHandle:
+ public cNetwork::cListenCallbacks
+{
+public:
+ /** Creates a new instance of the server handle,
+ attached to the specified lua plugin and wrapping the (listen-) callbacks that are in a table at the specified stack pos. */
+ cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos);
+
+ ~cLuaServerHandle();
+
+ /** Called by cNetwork::Listen()'s binding.
+ Sets the server handle around which this instance is wrapped, and a self SharedPtr for link management. */
+ void SetServerHandle(cServerHandlePtr a_ServerHandle, cLuaServerHandlePtr a_Self);
+
+ /** Terminates all connections and closes the listening socket. */
+ void Close(void);
+
+ /** Returns true if the server is currently listening. */
+ bool IsListening(void);
+
+ /** Removes the link from the list of links that the server is currently tracking. */
+ void RemoveLink(cLuaTCPLink * a_Link);
+
+ /** Called when Lua garbage-collects the object.
+ Releases the internal SharedPtr to self, so that the instance may be deallocated. */
+ void Release(void);
+
+protected:
+ /** The plugin for which the server is created. */
+ cPluginLua & m_Plugin;
+
+ /** The Lua table that holds the callbacks to be invoked. */
+ cLuaState::cRef m_Callbacks;
+
+ /** The port on which the server is listening.
+ Used mainly for better error reporting. */
+ UInt16 m_Port;
+
+ /** The cServerHandle around which this instance is wrapped. */
+ cServerHandlePtr m_ServerHandle;
+
+ /** Protects m_Connections against multithreaded access. */
+ cCriticalSection m_CSConnections;
+
+ /** All connections that are currently active in this server.
+ Protected by m_CSConnections. */
+ cLuaTCPLinkPtrs m_Connections;
+
+ /** SharedPtr to self, given out to newly created links. */
+ cLuaServerHandlePtr m_Self;
+
+
+ // cNetwork::cListenCallbacks overrides:
+ virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override;
+ virtual void OnAccepted(cTCPLink & a_Link) override;
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
+};
+
+
+
+
diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp
index 01d3ac687..25c77a652 100644
--- a/src/Bindings/LuaState.cpp
+++ b/src/Bindings/LuaState.cpp
@@ -343,6 +343,18 @@ bool cLuaState::PushFunction(const cTableRef & a_TableRef)
+void cLuaState::PushNil(void)
+{
+ ASSERT(IsValid());
+
+ lua_pushnil(m_LuaState);
+ m_NumCurrentFunctionArgs += 1;
+}
+
+
+
+
+
void cLuaState::Push(const AString & a_String)
{
ASSERT(IsValid());
@@ -656,6 +668,42 @@ void cLuaState::Push(cItems * a_Items)
+void cLuaState::Push(cLuaServerHandle * a_ServerHandle)
+{
+ ASSERT(IsValid());
+
+ tolua_pushusertype(m_LuaState, a_ServerHandle, "cServerHandle");
+ m_NumCurrentFunctionArgs += 1;
+}
+
+
+
+
+
+void cLuaState::Push(cLuaTCPLink * a_TCPLink)
+{
+ ASSERT(IsValid());
+
+ tolua_pushusertype(m_LuaState, a_TCPLink, "cTCPLink");
+ m_NumCurrentFunctionArgs += 1;
+}
+
+
+
+
+
+void cLuaState::Push(cLuaUDPEndpoint * a_UDPEndpoint)
+{
+ ASSERT(IsValid());
+
+ tolua_pushusertype(m_LuaState, a_UDPEndpoint, "cUDPEndpoint");
+ m_NumCurrentFunctionArgs += 1;
+}
+
+
+
+
+
void cLuaState::Push(cMonster * a_Monster)
{
ASSERT(IsValid());
@@ -958,6 +1006,15 @@ void cLuaState::GetStackValue(int a_StackPos, pWorld & a_ReturnedVal)
+void cLuaState::GetStackValue(int a_StackPos, cRef & a_Ref)
+{
+ a_Ref.RefStack(*this, a_StackPos);
+}
+
+
+
+
+
bool cLuaState::CallFunction(int a_NumResults)
{
ASSERT (m_NumCurrentFunctionArgs >= 0); // A function must be pushed to stack first
@@ -1527,6 +1584,18 @@ cLuaState::cRef::cRef(cLuaState & a_LuaState, int a_StackPos) :
+cLuaState::cRef::cRef(cRef && a_FromRef):
+ m_LuaState(a_FromRef.m_LuaState),
+ m_Ref(a_FromRef.m_Ref)
+{
+ a_FromRef.m_LuaState = nullptr;
+ a_FromRef.m_Ref = LUA_REFNIL;
+}
+
+
+
+
+
cLuaState::cRef::~cRef()
{
if (m_LuaState != nullptr)
diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h
index 97e6b47e1..159483634 100644
--- a/src/Bindings/LuaState.h
+++ b/src/Bindings/LuaState.h
@@ -59,6 +59,9 @@ class cTNTEntity;
class cHopperEntity;
class cBlockEntity;
class cBoundingBox;
+class cLuaTCPLink;
+class cLuaServerHandle;
+class cLuaUDPEndpoint;
typedef cBoundingBox * pBoundingBox;
typedef cWorld * pWorld;
@@ -83,6 +86,10 @@ public:
/** Creates a reference in the specified LuaState for object at the specified StackPos */
cRef(cLuaState & a_LuaState, int a_StackPos);
+
+ /** Moves the reference from the specified instance into a newly created instance.
+ The old instance is then "!IsValid()". */
+ cRef(cRef && a_FromRef);
~cRef();
@@ -178,6 +185,8 @@ public:
/** Returns true if a_FunctionName is a valid Lua function that can be called */
bool HasFunction(const char * a_FunctionName);
+ void PushNil(void);
+
// Push a const value onto the stack (keep alpha-sorted):
void Push(const AString & a_String);
void Push(const AStringVector & a_Vector);
@@ -202,6 +211,9 @@ public:
void Push(cHopperEntity * a_Hopper);
void Push(cItem * a_Item);
void Push(cItems * a_Items);
+ void Push(cLuaServerHandle * a_ServerHandle);
+ void Push(cLuaTCPLink * a_TCPLink);
+ void Push(cLuaUDPEndpoint * a_UDPEndpoint);
void Push(cMonster * a_Monster);
void Push(cPickup * a_Pickup);
void Push(cPlayer * a_Player);
@@ -240,6 +252,9 @@ public:
/** Retrieve value at a_StackPos, if it is a valid cWorld class. If not, a_Value is unchanged */
void GetStackValue(int a_StackPos, pWorld & a_Value);
+
+ /** Store the value at a_StackPos as a reference. */
+ void GetStackValue(int a_StackPos, cRef & a_Ref);
/** Call the specified Lua function.
Returns true if call succeeded, false if there was an error.
diff --git a/src/Bindings/LuaTCPLink.cpp b/src/Bindings/LuaTCPLink.cpp
new file mode 100644
index 000000000..d88c41120
--- /dev/null
+++ b/src/Bindings/LuaTCPLink.cpp
@@ -0,0 +1,610 @@
+
+// LuaTCPLink.cpp
+
+// Implements the cLuaTCPLink class representing a Lua wrapper for the cTCPLink class and the callbacks it needs
+
+#include "Globals.h"
+#include "LuaTCPLink.h"
+#include "LuaServerHandle.h"
+
+
+
+
+
+cLuaTCPLink::cLuaTCPLink(cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
+ m_Plugin(a_Plugin),
+ m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos)
+{
+ // Warn if the callbacks aren't valid:
+ if (!m_Callbacks.IsValid())
+ {
+ LOGWARNING("cTCPLink in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str());
+ cPluginLua::cOperation Op(m_Plugin);
+ Op().LogStackTrace();
+ }
+}
+
+
+
+
+
+cLuaTCPLink::cLuaTCPLink(cPluginLua & a_Plugin, cLuaState::cRef && a_CallbacksTableRef, cLuaServerHandleWPtr a_ServerHandle):
+ m_Plugin(a_Plugin),
+ m_Callbacks(std::move(a_CallbacksTableRef)),
+ m_Server(std::move(a_ServerHandle))
+{
+ // Warn if the callbacks aren't valid:
+ if (!m_Callbacks.IsValid())
+ {
+ LOGWARNING("cTCPLink in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str());
+ cPluginLua::cOperation Op(m_Plugin);
+ Op().LogStackTrace();
+ }
+}
+
+
+
+
+
+cLuaTCPLink::~cLuaTCPLink()
+{
+ // If the link is still open, close it:
+ cTCPLinkPtr Link = m_Link;
+ if (Link != nullptr)
+ {
+ Link->Close();
+ }
+
+ Terminated();
+}
+
+
+
+
+
+bool cLuaTCPLink::Send(const AString & a_Data)
+{
+ // If running in SSL mode, push the data into the SSL context instead:
+ if (m_SslContext != nullptr)
+ {
+ m_SslContext->Send(a_Data);
+ return true;
+ }
+
+ // Safely grab a copy of the link:
+ cTCPLinkPtr Link = m_Link;
+ if (Link == nullptr)
+ {
+ return false;
+ }
+
+ // Send the data:
+ return Link->Send(a_Data);
+}
+
+
+
+
+
+AString cLuaTCPLink::GetLocalIP(void) const
+{
+ // Safely grab a copy of the link:
+ cTCPLinkPtr Link = m_Link;
+ if (Link == nullptr)
+ {
+ return "";
+ }
+
+ // Get the IP address:
+ return Link->GetLocalIP();
+}
+
+
+
+
+
+UInt16 cLuaTCPLink::GetLocalPort(void) const
+{
+ // Safely grab a copy of the link:
+ cTCPLinkPtr Link = m_Link;
+ if (Link == nullptr)
+ {
+ return 0;
+ }
+
+ // Get the port:
+ return Link->GetLocalPort();
+}
+
+
+
+
+
+AString cLuaTCPLink::GetRemoteIP(void) const
+{
+ // Safely grab a copy of the link:
+ cTCPLinkPtr Link = m_Link;
+ if (Link == nullptr)
+ {
+ return "";
+ }
+
+ // Get the IP address:
+ return Link->GetRemoteIP();
+}
+
+
+
+
+
+UInt16 cLuaTCPLink::GetRemotePort(void) const
+{
+ // Safely grab a copy of the link:
+ cTCPLinkPtr Link = m_Link;
+ if (Link == nullptr)
+ {
+ return 0;
+ }
+
+ // Get the port:
+ return Link->GetRemotePort();
+}
+
+
+
+
+
+void cLuaTCPLink::Shutdown(void)
+{
+ // Safely grab a copy of the link and shut it down:
+ cTCPLinkPtr Link = m_Link;
+ if (Link != nullptr)
+ {
+ if (m_SslContext != nullptr)
+ {
+ m_SslContext->NotifyClose();
+ m_SslContext->ResetSelf();
+ m_SslContext.reset();
+ }
+ Link->Shutdown();
+ }
+}
+
+
+
+
+
+void cLuaTCPLink::Close(void)
+{
+ // If the link is still open, close it:
+ cTCPLinkPtr Link = m_Link;
+ if (Link != nullptr)
+ {
+ if (m_SslContext != nullptr)
+ {
+ m_SslContext->NotifyClose();
+ m_SslContext->ResetSelf();
+ m_SslContext.reset();
+ }
+ Link->Close();
+ }
+
+ Terminated();
+}
+
+
+
+
+
+AString cLuaTCPLink::StartTLSClient(
+ const AString & a_OwnCertData,
+ const AString & a_OwnPrivKeyData,
+ const AString & a_OwnPrivKeyPassword
+)
+{
+ // Check preconditions:
+ if (m_SslContext != nullptr)
+ {
+ return "TLS is already active on this link";
+ }
+ if (
+ (a_OwnCertData.empty() && !a_OwnPrivKeyData.empty()) ||
+ (!a_OwnCertData.empty() && a_OwnPrivKeyData.empty())
+ )
+ {
+ return "Either provide both the certificate and private key, or neither";
+ }
+
+ // Create the SSL context:
+ m_SslContext.reset(new cLinkSslContext(*this));
+ m_SslContext->Initialize(true);
+
+ // Create the peer cert, if required:
+ if (!a_OwnCertData.empty() && !a_OwnPrivKeyData.empty())
+ {
+ auto OwnCert = std::make_shared<cX509Cert>();
+ int res = OwnCert->Parse(a_OwnCertData.data(), a_OwnCertData.size());
+ if (res != 0)
+ {
+ m_SslContext.reset();
+ return Printf("Cannot parse peer certificate: -0x%x", res);
+ }
+ auto OwnPrivKey = std::make_shared<cCryptoKey>();
+ res = OwnPrivKey->ParsePrivate(a_OwnPrivKeyData.data(), a_OwnPrivKeyData.size(), a_OwnPrivKeyPassword);
+ if (res != 0)
+ {
+ m_SslContext.reset();
+ return Printf("Cannot parse peer private key: -0x%x", res);
+ }
+ m_SslContext->SetOwnCert(OwnCert, OwnPrivKey);
+ }
+ m_SslContext->SetSelf(cLinkSslContextWPtr(m_SslContext));
+
+ // Start the handshake:
+ m_SslContext->Handshake();
+ return "";
+}
+
+
+
+
+
+AString cLuaTCPLink::StartTLSServer(
+ const AString & a_OwnCertData,
+ const AString & a_OwnPrivKeyData,
+ const AString & a_OwnPrivKeyPassword,
+ const AString & a_StartTLSData
+)
+{
+ // Check preconditions:
+ if (m_SslContext != nullptr)
+ {
+ return "TLS is already active on this link";
+ }
+ if (a_OwnCertData.empty() || a_OwnPrivKeyData.empty())
+ {
+ return "Provide the server certificate and private key";
+ }
+
+ // Create the SSL context:
+ m_SslContext.reset(new cLinkSslContext(*this));
+ m_SslContext->Initialize(false);
+
+ // Create the peer cert:
+ auto OwnCert = std::make_shared<cX509Cert>();
+ int res = OwnCert->Parse(a_OwnCertData.data(), a_OwnCertData.size());
+ if (res != 0)
+ {
+ m_SslContext.reset();
+ return Printf("Cannot parse server certificate: -0x%x", res);
+ }
+ auto OwnPrivKey = std::make_shared<cCryptoKey>();
+ res = OwnPrivKey->ParsePrivate(a_OwnPrivKeyData.data(), a_OwnPrivKeyData.size(), a_OwnPrivKeyPassword);
+ if (res != 0)
+ {
+ m_SslContext.reset();
+ return Printf("Cannot parse server private key: -0x%x", res);
+ }
+ m_SslContext->SetOwnCert(OwnCert, OwnPrivKey);
+ m_SslContext->SetSelf(cLinkSslContextWPtr(m_SslContext));
+
+ // Push the initial data:
+ m_SslContext->StoreReceivedData(a_StartTLSData.data(), a_StartTLSData.size());
+
+ // Start the handshake:
+ m_SslContext->Handshake();
+ return "";
+}
+
+
+
+
+
+void cLuaTCPLink::Terminated(void)
+{
+ // Disable the callbacks:
+ if (m_Callbacks.IsValid())
+ {
+ m_Callbacks.UnRef();
+ }
+
+ // If the managing server is still alive, let it know we're terminating:
+ auto Server = m_Server.lock();
+ if (Server != nullptr)
+ {
+ Server->RemoveLink(this);
+ }
+
+ // If the link is still open, close it:
+ {
+ cTCPLinkPtr Link = m_Link;
+ if (Link != nullptr)
+ {
+ Link->Close();
+ m_Link.reset();
+ }
+ }
+
+ // If the SSL context still exists, free it:
+ m_SslContext.reset();
+}
+
+
+
+
+
+void cLuaTCPLink::ReceivedCleartextData(const char * a_Data, size_t a_NumBytes)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_NumBytes)))
+ {
+ LOGINFO("cTCPLink OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str());
+ }
+}
+
+
+
+
+
+void cLuaTCPLink::OnConnected(cTCPLink & a_Link)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnConnected"), this))
+ {
+ LOGINFO("cTCPLink OnConnected() callback failed in plugin %s.", m_Plugin.GetName().c_str());
+ }
+}
+
+
+
+
+
+void cLuaTCPLink::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), this, a_ErrorCode, a_ErrorMsg))
+ {
+ LOGINFO("cTCPLink OnError() callback failed in plugin %s; the link error is %d (%s).",
+ m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str()
+ );
+ }
+
+ Terminated();
+}
+
+
+
+
+
+void cLuaTCPLink::OnLinkCreated(cTCPLinkPtr a_Link)
+{
+ // Store the cTCPLink for later use:
+ m_Link = a_Link;
+}
+
+
+
+
+
+void cLuaTCPLink::OnReceivedData(const char * a_Data, size_t a_Length)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // If we're running in SSL mode, put the data into the SSL decryptor:
+ if (m_SslContext != nullptr)
+ {
+ m_SslContext->StoreReceivedData(a_Data, a_Length);
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_Length)))
+ {
+ LOGINFO("cTCPLink OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str());
+ }
+}
+
+
+
+
+
+void cLuaTCPLink::OnRemoteClosed(void)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnRemoteClosed"), this))
+ {
+ LOGINFO("cTCPLink OnRemoteClosed() callback failed in plugin %s.", m_Plugin.GetName().c_str());
+ }
+
+ Terminated();
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cLuaTCPLink::cLinkSslContext:
+
+cLuaTCPLink::cLinkSslContext::cLinkSslContext(cLuaTCPLink & a_Link):
+ m_Link(a_Link)
+{
+}
+
+
+
+
+
+void cLuaTCPLink::cLinkSslContext::SetSelf(cLinkSslContextWPtr a_Self)
+{
+ m_Self = a_Self;
+}
+
+
+
+
+
+void cLuaTCPLink::cLinkSslContext::ResetSelf(void)
+{
+ m_Self.reset();
+}
+
+
+
+
+
+void cLuaTCPLink::cLinkSslContext::StoreReceivedData(const char * a_Data, size_t a_NumBytes)
+{
+ // Hold self alive for the duration of this function
+ cLinkSslContextPtr Self(m_Self);
+
+ m_EncryptedData.append(a_Data, a_NumBytes);
+
+ // Try to finish a pending handshake:
+ TryFinishHandshaking();
+
+ // Flush any cleartext data that can be "received":
+ FlushBuffers();
+}
+
+
+
+
+
+void cLuaTCPLink::cLinkSslContext::FlushBuffers(void)
+{
+ // Hold self alive for the duration of this function
+ cLinkSslContextPtr Self(m_Self);
+
+ // If the handshake didn't complete yet, bail out:
+ if (!HasHandshaken())
+ {
+ return;
+ }
+
+ char Buffer[1024];
+ int NumBytes;
+ while ((NumBytes = ReadPlain(Buffer, sizeof(Buffer))) > 0)
+ {
+ m_Link.ReceivedCleartextData(Buffer, static_cast<size_t>(NumBytes));
+ if (m_Self.expired())
+ {
+ // The callback closed the SSL context, bail out
+ return;
+ }
+ }
+}
+
+
+
+
+
+void cLuaTCPLink::cLinkSslContext::TryFinishHandshaking(void)
+{
+ // Hold self alive for the duration of this function
+ cLinkSslContextPtr Self(m_Self);
+
+ // If the handshake hasn't finished yet, retry:
+ if (!HasHandshaken())
+ {
+ Handshake();
+ }
+
+ // If the handshake succeeded, write all the queued plaintext data:
+ if (HasHandshaken())
+ {
+ WritePlain(m_CleartextData.data(), m_CleartextData.size());
+ m_CleartextData.clear();
+ }
+}
+
+
+
+
+
+void cLuaTCPLink::cLinkSslContext::Send(const AString & a_Data)
+{
+ // Hold self alive for the duration of this function
+ cLinkSslContextPtr Self(m_Self);
+
+ // If the handshake hasn't completed yet, queue the data:
+ if (!HasHandshaken())
+ {
+ m_CleartextData.append(a_Data);
+ TryFinishHandshaking();
+ return;
+ }
+
+ // The connection is all set up, write the cleartext data into the SSL context:
+ WritePlain(a_Data.data(), a_Data.size());
+ FlushBuffers();
+}
+
+
+
+
+
+int cLuaTCPLink::cLinkSslContext::ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes)
+{
+ // Hold self alive for the duration of this function
+ cLinkSslContextPtr Self(m_Self);
+
+ // If there's nothing queued in the buffer, report empty buffer:
+ if (m_EncryptedData.empty())
+ {
+ return POLARSSL_ERR_NET_WANT_READ;
+ }
+
+ // Copy as much data as possible to the provided buffer:
+ size_t BytesToCopy = std::min(a_NumBytes, m_EncryptedData.size());
+ memcpy(a_Buffer, m_EncryptedData.data(), BytesToCopy);
+ m_EncryptedData.erase(0, BytesToCopy);
+ return static_cast<int>(BytesToCopy);
+}
+
+
+
+
+
+int cLuaTCPLink::cLinkSslContext::SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes)
+{
+ m_Link.m_Link->Send(a_Buffer, a_NumBytes);
+ return static_cast<int>(a_NumBytes);
+}
+
+
+
+
diff --git a/src/Bindings/LuaTCPLink.h b/src/Bindings/LuaTCPLink.h
new file mode 100644
index 000000000..c8ae776fe
--- /dev/null
+++ b/src/Bindings/LuaTCPLink.h
@@ -0,0 +1,181 @@
+
+// LuaTCPLink.h
+
+// Declares the cLuaTCPLink class representing a Lua wrapper for the cTCPLink class and the callbacks it needs
+
+
+
+
+
+#pragma once
+
+#include "../OSSupport/Network.h"
+#include "PluginLua.h"
+#include "../PolarSSL++/SslContext.h"
+
+
+
+
+
+// fwd:
+class cLuaServerHandle;
+typedef WeakPtr<cLuaServerHandle> cLuaServerHandleWPtr;
+
+
+
+
+
+class cLuaTCPLink:
+ public cNetwork::cConnectCallbacks,
+ public cTCPLink::cCallbacks
+{
+public:
+ /** Creates a new instance of the link, attached to the specified plugin and wrapping the callbacks that are in a table at the specified stack pos. */
+ cLuaTCPLink(cPluginLua & a_Plugin, int a_CallbacksTableStackPos);
+
+ /** Creates a new instance of the link, attached to the specified plugin and wrapping the callbacks that are in the specified referenced table. */
+ cLuaTCPLink(cPluginLua & a_Plugin, cLuaState::cRef && a_CallbacksTableRef, cLuaServerHandleWPtr a_Server);
+
+ ~cLuaTCPLink();
+
+ /** Sends the data contained in the string to the remote peer.
+ Returns true if successful, false on immediate failure (queueing the data failed or link not available). */
+ bool Send(const AString & a_Data);
+
+ /** Returns the IP address of the local endpoint of the connection. */
+ AString GetLocalIP(void) const;
+
+ /** Returns the port used by the local endpoint of the connection. */
+ UInt16 GetLocalPort(void) const;
+
+ /** Returns the IP address of the remote endpoint of the connection. */
+ AString GetRemoteIP(void) const;
+
+ /** Returns the port used by the remote endpoint of the connection. */
+ UInt16 GetRemotePort(void) const;
+
+ /** Closes the link gracefully.
+ The link will send any queued outgoing data, then it will send the FIN packet.
+ The link will still receive incoming data from remote until the remote closes the connection. */
+ void Shutdown(void);
+
+ /** Drops the connection without any more processing.
+ Sends the RST packet, queued outgoing and incoming data is lost. */
+ void Close(void);
+
+ /** Starts a TLS handshake as a client connection.
+ If a client certificate should be used for the connection, set the certificate into a_OwnCertData and
+ its corresponding private key to a_OwnPrivKeyData. If both are empty, no client cert is presented.
+ a_OwnPrivKeyPassword is the password to be used for decoding PrivKey, empty if not passworded.
+ Returns empty string on success, non-empty error description on failure. */
+ AString StartTLSClient(
+ const AString & a_OwnCertData,
+ const AString & a_OwnPrivKeyData,
+ const AString & a_OwnPrivKeyPassword
+ );
+
+ /** Starts a TLS handshake as a server connection.
+ Set the server certificate into a_CertData and its corresponding private key to a_OwnPrivKeyData.
+ a_OwnPrivKeyPassword is the password to be used for decoding PrivKey, empty if not passworded.
+ a_StartTLSData is any data that should be pushed into the TLS before reading more data from the remote.
+ This is used mainly for protocols starting TLS in the middle of communication, when the TLS start command
+ can be received together with the TLS Client Hello message in one OnReceivedData() call, to re-queue the
+ Client Hello message into the TLS handshake buffer.
+ Returns empty string on success, non-empty error description on failure. */
+ AString StartTLSServer(
+ const AString & a_OwnCertData,
+ const AString & a_OwnPrivKeyData,
+ const AString & a_OwnPrivKeyPassword,
+ const AString & a_StartTLSData
+ );
+
+protected:
+ // fwd:
+ class cLinkSslContext;
+ typedef SharedPtr<cLinkSslContext> cLinkSslContextPtr;
+ typedef WeakPtr<cLinkSslContext> cLinkSslContextWPtr;
+
+ /** Wrapper around cSslContext that is used when this link is being encrypted by SSL. */
+ class cLinkSslContext :
+ public cSslContext
+ {
+ cLuaTCPLink & m_Link;
+
+ /** Buffer for storing the incoming encrypted data until it is requested by the SSL decryptor. */
+ AString m_EncryptedData;
+
+ /** Buffer for storing the outgoing cleartext data until the link has finished handshaking. */
+ AString m_CleartextData;
+
+ /** Shared ownership of self, so that this object can keep itself alive for as long as it needs. */
+ cLinkSslContextWPtr m_Self;
+
+ public:
+ cLinkSslContext(cLuaTCPLink & a_Link);
+
+ /** Shares ownership of self, so that this object can keep itself alive for as long as it needs. */
+ void SetSelf(cLinkSslContextWPtr a_Self);
+
+ /** Removes the self ownership so that we can detect the SSL closure. */
+ void ResetSelf(void);
+
+ /** Stores the specified block of data into the buffer of the data to be decrypted (incoming from remote).
+ Also flushes the SSL buffers by attempting to read any data through the SSL context. */
+ void StoreReceivedData(const char * a_Data, size_t a_NumBytes);
+
+ /** Tries to read any cleartext data available through the SSL, reports it in the link. */
+ void FlushBuffers(void);
+
+ /** Tries to finish handshaking the SSL. */
+ void TryFinishHandshaking(void);
+
+ /** Sends the specified cleartext data over the SSL to the remote peer.
+ If the handshake hasn't been completed yet, queues the data for sending when it completes. */
+ void Send(const AString & a_Data);
+
+ // cSslContext overrides:
+ virtual int ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) override;
+ virtual int SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) override;
+ };
+
+
+ /** The plugin for which the link is created. */
+ cPluginLua & m_Plugin;
+
+ /** The Lua table that holds the callbacks to be invoked. */
+ cLuaState::cRef m_Callbacks;
+
+ /** The underlying link representing the connection.
+ May be nullptr. */
+ cTCPLinkPtr m_Link;
+
+ /** The server that is responsible for this link, if any. */
+ cLuaServerHandleWPtr m_Server;
+
+ /** The SSL context used for encryption, if this link uses SSL.
+ If valid, the link uses encryption through this context. */
+ cLinkSslContextPtr m_SslContext;
+
+
+ /** Common code called when the link is considered as terminated.
+ Releases m_Link, m_Callbacks and this from m_Server, each when applicable. */
+ void Terminated(void);
+
+ /** Called by the SSL context when there's incoming data available in the cleartext.
+ Reports the data via the Lua callback function. */
+ void ReceivedCleartextData(const char * a_Data, size_t a_NumBytes);
+
+ // cNetwork::cConnectCallbacks overrides:
+ virtual void OnConnected(cTCPLink & a_Link) override;
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
+
+ // cTCPLink::cCallbacks overrides:
+ virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
+ virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;
+ virtual void OnRemoteClosed(void) override;
+ // The OnError() callback is shared with cNetwork::cConnectCallbacks
+};
+
+
+
+
diff --git a/src/Bindings/LuaUDPEndpoint.cpp b/src/Bindings/LuaUDPEndpoint.cpp
new file mode 100644
index 000000000..8637eeb4e
--- /dev/null
+++ b/src/Bindings/LuaUDPEndpoint.cpp
@@ -0,0 +1,221 @@
+
+// LuaUDPEndpoint.cpp
+
+// Implements the cLuaUDPEndpoint class representing a Lua wrapper for the cUDPEndpoint class and the callbacks it needs
+
+#include "Globals.h"
+#include "LuaUDPEndpoint.h"
+
+
+
+
+
+cLuaUDPEndpoint::cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos):
+ m_Plugin(a_Plugin),
+ m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos)
+{
+ // Warn if the callbacks aren't valid:
+ if (!m_Callbacks.IsValid())
+ {
+ LOGWARNING("cLuaUDPEndpoint in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str());
+ cPluginLua::cOperation Op(m_Plugin);
+ Op().LogStackTrace();
+ }
+}
+
+
+
+
+
+cLuaUDPEndpoint::~cLuaUDPEndpoint()
+{
+ // If the endpoint is still open, close it:
+ cUDPEndpointPtr Endpoint = m_Endpoint;
+ if (Endpoint != nullptr)
+ {
+ Endpoint->Close();
+ }
+
+ Terminated();
+}
+
+
+
+
+
+bool cLuaUDPEndpoint::Open(UInt16 a_Port, cLuaUDPEndpointPtr a_Self)
+{
+ ASSERT(m_Self == nullptr); // Must not be opened yet
+ ASSERT(m_Endpoint == nullptr);
+
+ m_Self = a_Self;
+ m_Endpoint = cNetwork::CreateUDPEndpoint(a_Port, *this);
+ return m_Endpoint->IsOpen();
+}
+
+
+
+
+
+bool cLuaUDPEndpoint::Send(const AString & a_Data, const AString & a_RemotePeer, UInt16 a_RemotePort)
+{
+ // Safely grab a copy of the endpoint:
+ cUDPEndpointPtr Endpoint = m_Endpoint;
+ if (Endpoint == nullptr)
+ {
+ return false;
+ }
+
+ // Send the data:
+ return Endpoint->Send(a_Data, a_RemotePeer, a_RemotePort);
+}
+
+
+
+
+
+UInt16 cLuaUDPEndpoint::GetPort(void) const
+{
+ // Safely grab a copy of the endpoint:
+ cUDPEndpointPtr Endpoint = m_Endpoint;
+ if (Endpoint == nullptr)
+ {
+ return 0;
+ }
+
+ // Get the port:
+ return Endpoint->GetPort();
+}
+
+
+
+
+
+bool cLuaUDPEndpoint::IsOpen(void) const
+{
+ // Safely grab a copy of the endpoint:
+ cUDPEndpointPtr Endpoint = m_Endpoint;
+ if (Endpoint == nullptr)
+ {
+ // No endpoint means that we're not open
+ return false;
+ }
+
+ // Get the state:
+ return Endpoint->IsOpen();
+}
+
+
+
+
+
+void cLuaUDPEndpoint::Close(void)
+{
+ // If the endpoint is still open, close it:
+ cUDPEndpointPtr Endpoint = m_Endpoint;
+ if (Endpoint != nullptr)
+ {
+ Endpoint->Close();
+ m_Endpoint.reset();
+ }
+
+ Terminated();
+}
+
+
+
+
+
+void cLuaUDPEndpoint::EnableBroadcasts(void)
+{
+ // Safely grab a copy of the endpoint:
+ cUDPEndpointPtr Endpoint = m_Endpoint;
+ if (Endpoint != nullptr)
+ {
+ Endpoint->EnableBroadcasts();
+ }
+}
+
+
+
+
+
+void cLuaUDPEndpoint::Release(void)
+{
+ // Close the endpoint, if not already closed:
+ Close();
+
+ // Allow self to deallocate:
+ m_Self.reset();
+}
+
+
+
+
+
+void cLuaUDPEndpoint::Terminated(void)
+{
+ // Disable the callbacks:
+ if (m_Callbacks.IsValid())
+ {
+ m_Callbacks.UnRef();
+ }
+
+ // If the endpoint is still open, close it:
+ {
+ cUDPEndpointPtr Endpoint = m_Endpoint;
+ if (Endpoint != nullptr)
+ {
+ Endpoint->Close();
+ m_Endpoint.reset();
+ }
+ }
+}
+
+
+
+
+
+void cLuaUDPEndpoint::OnReceivedData(const char * a_Data, size_t a_NumBytes, const AString & a_RemotePeer, UInt16 a_RemotePort)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_NumBytes), a_RemotePeer, a_RemotePort))
+ {
+ LOGINFO("cUDPEndpoint OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str());
+ }
+}
+
+
+
+
+
+void cLuaUDPEndpoint::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
+{
+ // Check if we're still valid:
+ if (!m_Callbacks.IsValid())
+ {
+ return;
+ }
+
+ // Call the callback:
+ cPluginLua::cOperation Op(m_Plugin);
+ if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), a_ErrorCode, a_ErrorMsg))
+ {
+ LOGINFO("cUDPEndpoint OnError() callback failed in plugin %s; the endpoint error is %d (%s).",
+ m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str()
+ );
+ }
+
+ Terminated();
+}
+
+
+
+
diff --git a/src/Bindings/LuaUDPEndpoint.h b/src/Bindings/LuaUDPEndpoint.h
new file mode 100644
index 000000000..0587491ab
--- /dev/null
+++ b/src/Bindings/LuaUDPEndpoint.h
@@ -0,0 +1,86 @@
+
+// LuaUDPEndpoint.h
+
+// Declares the cLuaUDPEndpoint class representing a Lua wrapper for the cUDPEndpoint class and the callbacks it needs
+
+
+
+
+
+#pragma once
+
+#include "../OSSupport/Network.h"
+#include "PluginLua.h"
+
+
+
+
+
+// fwd:
+class cLuaUDPEndpoint;
+typedef SharedPtr<cLuaUDPEndpoint> cLuaUDPEndpointPtr;
+
+
+
+
+
+class cLuaUDPEndpoint:
+ public cUDPEndpoint::cCallbacks
+{
+public:
+ /** Creates a new instance of the endpoint, attached to the specified plugin and wrapping the callbacks that are in a table at the specified stack pos. */
+ cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos);
+
+ ~cLuaUDPEndpoint();
+
+ /** Opens the endpoint so that it starts listening for incoming data on the specified port.
+ a_Self is the shared pointer to self that the object keeps to keep itself alive for as long as it needs (for Lua). */
+ bool Open(UInt16 a_Port, cLuaUDPEndpointPtr a_Self);
+
+ /** Sends the data contained in the string to the specified remote peer.
+ Returns true if successful, false on immediate failure (queueing the data failed etc.) */
+ bool Send(const AString & a_Data, const AString & a_RemotePeer, UInt16 a_RemotePort);
+
+ /** Returns the port on which endpoint is listening for incoming data. */
+ UInt16 GetPort(void) const;
+
+ /** Returns true if the endpoint is open for incoming data. */
+ bool IsOpen(void) const;
+
+ /** Closes the UDP listener. */
+ void Close(void);
+
+ /** Enables outgoing broadcasts to be made using this endpoint. */
+ void EnableBroadcasts(void);
+
+ /** Called when Lua garbage-collects the object.
+ Releases the internal SharedPtr to self, so that the instance may be deallocated. */
+ void Release(void);
+
+protected:
+ /** The plugin for which the link is created. */
+ cPluginLua & m_Plugin;
+
+ /** The Lua table that holds the callbacks to be invoked. */
+ cLuaState::cRef m_Callbacks;
+
+ /** SharedPtr to self, so that the object can keep itself alive for as long as it needs (for Lua). */
+ cLuaUDPEndpointPtr m_Self;
+
+ /** The underlying network endpoint.
+ May be nullptr. */
+ cUDPEndpointPtr m_Endpoint;
+
+
+ /** Common code called when the endpoint is considered as terminated.
+ Releases m_Endpoint and m_Callbacks, each when applicable. */
+ void Terminated(void);
+
+ // cUDPEndpoint::cCallbacks overrides:
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
+ virtual void OnReceivedData(const char * a_Data, size_t a_Size, const AString & a_RemotePeer, UInt16 a_RemotePort) override;
+};
+
+
+
+
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index 56f2e73bc..cac81f325 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -3,8 +3,11 @@
#include "ManualBindings.h"
#undef TOLUA_TEMPLATE_BIND
+#include <sstream>
+#include <iomanip>
#include "tolua++/include/tolua++.h"
#include "polarssl/md5.h"
+#include "polarssl/sha1.h"
#include "PluginLua.h"
#include "PluginManager.h"
#include "LuaWindow.h"
@@ -28,6 +31,7 @@
#include "../LineBlockTracer.h"
#include "../WorldStorage/SchematicFileSerializer.h"
#include "../CompositeChat.h"
+#include "../StringCompression.h"
@@ -107,6 +111,146 @@ static int tolua_Clamp(lua_State * tolua_S)
+static int tolua_CompressStringZLIB(lua_State * tolua_S)
+{
+ cLuaState S(tolua_S);
+ if (
+ !S.CheckParamString(1) ||
+ (
+ !S.CheckParamNumber(2) &&
+ !S.CheckParamEnd(2)
+ )
+ )
+ {
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
+ // Get the params:
+ AString ToCompress;
+ int CompressionLevel = 5;
+ S.GetStackValues(1, ToCompress, CompressionLevel);
+
+ // Compress the string:
+ AString res;
+ CompressString(ToCompress.data(), ToCompress.size(), res, CompressionLevel);
+ S.Push(res);
+ return 1;
+}
+
+
+
+
+
+static int tolua_UncompressStringZLIB(lua_State * tolua_S)
+{
+ cLuaState S(tolua_S);
+ if (
+ !S.CheckParamString(1) ||
+ !S.CheckParamNumber(2)
+ )
+ {
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
+ // Get the params:
+ AString ToUncompress;
+ int UncompressedSize;
+ S.GetStackValues(1, ToUncompress, UncompressedSize);
+
+ // Compress the string:
+ AString res;
+ UncompressString(ToUncompress.data(), ToUncompress.size(), res, UncompressedSize);
+ S.Push(res);
+ return 1;
+}
+
+
+
+
+
+static int tolua_CompressStringGZIP(lua_State * tolua_S)
+{
+ cLuaState S(tolua_S);
+ if (
+ !S.CheckParamString(1) ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
+ // Get the params:
+ AString ToCompress;
+ S.GetStackValues(1, ToCompress);
+
+ // Compress the string:
+ AString res;
+ CompressStringGZIP(ToCompress.data(), ToCompress.size(), res);
+ S.Push(res);
+ return 1;
+}
+
+
+
+
+
+static int tolua_UncompressStringGZIP(lua_State * tolua_S)
+{
+ cLuaState S(tolua_S);
+ if (
+ !S.CheckParamString(1) ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
+ // Get the params:
+ AString ToUncompress;
+ S.GetStackValues(1, ToUncompress);
+
+ // Compress the string:
+ AString res;
+ UncompressStringGZIP(ToUncompress.data(), ToUncompress.size(), res);
+ S.Push(res);
+ return 1;
+}
+
+
+
+
+
+static int tolua_InflateString(lua_State * tolua_S)
+{
+ cLuaState S(tolua_S);
+ if (
+ !S.CheckParamString(1) ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
+ // Get the params:
+ AString ToUncompress;
+ S.GetStackValues(1, ToUncompress);
+
+ // Compress the string:
+ AString res;
+ InflateString(ToUncompress.data(), ToUncompress.size(), res);
+ S.Push(res);
+ return 1;
+}
+
+
+
+
+
static int tolua_StringSplit(lua_State * tolua_S)
{
cLuaState LuaState(tolua_S);
@@ -165,6 +309,14 @@ static AString GetLogMessage(lua_State * tolua_S)
static int tolua_LOG(lua_State * tolua_S)
{
+ // If there's no param, spit out an error message instead of crashing:
+ if (lua_isnil(tolua_S, 1))
+ {
+ LOGWARNING("Attempting to LOG a nil value!");
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
// If the param is a cCompositeChat, read the log level from it:
cLogger::eLogLevel LogLevel = cLogger::llRegular;
tolua_Error err;
@@ -184,6 +336,14 @@ static int tolua_LOG(lua_State * tolua_S)
static int tolua_LOGINFO(lua_State * tolua_S)
{
+ // If there's no param, spit out an error message instead of crashing:
+ if (lua_isnil(tolua_S, 1))
+ {
+ LOGWARNING("Attempting to LOGINFO a nil value!");
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llInfo);
return 0;
}
@@ -194,6 +354,14 @@ static int tolua_LOGINFO(lua_State * tolua_S)
static int tolua_LOGWARN(lua_State * tolua_S)
{
+ // If there's no param, spit out an error message instead of crashing:
+ if (lua_isnil(tolua_S, 1))
+ {
+ LOGWARNING("Attempting to LOGWARN a nil value!");
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llWarning);
return 0;
}
@@ -204,6 +372,14 @@ static int tolua_LOGWARN(lua_State * tolua_S)
static int tolua_LOGERROR(lua_State * tolua_S)
{
+ // If there's no param, spit out an error message instead of crashing:
+ if (lua_isnil(tolua_S, 1))
+ {
+ LOGWARNING("Attempting to LOGERROR a nil value!");
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+ }
+
cLogger::GetInstance().LogSimple(GetLogMessage(tolua_S).c_str(), cLogger::llError);
return 0;
}
@@ -256,7 +432,7 @@ static int tolua_Base64Decode(lua_State * tolua_S)
-static cPluginLua * GetLuaPlugin(lua_State * L)
+cPluginLua * GetLuaPlugin(lua_State * L)
{
// Get the plugin identification out of LuaState:
lua_getglobal(L, LUA_PLUGIN_INSTANCE_VAR_NAME);
@@ -2171,11 +2347,16 @@ static int tolua_cPlugin_Call(lua_State * tolua_S)
-static int tolua_md5(lua_State* tolua_S)
+static int tolua_md5(lua_State * tolua_S)
{
+ // Calculate the raw md5 checksum byte array:
unsigned char Output[16];
size_t len = 0;
const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len);
+ if (SourceString == nullptr)
+ {
+ return 0;
+ }
md5(SourceString, len, Output);
lua_pushlstring(tolua_S, (const char *)Output, ARRAYCOUNT(Output));
return 1;
@@ -2185,6 +2366,91 @@ static int tolua_md5(lua_State* tolua_S)
+/** Does the same as tolua_md5, but reports that the usage is obsolete and the plugin should use cCrypto.md5(). */
+static int tolua_md5_obsolete(lua_State * tolua_S)
+{
+ LOGWARNING("Using md5() is obsolete, please change your plugin to use cCryptoHash.md5()");
+ cLuaState::LogStackTrace(tolua_S);
+ return tolua_md5(tolua_S);
+}
+
+
+
+
+
+static int tolua_md5HexString(lua_State * tolua_S)
+{
+ // Calculate the raw md5 checksum byte array:
+ unsigned char md5Output[16];
+ size_t len = 0;
+ const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len);
+ if (SourceString == nullptr)
+ {
+ return 0;
+ }
+ md5(SourceString, len, md5Output);
+
+ // Convert the md5 checksum to hex string:
+ std::stringstream Output;
+ Output << std::hex << std::setfill('0');
+ for (size_t i = 0; i < ARRAYCOUNT(md5Output); i++)
+ {
+ Output << std::setw(2) << static_cast<unsigned short>(md5Output[i]); // Need to cast to a number, otherwise a char is output
+ }
+ lua_pushlstring(tolua_S, Output.str().c_str(), Output.str().size());
+ return 1;
+}
+
+
+
+
+
+static int tolua_sha1(lua_State * tolua_S)
+{
+ // Calculate the raw SHA1 checksum byte array from the input string:
+ unsigned char Output[20];
+ size_t len = 0;
+ const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len);
+ if (SourceString == nullptr)
+ {
+ return 0;
+ }
+ sha1(SourceString, len, Output);
+ lua_pushlstring(tolua_S, (const char *)Output, ARRAYCOUNT(Output));
+ return 1;
+}
+
+
+
+
+
+static int tolua_sha1HexString(lua_State * tolua_S)
+{
+ // Calculate the raw SHA1 checksum byte array from the input string:
+ unsigned char sha1Output[20];
+ size_t len = 0;
+ const unsigned char * SourceString = (const unsigned char *)lua_tolstring(tolua_S, 1, &len);
+ if (SourceString == nullptr)
+ {
+ return 0;
+ }
+ sha1(SourceString, len, sha1Output);
+
+ // Convert the sha1 checksum to hex string:
+ std::stringstream Output;
+ Output << std::hex << std::setfill('0');
+ for (size_t i = 0; i < ARRAYCOUNT(sha1Output); i++)
+ {
+ Output << std::setw(2) << static_cast<unsigned short>(sha1Output[i]); // Need to cast to a number, otherwise a char is output
+ }
+ lua_pushlstring(tolua_S, Output.str().c_str(), Output.str().size());
+ return 1;
+}
+
+
+
+
+
static int tolua_push_StringStringMap(lua_State* tolua_S, std::map< std::string, std::string >& a_StringStringMap)
{
lua_newtable(tolua_S);
@@ -3387,6 +3653,14 @@ static int tolua_cCompositeChat_UnderlineUrls(lua_State * tolua_S)
void ManualBindings::Bind(lua_State * tolua_S)
{
tolua_beginmodule(tolua_S, nullptr);
+
+ // Create the new classes:
+ tolua_usertype(tolua_S, "cCryptoHash");
+ tolua_cclass(tolua_S, "cCryptoHash", "cCryptoHash", "", nullptr);
+ tolua_usertype(tolua_S, "cStringCompression");
+ tolua_cclass(tolua_S, "cStringCompression", "cStringCompression", "", nullptr);
+
+ // Globals:
tolua_function(tolua_S, "Clamp", tolua_Clamp);
tolua_function(tolua_S, "StringSplit", tolua_StringSplit);
tolua_function(tolua_S, "StringSplitAndTrim", tolua_StringSplitAndTrim);
@@ -3397,6 +3671,7 @@ void ManualBindings::Bind(lua_State * tolua_S)
tolua_function(tolua_S, "LOGERROR", tolua_LOGERROR);
tolua_function(tolua_S, "Base64Encode", tolua_Base64Encode);
tolua_function(tolua_S, "Base64Decode", tolua_Base64Decode);
+ tolua_function(tolua_S, "md5", tolua_md5_obsolete); // OBSOLETE, use cCryptoHash.md5() instead
tolua_beginmodule(tolua_S, "cFile");
tolua_function(tolua_S, "GetFolderContents", tolua_cFile_GetFolderContents);
@@ -3553,9 +3828,23 @@ void ManualBindings::Bind(lua_State * tolua_S)
tolua_function(tolua_S, "GetSlotCoords", Lua_ItemGrid_GetSlotCoords);
tolua_endmodule(tolua_S);
- tolua_function(tolua_S, "md5", tolua_md5);
+ tolua_beginmodule(tolua_S, "cCryptoHash");
+ tolua_function(tolua_S, "md5", tolua_md5);
+ tolua_function(tolua_S, "md5HexString", tolua_md5HexString);
+ tolua_function(tolua_S, "sha1", tolua_sha1);
+ tolua_function(tolua_S, "sha1HexString", tolua_sha1HexString);
+ tolua_endmodule(tolua_S);
+
+ tolua_beginmodule(tolua_S, "cStringCompression");
+ tolua_function(tolua_S, "CompressStringZLIB", tolua_CompressStringZLIB);
+ tolua_function(tolua_S, "UncompressStringZLIB", tolua_UncompressStringZLIB);
+ tolua_function(tolua_S, "CompressStringGZIP", tolua_CompressStringGZIP);
+ tolua_function(tolua_S, "UncompressStringGZIP", tolua_UncompressStringGZIP);
+ tolua_function(tolua_S, "InflateString", tolua_InflateString);
+ tolua_endmodule(tolua_S);
BindRankManager(tolua_S);
+ BindNetwork(tolua_S);
tolua_endmodule(tolua_S);
}
diff --git a/src/Bindings/ManualBindings.h b/src/Bindings/ManualBindings.h
index 1b6e65654..74d24d5f5 100644
--- a/src/Bindings/ManualBindings.h
+++ b/src/Bindings/ManualBindings.h
@@ -1,6 +1,7 @@
#pragma once
struct lua_State;
+class cPluginLua;
@@ -17,8 +18,17 @@ protected:
/** Binds the manually implemented cRankManager glue code to tolua_S.
Implemented in ManualBindings_RankManager.cpp. */
static void BindRankManager(lua_State * tolua_S);
+
+ /** Binds the manually implemented cNetwork-related API to tolua_S.
+ Implemented in ManualBindings_Network.cpp. */
+ static void BindNetwork(lua_State * tolua_S);
};
+extern cPluginLua * GetLuaPlugin(lua_State * L);
+
+
+
+
diff --git a/src/Bindings/ManualBindings_Network.cpp b/src/Bindings/ManualBindings_Network.cpp
new file mode 100644
index 000000000..a628eb9ca
--- /dev/null
+++ b/src/Bindings/ManualBindings_Network.cpp
@@ -0,0 +1,942 @@
+
+// ManualBindings_Network.cpp
+
+// Implements the cNetwork-related API bindings for Lua
+
+#include "Globals.h"
+#include "LuaTCPLink.h"
+#include "ManualBindings.h"
+#include "tolua++/include/tolua++.h"
+#include "LuaState.h"
+#include "LuaTCPLink.h"
+#include "LuaNameLookup.h"
+#include "LuaServerHandle.h"
+#include "LuaUDPEndpoint.h"
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cNetwork API functions:
+
+/** Binds cNetwork::Connect */
+static int tolua_cNetwork_Connect(lua_State * L)
+{
+ // Function signature:
+ // cNetwork:Connect(Host, Port, Callbacks) -> bool
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserTable(1, "cNetwork") ||
+ !S.CheckParamString(2) ||
+ !S.CheckParamNumber(3) ||
+ !S.CheckParamTable(4) ||
+ !S.CheckParamEnd(5)
+ )
+ {
+ return 0;
+ }
+
+ // Get the plugin instance:
+ cPluginLua * Plugin = GetLuaPlugin(L);
+ if (Plugin == nullptr)
+ {
+ // An error message has been already printed in GetLuaPlugin()
+ S.Push(false);
+ return 1;
+ }
+
+ // Read the params:
+ AString Host;
+ int Port;
+ S.GetStackValues(2, Host, Port);
+
+ // Check validity:
+ if ((Port < 0) || (Port > 65535))
+ {
+ LOGWARNING("cNetwork:Connect() called with invalid port (%d), failing the request.", Port);
+ S.Push(false);
+ return 1;
+ }
+
+ // Create the LuaTCPLink glue class:
+ auto Link = std::make_shared<cLuaTCPLink>(*Plugin, 4);
+
+ // Try to connect:
+ bool res = cNetwork::Connect(Host, static_cast<UInt16>(Port), Link, Link);
+ S.Push(res);
+
+ return 1;
+}
+
+
+
+
+
+/** Binds cNetwork::CreateUDPEndpoint */
+static int tolua_cNetwork_CreateUDPEndpoint(lua_State * L)
+{
+ // Function signature:
+ // cNetwork:CreateUDPEndpoint(Port, Callbacks) -> cUDPEndpoint
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserTable(1, "cNetwork") ||
+ !S.CheckParamNumber(2) ||
+ !S.CheckParamTable(3) ||
+ !S.CheckParamEnd(4)
+ )
+ {
+ return 0;
+ }
+
+ // Get the plugin instance:
+ cPluginLua * Plugin = GetLuaPlugin(L);
+ if (Plugin == nullptr)
+ {
+ // An error message has been already printed in GetLuaPlugin()
+ S.Push(false);
+ return 1;
+ }
+
+ // Read the params:
+ int Port;
+ S.GetStackValues(2, Port);
+
+ // Check validity:
+ if ((Port < 0) || (Port > 65535))
+ {
+ LOGWARNING("cNetwork:CreateUDPEndpoint() called with invalid port (%d), failing the request.", Port);
+ S.Push(false);
+ return 1;
+ }
+
+ // Create the LuaUDPEndpoint glue class:
+ auto Endpoint = std::make_shared<cLuaUDPEndpoint>(*Plugin, 3);
+ Endpoint->Open(Port, Endpoint);
+
+ // Register the endpoint to be garbage-collected by Lua:
+ tolua_pushusertype(L, Endpoint.get(), "cUDPEndpoint");
+ tolua_register_gc(L, lua_gettop(L));
+
+ // Return the endpoint object:
+ S.Push(Endpoint.get());
+ return 1;
+}
+
+
+
+
+
+/** Binds cNetwork::HostnameToIP */
+static int tolua_cNetwork_HostnameToIP(lua_State * L)
+{
+ // Function signature:
+ // cNetwork:HostnameToIP(Host, Callbacks) -> bool
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserTable(1, "cNetwork") ||
+ !S.CheckParamString(2) ||
+ !S.CheckParamTable(3) ||
+ !S.CheckParamEnd(4)
+ )
+ {
+ return 0;
+ }
+
+ // Get the plugin instance:
+ cPluginLua * Plugin = GetLuaPlugin(L);
+ if (Plugin == nullptr)
+ {
+ // An error message has been already printed in GetLuaPlugin()
+ S.Push(false);
+ return 1;
+ }
+
+ // Read the params:
+ AString Host;
+ S.GetStackValue(2, Host);
+
+ // Try to look up:
+ bool res = cNetwork::HostnameToIP(Host, std::make_shared<cLuaNameLookup>(Host, *Plugin, 3));
+ S.Push(res);
+
+ return 1;
+}
+
+
+
+
+
+/** Binds cNetwork::IPToHostname */
+static int tolua_cNetwork_IPToHostname(lua_State * L)
+{
+ // Function signature:
+ // cNetwork:IPToHostname(IP, Callbacks) -> bool
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserTable(1, "cNetwork") ||
+ !S.CheckParamString(2) ||
+ !S.CheckParamTable(3) ||
+ !S.CheckParamEnd(4)
+ )
+ {
+ return 0;
+ }
+
+ // Get the plugin instance:
+ cPluginLua * Plugin = GetLuaPlugin(L);
+ if (Plugin == nullptr)
+ {
+ // An error message has been already printed in GetLuaPlugin()
+ S.Push(false);
+ return 1;
+ }
+
+ // Read the params:
+ AString Host;
+ S.GetStackValue(2, Host);
+
+ // Try to look up:
+ bool res = cNetwork::IPToHostName(Host, std::make_shared<cLuaNameLookup>(Host, *Plugin, 3));
+ S.Push(res);
+
+ return 1;
+}
+
+
+
+
+
+/** Binds cNetwork::Listen */
+static int tolua_cNetwork_Listen(lua_State * L)
+{
+ // Function signature:
+ // cNetwork:Listen(Port, Callbacks) -> cServerHandle
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserTable(1, "cNetwork") ||
+ !S.CheckParamNumber(2) ||
+ !S.CheckParamTable(3) ||
+ !S.CheckParamEnd(4)
+ )
+ {
+ return 0;
+ }
+
+ // Get the plugin instance:
+ cPluginLua * Plugin = GetLuaPlugin(L);
+ if (Plugin == nullptr)
+ {
+ // An error message has been already printed in GetLuaPlugin()
+ S.Push(false);
+ return 1;
+ }
+
+ // Read the params:
+ int Port;
+ S.GetStackValues(2, Port);
+ if ((Port < 0) || (Port > 65535))
+ {
+ LOGWARNING("cNetwork:Listen() called with invalid port (%d), failing the request.", Port);
+ S.Push(false);
+ return 1;
+ }
+ UInt16 Port16 = static_cast<UInt16>(Port);
+
+ // Create the LuaTCPLink glue class:
+ auto Srv = std::make_shared<cLuaServerHandle>(Port16, *Plugin, 3);
+
+ // Listen:
+ Srv->SetServerHandle(cNetwork::Listen(Port16, Srv), Srv);
+
+ // Register the server to be garbage-collected by Lua:
+ tolua_pushusertype(L, Srv.get(), "cServerHandle");
+ tolua_register_gc(L, lua_gettop(L));
+
+ // Return the server handle wrapper:
+ S.Push(Srv.get());
+ return 1;
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cServerHandle bindings (routed through cLuaServerHandle):
+
+/** Called when Lua destroys the object instance.
+Close the server and let it deallocate on its own (it's in a SharedPtr). */
+static int tolua_collect_cServerHandle(lua_State * L)
+{
+ cLuaServerHandle * Srv = static_cast<cLuaServerHandle *>(tolua_tousertype(L, 1, nullptr));
+ Srv->Release();
+ return 0;
+}
+
+
+
+
+
+/** Binds cLuaServerHandle::Close */
+static int tolua_cServerHandle_Close(lua_State * L)
+{
+ // Function signature:
+ // ServerInstance:Close()
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cServerHandle") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the server handle:
+ cLuaServerHandle * Srv;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cServerHandle:Close(): invalid server handle object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1));
+
+ // Close it:
+ Srv->Close();
+ return 0;
+}
+
+
+
+
+
+/** Binds cLuaServerHandle::IsListening */
+static int tolua_cServerHandle_IsListening(lua_State * L)
+{
+ // Function signature:
+ // ServerInstance:IsListening() -> bool
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cServerHandle") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the server handle:
+ cLuaServerHandle * Srv;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cServerHandle:IsListening(): invalid server handle object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1));
+
+ // Close it:
+ S.Push(Srv->IsListening());
+ return 1;
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cTCPLink bindings (routed through cLuaTCPLink):
+
+/** Binds cLuaTCPLink::Close */
+static int tolua_cTCPLink_Close(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:Close()
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:Close(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // CLose the link:
+ Link->Close();
+ return 0;
+}
+
+
+
+
+
+/** Binds cLuaTCPLink::GetLocalIP */
+static int tolua_cTCPLink_GetLocalIP(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:GetLocalIP() -> string
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:GetLocalIP(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // Get the IP:
+ S.Push(Link->GetLocalIP());
+ return 1;
+}
+
+
+
+
+
+/** Binds cLuaTCPLink::GetLocalPort */
+static int tolua_cTCPLink_GetLocalPort(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:GetLocalPort() -> number
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:GetLocalPort(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // Get the Port:
+ S.Push(Link->GetLocalPort());
+ return 1;
+}
+
+
+
+
+
+/** Binds cLuaTCPLink::GetRemoteIP */
+static int tolua_cTCPLink_GetRemoteIP(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:GetRemoteIP() -> string
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:GetRemoteIP(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // Get the IP:
+ S.Push(Link->GetRemoteIP());
+ return 1;
+}
+
+
+
+
+
+/** Binds cLuaTCPLink::GetRemotePort */
+static int tolua_cTCPLink_GetRemotePort(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:GetRemotePort() -> number
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:GetRemotePort(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // Get the Port:
+ S.Push(Link->GetRemotePort());
+ return 1;
+}
+
+
+
+
+
+/** Binds cLuaTCPLink::Send */
+static int tolua_cTCPLink_Send(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:Send(DataString)
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamString(2) ||
+ !S.CheckParamEnd(3)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:Send(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // Get the data to send:
+ AString Data;
+ S.GetStackValues(2, Data);
+
+ // Send the data:
+ Link->Send(Data);
+ return 0;
+}
+
+
+
+
+
+/** Binds cLuaTCPLink::Shutdown */
+static int tolua_cTCPLink_Shutdown(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:Shutdown()
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:Shutdown(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // Shutdown the link:
+ Link->Shutdown();
+ return 0;
+}
+
+
+
+
+
+/** Binds cLuaTCPLink::StartTLSClient */
+static int tolua_cTCPLink_StartTLSClient(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:StartTLSClient(OwnCert, OwnPrivKey, OwnPrivKeyPassword) -> [true] or [nil, ErrMsg]
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamString(2, 4) ||
+ !S.CheckParamEnd(5)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:StartTLSClient(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // Read the params:
+ AString OwnCert, OwnPrivKey, OwnPrivKeyPassword;
+ S.GetStackValues(2, OwnCert, OwnPrivKey, OwnPrivKeyPassword);
+
+ // Start the TLS handshake:
+ AString res = Link->StartTLSClient(OwnCert, OwnPrivKey, OwnPrivKeyPassword);
+ if (!res.empty())
+ {
+ S.PushNil();
+ S.Push(Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str()));
+ return 2;
+ }
+ return 1;
+}
+
+
+
+
+
+/** Binds cLuaTCPLink::StartTLSServer */
+static int tolua_cTCPLink_StartTLSServer(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:StartTLSServer(OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData) -> [true] or [nil, ErrMsg]
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cTCPLink") ||
+ !S.CheckParamString(2, 4) ||
+ // Param 5 is optional, don't check
+ !S.CheckParamEnd(6)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ cLuaTCPLink * Link;
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cTCPLink:StartTLSServer(): invalid link object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ Link = *static_cast<cLuaTCPLink **>(lua_touserdata(L, 1));
+
+ // Read the params:
+ AString OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData;
+ S.GetStackValues(2, OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData);
+
+ // Start the TLS handshake:
+ AString res = Link->StartTLSServer(OwnCert, OwnPrivKey, OwnPrivKeyPassword, StartTLSData);
+ if (!res.empty())
+ {
+ S.PushNil();
+ S.Push(Printf("Cannot start TLS on link to %s:%d: %s", Link->GetRemoteIP().c_str(), Link->GetRemotePort(), res.c_str()));
+ return 2;
+ }
+ return 1;
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cUDPEndpoint bindings (routed through cLuaUDPEndpoint):
+
+/** Called when Lua destroys the object instance.
+Close the endpoint and let it deallocate on its own (it's in a SharedPtr). */
+static int tolua_collect_cUDPEndpoint(lua_State * L)
+{
+ LOGD("Lua: Collecting cUDPEndpoint");
+ cLuaUDPEndpoint * Endpoint = static_cast<cLuaUDPEndpoint *>(tolua_tousertype(L, 1, nullptr));
+ Endpoint->Release();
+ return 0;
+}
+
+
+
+
+
+/** Binds cLuaUDPEndpoint::Close */
+static int tolua_cUDPEndpoint_Close(lua_State * L)
+{
+ // Function signature:
+ // EndpointInstance:Close()
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cUDPEndpoint") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the endpoint:
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cUDPEndpoint:Close(): invalid endpoint object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1));
+
+ // Close it:
+ Endpoint->Close();
+ return 0;
+}
+
+
+
+
+
+/** Binds cLuaUDPEndpoint::EnableBroadcasts */
+static int tolua_cUDPEndpoint_EnableBroadcasts(lua_State * L)
+{
+ // Function signature:
+ // EndpointInstance:EnableBroadcasts()
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cUDPEndpoint") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the endpoint:
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cUDPEndpoint:EnableBroadcasts(): invalid endpoint object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1));
+
+ // Enable the broadcasts:
+ Endpoint->EnableBroadcasts();
+ return 0;
+}
+
+
+
+
+
+/** Binds cLuaUDPEndpoint::GetPort */
+static int tolua_cUDPEndpoint_GetPort(lua_State * L)
+{
+ // Function signature:
+ // Endpoint:GetPort() -> number
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cUDPEndpoint") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the endpoint:
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cUDPEndpoint:GetPort(): invalid endpoint object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1));
+
+ // Get the Port:
+ S.Push(Endpoint->GetPort());
+ return 1;
+}
+
+
+
+
+
+/** Binds cLuaUDPEndpoint::IsOpen */
+static int tolua_cUDPEndpoint_IsOpen(lua_State * L)
+{
+ // Function signature:
+ // Endpoint:IsOpen() -> bool
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cUDPEndpoint") ||
+ !S.CheckParamEnd(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the endpoint:
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cUDPEndpoint:IsListening(): invalid server handle object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1));
+
+ // Close it:
+ S.Push(Endpoint->IsOpen());
+ return 1;
+}
+
+
+
+
+
+/** Binds cLuaUDPEndpoint::Send */
+static int tolua_cUDPEndpoint_Send(lua_State * L)
+{
+ // Function signature:
+ // LinkInstance:Send(DataString)
+
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserType(1, "cUDPEndpoint") ||
+ !S.CheckParamString(2, 3) ||
+ !S.CheckParamNumber(4) ||
+ !S.CheckParamEnd(5)
+ )
+ {
+ return 0;
+ }
+
+ // Get the link:
+ if (lua_isnil(L, 1))
+ {
+ LOGWARNING("cUDPEndpoint:Send(): invalid endpoint object. Stack trace:");
+ S.LogStackTrace();
+ return 0;
+ }
+ auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1));
+
+ // Get the data to send:
+ AString Data, RemotePeer;
+ int RemotePort;
+ S.GetStackValues(2, Data, RemotePeer, RemotePort);
+
+ // Check the port:
+ if ((RemotePort < 0) || (RemotePort > USHRT_MAX))
+ {
+ LOGWARNING("cUDPEndpoint:Send() called with invalid port (%d), failing.", RemotePort);
+ S.LogStackTrace();
+ S.Push(false);
+ return 1;
+ }
+
+ // Send the data:
+ S.Push(Endpoint->Send(Data, RemotePeer, static_cast<UInt16>(RemotePort)));
+ return 1;
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Register the bindings:
+
+void ManualBindings::BindNetwork(lua_State * tolua_S)
+{
+ // Create the cNetwork API classes:
+ tolua_usertype(tolua_S, "cNetwork");
+ tolua_cclass(tolua_S, "cNetwork", "cNetwork", "", nullptr);
+ tolua_usertype(tolua_S, "cTCPLink");
+ tolua_cclass(tolua_S, "cTCPLink", "cTCPLink", "", nullptr);
+ tolua_usertype(tolua_S, "cServerHandle");
+ tolua_cclass(tolua_S, "cServerHandle", "cServerHandle", "", tolua_collect_cServerHandle);
+ tolua_usertype(tolua_S, "cUDPEndpoint");
+ tolua_cclass(tolua_S, "cUDPEndpoint", "cUDPEndpoint", "", tolua_collect_cUDPEndpoint);
+
+ // Fill in the functions (alpha-sorted):
+ tolua_beginmodule(tolua_S, "cNetwork");
+ tolua_function(tolua_S, "Connect", tolua_cNetwork_Connect);
+ tolua_function(tolua_S, "CreateUDPEndpoint", tolua_cNetwork_CreateUDPEndpoint);
+ tolua_function(tolua_S, "HostnameToIP", tolua_cNetwork_HostnameToIP);
+ tolua_function(tolua_S, "IPToHostname", tolua_cNetwork_IPToHostname);
+ tolua_function(tolua_S, "Listen", tolua_cNetwork_Listen);
+ tolua_endmodule(tolua_S);
+
+ tolua_beginmodule(tolua_S, "cServerHandle");
+ tolua_function(tolua_S, "Close", tolua_cServerHandle_Close);
+ tolua_function(tolua_S, "IsListening", tolua_cServerHandle_IsListening);
+ tolua_endmodule(tolua_S);
+
+ tolua_beginmodule(tolua_S, "cTCPLink");
+ tolua_function(tolua_S, "Close", tolua_cTCPLink_Close);
+ tolua_function(tolua_S, "GetLocalIP", tolua_cTCPLink_GetLocalIP);
+ tolua_function(tolua_S, "GetLocalPort", tolua_cTCPLink_GetLocalPort);
+ tolua_function(tolua_S, "GetRemoteIP", tolua_cTCPLink_GetRemoteIP);
+ tolua_function(tolua_S, "GetRemotePort", tolua_cTCPLink_GetRemotePort);
+ tolua_function(tolua_S, "Send", tolua_cTCPLink_Send);
+ tolua_function(tolua_S, "Shutdown", tolua_cTCPLink_Shutdown);
+ tolua_function(tolua_S, "StartTLSClient", tolua_cTCPLink_StartTLSClient);
+ tolua_function(tolua_S, "StartTLSServer", tolua_cTCPLink_StartTLSServer);
+ tolua_endmodule(tolua_S);
+
+ tolua_beginmodule(tolua_S, "cUDPEndpoint");
+ tolua_function(tolua_S, "Close", tolua_cUDPEndpoint_Close);
+ tolua_function(tolua_S, "EnableBroadcasts", tolua_cUDPEndpoint_EnableBroadcasts);
+ tolua_function(tolua_S, "GetPort", tolua_cUDPEndpoint_GetPort);
+ tolua_function(tolua_S, "IsOpen", tolua_cUDPEndpoint_IsOpen);
+ tolua_function(tolua_S, "Send", tolua_cUDPEndpoint_Send);
+ tolua_endmodule(tolua_S);
+
+}
+
+
+
+
diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h
index 6210dbed4..6ade8ef9f 100644
--- a/src/Bindings/Plugin.h
+++ b/src/Bindings/Plugin.h
@@ -57,6 +57,7 @@ public:
virtual bool OnCraftingNoRecipe (cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe) = 0;
virtual bool OnDisconnect (cClientHandle & a_Client, const AString & a_Reason) = 0;
virtual bool OnEntityAddEffect (cEntity & a_Entity, int a_EffectType, int a_EffectDurationTicks, int a_EffectIntensity, double a_DistanceModifier) = 0;
+ virtual bool OnEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) = 0;
virtual bool OnExecuteCommand (cPlayer * a_Player, const AStringVector & a_Split) = 0;
virtual bool OnExploded (cWorld & a_World, double a_ExplosionSize, bool a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData) = 0;
virtual bool OnExploding (cWorld & a_World, double & a_ExplosionSize, bool & a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData) = 0;
diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp
index 500913e76..fb7650d42 100644
--- a/src/Bindings/PluginLua.cpp
+++ b/src/Bindings/PluginLua.cpp
@@ -857,6 +857,26 @@ bool cPluginLua::OnPlayerMoving(cPlayer & a_Player, const Vector3d & a_OldPositi
+bool cPluginLua::OnEntityTeleport(cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition)
+{
+ cCSLock Lock(m_CriticalSection);
+ bool res = false;
+ cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_ENTITY_TELEPORT];
+ for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr)
+ {
+ m_LuaState.Call((int)(**itr), &a_Entity, a_OldPosition, a_NewPosition, cLuaState::Return, res);
+ if (res)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange)
{
cCSLock Lock(m_CriticalSection);
@@ -1577,6 +1597,7 @@ const char * cPluginLua::GetHookFnName(int a_HookType)
case cPluginManager::HOOK_DISCONNECT: return "OnDisconnect";
case cPluginManager::HOOK_PLAYER_ANIMATION: return "OnPlayerAnimation";
case cPluginManager::HOOK_ENTITY_ADD_EFFECT: return "OnEntityAddEffect";
+ case cPluginManager::HOOK_ENTITY_TELEPORT: return "OnEntityTeleport";
case cPluginManager::HOOK_EXECUTE_COMMAND: return "OnExecuteCommand";
case cPluginManager::HOOK_HANDSHAKE: return "OnHandshake";
case cPluginManager::HOOK_KILLING: return "OnKilling";
diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h
index f443f5fc0..7b528501b 100644
--- a/src/Bindings/PluginLua.h
+++ b/src/Bindings/PluginLua.h
@@ -106,6 +106,7 @@ public:
virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) override;
virtual bool OnPlayerShooting (cPlayer & a_Player) override;
virtual bool OnPlayerSpawned (cPlayer & a_Player) override;
+ virtual bool OnEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) override;
virtual bool OnPlayerTossingItem (cPlayer & a_Player) override;
virtual bool OnPlayerUsedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override;
virtual bool OnPlayerUsedItem (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override;
diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp
index 9d86c64a2..41b36337e 100644
--- a/src/Bindings/PluginManager.cpp
+++ b/src/Bindings/PluginManager.cpp
@@ -505,6 +505,24 @@ bool cPluginManager::CallHookEntityAddEffect(cEntity & a_Entity, int a_EffectTyp
+bool cPluginManager::CallHookEntityTeleport(cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition)
+{
+ FIND_HOOK(HOOK_ENTITY_TELEPORT);
+ VERIFY_HOOK;
+
+ for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr)
+ {
+ if ((*itr)->OnEntityTeleport(a_Entity, a_OldPosition, a_NewPosition))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
bool cPluginManager::CallHookExecuteCommand(cPlayer * a_Player, const AStringVector & a_Split)
{
FIND_HOOK(HOOK_EXECUTE_COMMAND);
diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h
index 97e91c1df..c8b4de9d6 100644
--- a/src/Bindings/PluginManager.h
+++ b/src/Bindings/PluginManager.h
@@ -109,6 +109,7 @@ public:
HOOK_PLAYER_RIGHT_CLICKING_ENTITY,
HOOK_PLAYER_SHOOTING,
HOOK_PLAYER_SPAWNED,
+ HOOK_ENTITY_TELEPORT,
HOOK_PLAYER_TOSSING_ITEM,
HOOK_PLAYER_USED_BLOCK,
HOOK_PLAYER_USED_ITEM,
@@ -190,6 +191,7 @@ public:
bool CallHookCraftingNoRecipe (cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe);
bool CallHookDisconnect (cClientHandle & a_Client, const AString & a_Reason);
bool CallHookEntityAddEffect (cEntity & a_Entity, int a_EffectType, int a_EffectDurationTicks, int a_EffectIntensity, double a_DistanceModifier);
+ bool CallHookEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition);
bool CallHookExecuteCommand (cPlayer * a_Player, const AStringVector & a_Split); // If a_Player == nullptr, it is a console cmd
bool CallHookExploded (cWorld & a_World, double a_ExplosionSize, bool a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData);
bool CallHookExploding (cWorld & a_World, double & a_ExplosionSize, bool & a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData);
diff --git a/src/BiomeDef.cpp b/src/BiomeDef.cpp
index 188e06173..3e34ebdbe 100644
--- a/src/BiomeDef.cpp
+++ b/src/BiomeDef.cpp
@@ -222,3 +222,130 @@ bool IsBiomeCold(EMCSBiome a_Biome)
+
+int GetSnowStartHeight(EMCSBiome a_Biome)
+{
+ switch (a_Biome)
+ {
+ case biIcePlainsSpikes:
+ case biIcePlains:
+ case biIceMountains:
+ case biFrozenRiver:
+ case biColdBeach:
+ case biColdTaiga:
+ case biColdTaigaHills:
+ case biColdTaigaM:
+ {
+ // Always snow
+ return 0;
+ }
+
+ case biExtremeHills:
+ case biExtremeHillsM:
+ case biExtremeHillsPlus:
+ case biExtremeHillsPlusM:
+ case biStoneBeach:
+ {
+ // Starts snowing at 96
+ return 96;
+ }
+
+ case biTaiga:
+ case biTaigaHills:
+ case biTaigaM:
+ {
+ // Start snowing at 130
+ return 130;
+ }
+
+ case biMegaTaiga:
+ case biMegaSpruceTaiga:
+ case biMegaTaigaHills:
+ case biMegaSpruceTaigaHills:
+ {
+ // Start snowing at 160
+ return 160;
+ }
+
+ case biRiver:
+ case biOcean:
+ case biDeepOcean:
+ {
+ // Starts snowing at 280
+ return 280;
+ }
+
+ case biBirchForest:
+ case biBirchForestHills:
+ case biBirchForestM:
+ case biBirchForestHillsM:
+ {
+ // Starts snowing at 335
+ return 335;
+ }
+
+ case biForest:
+ case biForestHills:
+ case biFlowerForest:
+ case biRoofedForest:
+ case biRoofedForestM:
+ {
+ // Starts snowing at 400
+ return 400;
+ }
+
+ case biPlains:
+ case biSunflowerPlains:
+ case biSwampland:
+ case biSwamplandM:
+ case biBeach:
+ {
+ // Starts snowing at 460
+ return 460;
+ }
+
+ case biMushroomIsland:
+ case biMushroomShore:
+ {
+ // Starts snowing at 520
+ return 520;
+ }
+
+ case biJungle:
+ case biJungleHills:
+ case biJungleM:
+ case biJungleEdge:
+ case biJungleEdgeM:
+ {
+ // Starts snowing at 550
+ return 550;
+ }
+
+ case biDesert:
+ case biDesertHills:
+ case biDesertM:
+ case biSavanna:
+ case biSavannaM:
+ case biSavannaPlateau:
+ case biSavannaPlateauM:
+ case biMesa:
+ case biMesaBryce:
+ case biMesaPlateau:
+ case biMesaPlateauF:
+ case biMesaPlateauFM:
+ case biMesaPlateauM:
+ {
+ // These biomes don't actualy have any downfall.
+ return 1000;
+ }
+
+ default:
+ {
+ return 0;
+ }
+ }
+}
+
+
+
+
diff --git a/src/BiomeDef.h b/src/BiomeDef.h
index 84751cfd7..cda12556a 100644
--- a/src/BiomeDef.h
+++ b/src/BiomeDef.h
@@ -129,4 +129,7 @@ extern bool IsBiomeVeryCold(EMCSBiome a_Biome);
Doesn't report Very Cold biomes, use IsBiomeVeryCold() for those. */
extern bool IsBiomeCold(EMCSBiome a_Biome);
+/** Returns the height when a biome when a biome starts snowing.*/
+extern int GetSnowStartHeight(EMCSBiome a_Biome);
+
// tolua_end
diff --git a/src/Blocks/BlockDirt.h b/src/Blocks/BlockDirt.h
index aae6719e2..12bca92dd 100644
--- a/src/Blocks/BlockDirt.h
+++ b/src/Blocks/BlockDirt.h
@@ -52,7 +52,19 @@ public:
return;
}
}
-
+
+ // Make sure that there is enough light at the source block to spread
+ if (!a_Chunk.GetWorld()->IsChunkLighted(a_Chunk.GetPosX(), a_Chunk.GetPosZ()))
+ {
+ a_Chunk.GetWorld()->QueueLightChunk(a_Chunk.GetPosX(), a_Chunk.GetPosZ());
+ return;
+ }
+ else if (std::max(a_Chunk.GetBlockLight(a_RelX, a_RelY + 1, a_RelZ), a_Chunk.GetTimeAlteredLight(a_Chunk.GetSkyLight(a_RelX, a_RelY + 1, a_RelZ))) < 9)
+ {
+ // Source block is not bright enough to spread
+ return;
+ }
+
// Grass spreads to adjacent dirt blocks:
cFastRandom rand;
for (int i = 0; i < 2; i++) // Pick two blocks to grow to
diff --git a/src/Blocks/BlockOre.h b/src/Blocks/BlockOre.h
index f6ea3aa3c..08d79f435 100644
--- a/src/Blocks/BlockOre.h
+++ b/src/Blocks/BlockOre.h
@@ -11,6 +11,7 @@
class cBlockOreHandler :
public cBlockHandler
{
+ typedef cBlockHandler super;
public:
cBlockOreHandler(BLOCKTYPE a_BlockType)
: cBlockHandler(a_BlockType)
@@ -56,6 +57,64 @@ public:
}
}
}
+
+ virtual void OnDestroyedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) override
+ {
+ super::OnDestroyedByPlayer(a_ChunkInterface, a_WorldInterface, a_Player, a_BlockX, a_BlockY, a_BlockZ);
+
+ if (a_Player->IsGameModeCreative())
+ {
+ // Don't drop XP when the player is in creative mode.
+ return;
+ }
+
+ if (a_Player->GetEquippedItem().m_Enchantments.GetLevel(cEnchantments::enchSilkTouch) != 0)
+ {
+ // Don't drop XP when the ore is mined with the Silk Touch enchantment
+ return;
+ }
+
+ cFastRandom Random;
+ int Reward = 0;
+
+ switch (m_BlockType)
+ {
+ case E_BLOCK_NETHER_QUARTZ_ORE:
+ case E_BLOCK_LAPIS_ORE:
+ {
+ // Lapis and nether quartz get 2 - 5 experience
+ Reward = Random.NextInt(4) + 2;
+ break;
+ }
+ case E_BLOCK_REDSTONE_ORE:
+ case E_BLOCK_REDSTONE_ORE_GLOWING:
+ {
+ // Redstone gets 1 - 5 experience
+ Reward = Random.NextInt(5) + 1;
+ break;
+ }
+ case E_BLOCK_DIAMOND_ORE:
+ case E_BLOCK_EMERALD_ORE:
+ {
+ // Diamond and emerald get 3 - 7 experience
+ Reward = Random.NextInt(5) + 3;
+ break;
+ }
+ case E_BLOCK_COAL_ORE:
+ {
+ // Coal gets 0 - 2 experience
+ Reward = Random.NextInt(3);
+ break;
+ }
+
+ default: break;
+ }
+
+ if (Reward != 0)
+ {
+ a_WorldInterface.SpawnExperienceOrb(a_BlockX, a_BlockY, a_BlockZ, Reward);
+ }
+ }
} ;
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index a4198c322..00ac1fdb1 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -776,10 +776,22 @@ void cChunk::BroadcastPendingBlockChanges(void)
{
return;
}
-
- for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr)
+
+ if (m_PendingSendBlocks.size() >= 10240)
{
- (*itr)->SendBlockChanges(m_PosX, m_PosZ, m_PendingSendBlocks);
+ // Resend the full chunk
+ for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr)
+ {
+ m_World->ForceSendChunkTo(m_PosX, m_PosZ, cChunkSender::E_CHUNK_PRIORITY_MEDIUM, (*itr));
+ }
+ }
+ else
+ {
+ // Only send block changes
+ for (cClientHandleList::iterator itr = m_LoadedByClient.begin(), end = m_LoadedByClient.end(); itr != end; ++itr)
+ {
+ (*itr)->SendBlockChanges(m_PosX, m_PosZ, m_PendingSendBlocks);
+ }
}
m_PendingSendBlocks.clear();
}
@@ -874,80 +886,71 @@ void cChunk::ApplyWeatherToTop()
int X = m_World->GetTickRandomNumber(15);
int Z = m_World->GetTickRandomNumber(15);
- switch (GetBiomeAt(X, Z))
- {
- case biTaiga:
- case biFrozenOcean:
- case biFrozenRiver:
- case biIcePlains:
- case biIceMountains:
- case biTaigaHills:
- {
- // TODO: Check light levels, don't snow over when the BlockLight is higher than (7?)
- int Height = GetHeight(X, Z);
- BLOCKTYPE TopBlock = GetBlock(X, Height, Z);
- NIBBLETYPE TopMeta = GetMeta (X, Height, Z);
- if (m_World->IsDeepSnowEnabled() && (TopBlock == E_BLOCK_SNOW))
+
+ // TODO: Check light levels, don't snow over when the BlockLight is higher than (7?)
+ int Height = GetHeight(X, Z);
+
+ if (GetSnowStartHeight(GetBiomeAt(X, Z)) > Height)
+ {
+ return;
+ }
+
+ BLOCKTYPE TopBlock = GetBlock(X, Height, Z);
+ NIBBLETYPE TopMeta = GetMeta (X, Height, Z);
+ if (m_World->IsDeepSnowEnabled() && (TopBlock == E_BLOCK_SNOW))
+ {
+ int MaxSize = 7;
+ BLOCKTYPE BlockType[4];
+ NIBBLETYPE BlockMeta[4];
+ UnboundedRelGetBlock(X - 1, Height, Z, BlockType[0], BlockMeta[0]);
+ UnboundedRelGetBlock(X + 1, Height, Z, BlockType[1], BlockMeta[1]);
+ UnboundedRelGetBlock(X, Height, Z - 1, BlockType[2], BlockMeta[2]);
+ UnboundedRelGetBlock(X, Height, Z + 1, BlockType[3], BlockMeta[3]);
+ for (int i = 0; i < 4; i++)
+ {
+ switch (BlockType[i])
{
- int MaxSize = 7;
- BLOCKTYPE BlockType[4];
- NIBBLETYPE BlockMeta[4];
- UnboundedRelGetBlock(X - 1, Height, Z, BlockType[0], BlockMeta[0]);
- UnboundedRelGetBlock(X + 1, Height, Z, BlockType[1], BlockMeta[1]);
- UnboundedRelGetBlock(X, Height, Z - 1, BlockType[2], BlockMeta[2]);
- UnboundedRelGetBlock(X, Height, Z + 1, BlockType[3], BlockMeta[3]);
- for (int i = 0; i < 4; i++)
- {
- switch (BlockType[i])
- {
- case E_BLOCK_AIR:
- {
- MaxSize = 0;
- break;
- }
- case E_BLOCK_SNOW:
- {
- MaxSize = std::min(BlockMeta[i] + 1, MaxSize);
- break;
- }
- }
- }
- if (TopMeta < MaxSize)
+ case E_BLOCK_AIR:
{
- FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta + 1);
+ MaxSize = 0;
+ break;
}
- else if (TopMeta > MaxSize)
+ case E_BLOCK_SNOW:
{
- FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta - 1);
+ MaxSize = std::min(BlockMeta[i] + 1, MaxSize);
+ break;
}
}
- else if (cBlockInfo::IsSnowable(TopBlock) && (Height + 1 < cChunkDef::Height))
- {
- SetBlock(X, Height + 1, Z, E_BLOCK_SNOW, 0);
- }
- else if ((TopBlock == E_BLOCK_WATER) || (TopBlock == E_BLOCK_STATIONARY_WATER))
- {
- SetBlock(X, Height, Z, E_BLOCK_ICE, 0);
- }
- else if (
- (m_World->IsDeepSnowEnabled()) &&
- (
- (TopBlock == E_BLOCK_RED_ROSE) ||
- (TopBlock == E_BLOCK_YELLOW_FLOWER) ||
- (TopBlock == E_BLOCK_RED_MUSHROOM) ||
- (TopBlock == E_BLOCK_BROWN_MUSHROOM)
- )
- )
- {
- SetBlock(X, Height, Z, E_BLOCK_SNOW, 0);
- }
- break;
- } // case (snowy biomes)
- default:
+ }
+ if (TopMeta < MaxSize)
{
- break;
+ FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta + 1);
+ }
+ else if (TopMeta > MaxSize)
+ {
+ FastSetBlock(X, Height, Z, E_BLOCK_SNOW, TopMeta - 1);
}
- } // switch (biome)
+ }
+ else if (cBlockInfo::IsSnowable(TopBlock) && (Height + 1 < cChunkDef::Height))
+ {
+ SetBlock(X, Height + 1, Z, E_BLOCK_SNOW, 0);
+ }
+ else if (IsBlockWater(TopBlock) && (TopMeta == 0))
+ {
+ SetBlock(X, Height, Z, E_BLOCK_ICE, 0);
+ }
+ else if (
+ (m_World->IsDeepSnowEnabled()) &&
+ (
+ (TopBlock == E_BLOCK_RED_ROSE) ||
+ (TopBlock == E_BLOCK_YELLOW_FLOWER) ||
+ (TopBlock == E_BLOCK_RED_MUSHROOM) ||
+ (TopBlock == E_BLOCK_BROWN_MUSHROOM)
+ )
+ )
+ {
+ SetBlock(X, Height, Z, E_BLOCK_SNOW, 0);
+ }
}
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index fba79031b..4fbce2b67 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -1895,9 +1895,13 @@ void cClientHandle::Tick(float a_Dt)
cCSLock Lock(m_CSOutgoingData);
std::swap(OutgoingData, m_OutgoingData);
}
- if ((m_Link != nullptr) && !OutgoingData.empty())
+ if (!OutgoingData.empty())
{
- m_Link->Send(OutgoingData.data(), OutgoingData.size());
+ cTCPLinkPtr Link(m_Link); // Grab a copy of the link in a multithread-safe way
+ if ((Link != nullptr))
+ {
+ Link->Send(OutgoingData.data(), OutgoingData.size());
+ }
}
m_TicksSinceLastPacket += 1;
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index c51a27961..1bc4690e1 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -1632,8 +1632,12 @@ void cEntity::TeleportToEntity(cEntity & a_Entity)
void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
{
- SetPosition(a_PosX, a_PosY, a_PosZ);
- m_World->BroadcastTeleportEntity(*this);
+ // ask the plugins to allow teleport to the new position.
+ if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPos, Vector3d(a_PosX, a_PosY, a_PosZ)))
+ {
+ SetPosition(a_PosX, a_PosY, a_PosZ);
+ m_World->BroadcastTeleportEntity(*this);
+ }
}
@@ -1913,10 +1917,7 @@ void cEntity::AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ)
void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ)
{
- m_Speed.x += a_AddSpeedX;
- m_Speed.y += a_AddSpeedY;
- m_Speed.z += a_AddSpeedZ;
- WrapSpeed();
+ DoSetSpeed(m_Speed.x + a_AddSpeedX, m_Speed.y + a_AddSpeedY, m_Speed.z + a_AddSpeedZ);
}
@@ -1925,8 +1926,7 @@ void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeed
void cEntity::AddSpeedX(double a_AddSpeedX)
{
- m_Speed.x += a_AddSpeedX;
- WrapSpeed();
+ AddSpeed(a_AddSpeedX, 0, 0);
}
@@ -1935,8 +1935,7 @@ void cEntity::AddSpeedX(double a_AddSpeedX)
void cEntity::AddSpeedY(double a_AddSpeedY)
{
- m_Speed.y += a_AddSpeedY;
- WrapSpeed();
+ AddSpeed(0, a_AddSpeedY, 0);
}
@@ -1945,8 +1944,7 @@ void cEntity::AddSpeedY(double a_AddSpeedY)
void cEntity::AddSpeedZ(double a_AddSpeedZ)
{
- m_Speed.z += a_AddSpeedZ;
- WrapSpeed();
+ AddSpeed(0, 0, a_AddSpeedZ);
}
diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h
index de9b88dfb..809e974d2 100644
--- a/src/Entities/Entity.h
+++ b/src/Entities/Entity.h
@@ -443,6 +443,9 @@ public:
/** Set the invulnerable ticks from the entity */
void SetInvulnerableTicks(int a_InvulnerableTicks) { m_InvulnerableTicks = a_InvulnerableTicks; }
+
+ /** Returns whether the entity is on ground or not */
+ virtual bool IsOnGround(void) const { return m_bOnGround; }
// tolua_end
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 3e225f1ec..c89e7b87c 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -105,15 +105,15 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
SetPosX(World->GetSpawnX());
SetPosY(World->GetSpawnY());
SetPosZ(World->GetSpawnZ());
- SetBedPos(Vector3i((int)World->GetSpawnX(), (int)World->GetSpawnY(), (int)World->GetSpawnZ()));
+ SetBedPos(Vector3i(static_cast<int>(World->GetSpawnX()), static_cast<int>(World->GetSpawnY()), static_cast<int>(World->GetSpawnZ())));
LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}",
a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ()
);
}
- m_LastJumpHeight = (float)(GetPosY());
- m_LastGroundHeight = (float)(GetPosY());
+ m_LastJumpHeight = static_cast<float>(GetPosY());
+ m_LastGroundHeight = static_cast<float>(GetPosY());
m_Stance = GetPosY() + 1.62;
if (m_GameMode == gmNotSet)
@@ -232,7 +232,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
}
bool CanMove = true;
- if (!GetPosition().EqualsEps(m_LastPos, 0.01)) // Non negligible change in position from last tick?
+ if (!GetPosition().EqualsEps(m_LastPos, 0.02)) // Non negligible change in position from last tick? 0.02 tp prevent continous calling while floating sometimes.
{
// Apply food exhaustion from movement:
ApplyFoodExhaustionFromMovement();
@@ -278,7 +278,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (IsFlying())
{
- m_LastGroundHeight = (float)GetPosY();
+ m_LastGroundHeight = static_cast<float>(GetPosY());
}
if (m_TicksUntilNextSave == 0)
@@ -296,7 +296,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
-short cPlayer::CalcLevelFromXp(short a_XpTotal)
+int cPlayer::CalcLevelFromXp(int a_XpTotal)
{
// level 0 to 15
if (a_XpTotal <= XP_TO_LEVEL15)
@@ -307,18 +307,18 @@ short cPlayer::CalcLevelFromXp(short a_XpTotal)
// level 30+
if (a_XpTotal > XP_TO_LEVEL30)
{
- return (short) (151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7;
+ return static_cast<int>((151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7);
}
// level 16 to 30
- return (short) ( 29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal)))) / 3;
+ return static_cast<int>((29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal)))) / 3);
}
-short cPlayer::XpForLevel(short a_Level)
+int cPlayer::XpForLevel(int a_Level)
{
// level 0 to 15
if (a_Level <= 15)
@@ -329,18 +329,18 @@ short cPlayer::XpForLevel(short a_Level)
// level 30+
if (a_Level >= 31)
{
- return (short) ( (3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220);
+ return static_cast<int>((3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220);
}
// level 16 to 30
- return (short) ( (1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360);
+ return static_cast<int>((1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360);
}
-short cPlayer::GetXpLevel()
+int cPlayer::GetXpLevel()
{
return CalcLevelFromXp(m_CurrentXp);
}
@@ -351,20 +351,20 @@ short cPlayer::GetXpLevel()
float cPlayer::GetXpPercentage()
{
- short int currentLevel = CalcLevelFromXp(m_CurrentXp);
- short int currentLevel_XpBase = XpForLevel(currentLevel);
+ int currentLevel = CalcLevelFromXp(m_CurrentXp);
+ int currentLevel_XpBase = XpForLevel(currentLevel);
- return (float)(m_CurrentXp - currentLevel_XpBase) /
- (float)(XpForLevel(1+currentLevel) - currentLevel_XpBase);
+ return static_cast<float>(m_CurrentXp - currentLevel_XpBase) /
+ static_cast<float>(XpForLevel(1+currentLevel) - currentLevel_XpBase);
}
-bool cPlayer::SetCurrentExperience(short int a_CurrentXp)
+bool cPlayer::SetCurrentExperience(int a_CurrentXp)
{
- if (!(a_CurrentXp >= 0) || (a_CurrentXp > (std::numeric_limits<short>().max() - m_LifetimeTotalXp)))
+ if (!(a_CurrentXp >= 0) || (a_CurrentXp > (std::numeric_limits<int>().max() - m_LifetimeTotalXp)))
{
LOGWARNING("Tried to update experiece with an invalid Xp value: %d", a_CurrentXp);
return false; // oops, they gave us a dodgey number
@@ -382,19 +382,20 @@ bool cPlayer::SetCurrentExperience(short int a_CurrentXp)
-short cPlayer::DeltaExperience(short a_Xp_delta)
+int cPlayer::DeltaExperience(int a_Xp_delta)
{
- if (a_Xp_delta > (std::numeric_limits<short>().max() - m_CurrentXp))
+ if (a_Xp_delta > (std::numeric_limits<int>().max() - m_CurrentXp))
{
// Value was bad, abort and report
- LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the short datatype. Ignoring.", a_Xp_delta);
+ LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the int datatype. Ignoring.", a_Xp_delta);
return -1; // Should we instead just return the current Xp?
}
m_CurrentXp += a_Xp_delta;
// Make sure they didn't subtract too much
- m_CurrentXp = std::max<short>(m_CurrentXp, 0);
+ m_CurrentXp = std::max(m_CurrentXp, 0);
+
// Update total for score calculation
if (a_Xp_delta > 0)
@@ -466,7 +467,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
{
if (GetPosY() > m_LastJumpHeight)
{
- m_LastJumpHeight = (float)GetPosY();
+ m_LastJumpHeight = static_cast<float>(GetPosY());
}
cWorld * World = GetWorld();
if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height))
@@ -483,13 +484,13 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
(BlockType == E_BLOCK_VINES)
)
{
- m_LastGroundHeight = (float)GetPosY();
+ m_LastGroundHeight = static_cast<float>(GetPosY());
}
}
}
else
{
- float Dist = (float)(m_LastGroundHeight - floor(GetPosY()));
+ float Dist = static_cast<float>(m_LastGroundHeight - floor(GetPosY()));
if (Dist >= 2.0) // At least two blocks - TODO: Use m_LastJumpHeight instead of m_LastGroundHeight above
{
@@ -497,12 +498,12 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
m_Stats.AddValue(statDistFallen, (StatValue)floor(Dist * 100 + 0.5));
}
- int Damage = (int)(Dist - 3.f);
+ int Damage = static_cast<int>(Dist - 3.f);
if (m_LastJumpHeight > m_LastGroundHeight)
{
Damage++;
}
- m_LastJumpHeight = (float)GetPosY();
+ m_LastJumpHeight = static_cast<float>(GetPosY());
if (Damage > 0)
{
@@ -510,10 +511,10 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
TakeDamage(dtFalling, nullptr, Damage, Damage, 0);
// Fall particles
- GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, (int)GetPosY() - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */);
+ GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, static_cast<int>(GetPosY()) - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */);
}
- m_LastGroundHeight = (float)GetPosY();
+ m_LastGroundHeight = static_cast<float>(GetPosY());
}
}
@@ -551,7 +552,7 @@ void cPlayer::SetFoodLevel(int a_FoodLevel)
void cPlayer::SetFoodSaturationLevel(double a_FoodSaturationLevel)
{
- m_FoodSaturationLevel = Clamp(a_FoodSaturationLevel, 0.0, (double) m_FoodLevel);
+ m_FoodSaturationLevel = Clamp(a_FoodSaturationLevel, 0.0, static_cast<double>(m_FoodLevel));
}
@@ -1274,13 +1275,17 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach)
void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
{
- SetPosition(a_PosX, a_PosY, a_PosZ);
- m_LastGroundHeight = (float)a_PosY;
- m_LastJumpHeight = (float)a_PosY;
- m_bIsTeleporting = true;
+ // ask plugins to allow teleport to the new position.
+ if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPos, Vector3d(a_PosX, a_PosY, a_PosZ)))
+ {
+ SetPosition(a_PosX, a_PosY, a_PosZ);
+ m_LastGroundHeight = static_cast<float>(a_PosY);
+ m_LastJumpHeight = static_cast<float>(a_PosY);
+ m_bIsTeleporting = true;
- m_World->BroadcastTeleportEntity(*this, GetClientHandle());
- m_ClientHandle->SendPlayerMoveLook();
+ m_World->BroadcastTeleportEntity(*this, GetClientHandle());
+ m_ClientHandle->SendPlayerMoveLook();
+ }
}
@@ -1714,9 +1719,9 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
Json::Value & JSON_PlayerRotation = root["rotation"];
if (JSON_PlayerRotation.size() == 3)
{
- SetYaw ((float)JSON_PlayerRotation[(unsigned)0].asDouble());
- SetPitch ((float)JSON_PlayerRotation[(unsigned)1].asDouble());
- SetRoll ((float)JSON_PlayerRotation[(unsigned)2].asDouble());
+ SetYaw (static_cast<float>(JSON_PlayerRotation[(unsigned)0].asDouble()));
+ SetPitch (static_cast<float>(JSON_PlayerRotation[(unsigned)1].asDouble()));
+ SetRoll (static_cast<float>(JSON_PlayerRotation[(unsigned)2].asDouble()));
}
m_Health = root.get("health", 0).asInt();
@@ -1725,9 +1730,9 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
m_FoodSaturationLevel = root.get("foodSaturation", MAX_FOOD_LEVEL).asDouble();
m_FoodTickTimer = root.get("foodTickTimer", 0).asInt();
m_FoodExhaustionLevel = root.get("foodExhaustion", 0).asDouble();
- m_LifetimeTotalXp = (short) root.get("xpTotal", 0).asInt();
- m_CurrentXp = (short) root.get("xpCurrent", 0).asInt();
- m_IsFlying = root.get("isflying", 0).asBool();
+ m_LifetimeTotalXp = root.get("xpTotal", 0).asInt();
+ m_CurrentXp = root.get("xpCurrent", 0).asInt();
+ m_IsFlying = root.get("isflying", 0).asBool();
m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt();
@@ -1812,18 +1817,18 @@ bool cPlayer::SaveToDisk()
root["world"] = m_World->GetName();
if (m_GameMode == m_World->GetGameMode())
{
- root["gamemode"] = (int) eGameMode_NotSet;
+ root["gamemode"] = static_cast<int>(eGameMode_NotSet);
}
else
{
- root["gamemode"] = (int) m_GameMode;
+ root["gamemode"] = static_cast<int>(m_GameMode);
}
}
else
{
// This happens if the player is saved to new format after loading from the old format
root["world"] = m_LoadedWorldName;
- root["gamemode"] = (int) eGameMode_NotSet;
+ root["gamemode"] = static_cast<int>(eGameMode_NotSet);
}
Json::StyledWriter writer;
@@ -1839,7 +1844,7 @@ bool cPlayer::SaveToDisk()
);
return false;
}
- if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size())
+ if (f.Write(JsonData.c_str(), JsonData.size()) != static_cast<int>(JsonData.size()))
{
LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot save data. Player will lose their progress. ",
GetName().c_str(), SourceFile.c_str()
@@ -1894,7 +1899,7 @@ void cPlayer::UseEquippedItem(int a_Amount)
if (GetInventory().DamageEquippedItem(a_Amount))
{
- m_World->BroadcastSoundEffect("random.break", GetPosX(), GetPosY(), GetPosZ(), 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
+ m_World->BroadcastSoundEffect("random.break", GetPosX(), GetPosY(), GetPosZ(), 0.5f, static_cast<float>(0.75 + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
}
}
@@ -2042,17 +2047,17 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos)
else if (IsSubmerged())
{
m_Stats.AddValue(statDistDove, Value);
- AddFoodExhaustion(0.00015 * (double)Value);
+ AddFoodExhaustion(0.00015 * static_cast<double>(Value));
}
else if (IsSwimming())
{
m_Stats.AddValue(statDistSwum, Value);
- AddFoodExhaustion(0.00015 * (double)Value);
+ AddFoodExhaustion(0.00015 * static_cast<double>(Value));
}
else if (IsOnGround())
{
m_Stats.AddValue(statDistWalked, Value);
- AddFoodExhaustion((m_IsSprinting ? 0.001 : 0.0001) * (double)Value);
+ AddFoodExhaustion((m_IsSprinting ? 0.001 : 0.0001) * static_cast<double>(Value));
}
else
{
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index fa9ac7cad..e02c66bd3 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -72,22 +72,22 @@ public:
Returns true on success
"should" really only be called at init or player death, plugins excepted
*/
- bool SetCurrentExperience(short a_XpTotal);
+ bool SetCurrentExperience(int a_XpTotal);
/* changes Xp by Xp_delta, you "shouldn't" inc more than MAX_EXPERIENCE_ORB_SIZE
Wont't allow xp to go negative
Returns the new current experience, -1 on error
*/
- short DeltaExperience(short a_Xp_delta);
+ int DeltaExperience(int a_Xp_delta);
/** Gets the experience total - XpTotal for score on death */
- inline short GetXpLifetimeTotal(void) { return m_LifetimeTotalXp; }
+ inline int GetXpLifetimeTotal(void) { return m_LifetimeTotalXp; }
/** Gets the currrent experience */
- inline short GetCurrentXp(void) { return m_CurrentXp; }
+ inline int GetCurrentXp(void) { return m_CurrentXp; }
/** Gets the current level - XpLevel */
- short GetXpLevel(void);
+ int GetXpLevel(void);
/** Gets the experience bar percentage - XpP */
float GetXpPercentage(void);
@@ -95,13 +95,13 @@ public:
/** Caculates the amount of XP needed for a given level
Ref: http://minecraft.gamepedia.com/XP
*/
- static short XpForLevel(short int a_Level);
+ static int XpForLevel(int a_Level);
/** Inverse of XpForLevel
Ref: http://minecraft.gamepedia.com/XP
values are as per this with pre-calculations
*/
- static short CalcLevelFromXp(short int a_CurrentXp);
+ static int CalcLevelFromXp(int a_CurrentXp);
// tolua_end
@@ -121,7 +121,7 @@ public:
inline void SetStance( const double a_Stance) { m_Stance = a_Stance; }
double GetEyeHeight(void) const; // tolua_export
Vector3d GetEyePosition(void) const; // tolua_export
- inline bool IsOnGround(void) const {return m_bTouchGround; } // tolua_export
+ virtual bool IsOnGround(void) const override { return m_bTouchGround; }
inline double GetStance(void) const { return GetPosY() + 1.62; } // tolua_export // TODO: Proper stance when crouching etc.
inline cInventory & GetInventory(void) { return m_Inventory; } // tolua_export
inline const cInventory & GetInventory(void) const { return m_Inventory; }
@@ -581,8 +581,8 @@ protected:
Int64 m_EatingFinishTick;
/** Player Xp level */
- short int m_LifetimeTotalXp;
- short int m_CurrentXp;
+ int m_LifetimeTotalXp;
+ int m_CurrentXp;
// flag saying we need to send a xp update to client
bool m_bDirtyExperience;
diff --git a/src/Generating/BioGen.cpp b/src/Generating/BioGen.cpp
index 378ece6a3..265db30ad 100644
--- a/src/Generating/BioGen.cpp
+++ b/src/Generating/BioGen.cpp
@@ -1036,7 +1036,7 @@ protected:
////////////////////////////////////////////////////////////////////////////////
-// cBioGenGrown:
+// cBioGenProtGrown:
class cBioGenProtGrown:
public cBiomeGen
diff --git a/src/Generating/CompoGenBiomal.cpp b/src/Generating/CompoGenBiomal.cpp
index 030c2baa5..3140bd754 100644
--- a/src/Generating/CompoGenBiomal.cpp
+++ b/src/Generating/CompoGenBiomal.cpp
@@ -542,6 +542,20 @@ protected:
HasHadWater = true;
} // for y
a_ChunkDesc.SetBlockType(a_RelX, 0, a_RelZ, E_BLOCK_BEDROCK);
+
+ EMCSBiome MesaVersion = a_ChunkDesc.GetBiome(a_RelX, a_RelZ);
+ if ((MesaVersion == biMesaPlateauF) || (MesaVersion == biMesaPlateauFM))
+ {
+ if (Top < 95 + static_cast<int>(m_MesaFloor.CubicNoise2D(NoiseY * 2, NoiseX * 2) * 6))
+ {
+ return;
+ }
+
+ BLOCKTYPE Block = (m_MesaFloor.CubicNoise2D(NoiseX * 4, NoiseY * 4) < 0) ? E_BLOCK_DIRT : E_BLOCK_GRASS;
+ NIBBLETYPE Meta = (Block == E_BLOCK_GRASS) ? 0 : 1;
+
+ a_ChunkDesc.SetBlockTypeMeta(a_RelX, Top, a_RelZ, Block, Meta);
+ }
}
diff --git a/src/Generating/ComposableGenerator.cpp b/src/Generating/ComposableGenerator.cpp
index bda45ad92..4a670b064 100644
--- a/src/Generating/ComposableGenerator.cpp
+++ b/src/Generating/ComposableGenerator.cpp
@@ -616,6 +616,11 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile)
int MaxDensity = a_IniFile.GetValueSetI("Generator", "VillageMaxDensity", 80);
m_FinishGens.push_back(std::make_shared<cVillageGen>(Seed, GridSize, MaxOffset, MaxDepth, MaxSize, MinDensity, MaxDensity, m_BiomeGen, m_CompositedHeightCache));
}
+ else if (NoCaseCompare(*itr, "Vines") == 0)
+ {
+ int Level = a_IniFile.GetValueSetI("Generator", "VinesLevel", 40);
+ m_FinishGens.push_back(std::make_shared<cFinishGenVines>(Seed, Level));
+ }
else if (NoCaseCompare(*itr, "WaterLakes") == 0)
{
int Probability = a_IniFile.GetValueSetI("Generator", "WaterLakesProbability", 25);
diff --git a/src/Generating/FinishGen.cpp b/src/Generating/FinishGen.cpp
index e10cb00f8..260253d62 100644
--- a/src/Generating/FinishGen.cpp
+++ b/src/Generating/FinishGen.cpp
@@ -244,6 +244,100 @@ void cFinishGenTallGrass::GenFinish(cChunkDesc & a_ChunkDesc)
////////////////////////////////////////////////////////////////////////////////
+// cFinishGenVines
+
+bool cFinishGenVines::IsJungleVariant(EMCSBiome a_Biome)
+{
+ switch (a_Biome)
+ {
+ case biJungle:
+ case biJungleEdge:
+ case biJungleEdgeM:
+ case biJungleHills:
+ case biJungleM:
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+
+
+
+void cFinishGenVines::GenFinish(cChunkDesc & a_ChunkDesc)
+{
+ for (int x = 0; x < cChunkDef::Width; x++)
+ {
+ int xx = x + a_ChunkDesc.GetChunkX() * cChunkDef::Width;
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ int zz = z + a_ChunkDesc.GetChunkZ() * cChunkDef::Width;
+ if (!IsJungleVariant(a_ChunkDesc.GetBiome(x, z)))
+ {
+ // Current biome isn't a jungle
+ continue;
+ }
+
+ if ((m_Noise.IntNoise2DInt(xx, zz) % 101) < 50)
+ {
+ continue;
+ }
+
+ int Height = a_ChunkDesc.GetHeight(x, z);
+ for (int y = Height; y > m_Level; y--)
+ {
+ if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR)
+ {
+ // Can't place vines in non-air blocks
+ continue;
+ }
+
+ if ((m_Noise.IntNoise3DInt(xx, y, zz) % 101) < 50)
+ {
+ continue;
+ }
+
+ std::vector<NIBBLETYPE> Places;
+ if ((x + 1 < cChunkDef::Width) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x + 1, y, z)))
+ {
+ Places.push_back(8);
+ }
+
+ if ((x - 1 > 0) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x - 1, y, z)))
+ {
+ Places.push_back(2);
+ }
+
+ if ((z + 1 < cChunkDef::Width) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x, y, z + 1)))
+ {
+ Places.push_back(1);
+ }
+
+ if ((z - 1 > 0) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x, y, z - 1)))
+ {
+ Places.push_back(4);
+ }
+
+ if (Places.size() == 0)
+ {
+ continue;
+ }
+
+ NIBBLETYPE Meta = Places[m_Noise.IntNoise3DInt(xx, y, zz) % Places.size()];
+ a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_VINES, Meta);
+ }
+ }
+ }
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
// cFinishGenSprinkleFoliage:
bool cFinishGenSprinkleFoliage::TryAddSugarcane(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ)
@@ -470,30 +564,22 @@ void cFinishGenSnow::GenFinish(cChunkDesc & a_ChunkDesc)
{
for (int x = 0; x < cChunkDef::Width; x++)
{
- switch (a_ChunkDesc.GetBiome(x, z))
+ int Height = a_ChunkDesc.GetHeight(x, z);
+ if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height)
{
- case biIcePlains:
- case biIceMountains:
- case biTaiga:
- case biTaigaHills:
- case biFrozenRiver:
- case biFrozenOcean:
- {
- int Height = a_ChunkDesc.GetHeight(x, z);
- if (cBlockInfo::IsSnowable(a_ChunkDesc.GetBlockType(x, Height, z)) && (Height < cChunkDef::Height - 1))
- {
- a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW);
- a_ChunkDesc.SetHeight(x, z, Height + 1);
- }
- break;
- }
- default:
- {
- // There's no snow in the other biomes.
- break;
- }
+ // Height isn't high enough for snow to start forming.
+ continue;
}
- }
+
+ if (!cBlockInfo::IsSnowable(a_ChunkDesc.GetBlockType(x, Height, z)) && (Height < cChunkDef::Height - 1))
+ {
+ // The top block can't be snown over.
+ continue;
+ }
+
+ a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW);
+ a_ChunkDesc.SetHeight(x, z, Height + 1);
+ } // for x
} // for z
}
@@ -511,34 +597,27 @@ void cFinishGenIce::GenFinish(cChunkDesc & a_ChunkDesc)
{
for (int x = 0; x < cChunkDef::Width; x++)
{
- switch (a_ChunkDesc.GetBiome(x, z))
+ int Height = a_ChunkDesc.GetHeight(x, z);
+ if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height)
{
- case biIcePlains:
- case biIceMountains:
- case biTaiga:
- case biTaigaHills:
- case biFrozenRiver:
- case biFrozenOcean:
- {
- int Height = a_ChunkDesc.GetHeight(x, z);
- switch (a_ChunkDesc.GetBlockType(x, Height, z))
- {
- case E_BLOCK_WATER:
- case E_BLOCK_STATIONARY_WATER:
- {
- a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE);
- break;
- }
- }
- break;
- }
- default:
- {
- // No icy water in other biomes.
- break;
- }
+ // Height isn't high enough for snow to start forming.
+ continue;
}
- }
+
+ if (!IsBlockWater(a_ChunkDesc.GetBlockType(x, Height, z)))
+ {
+ // The block isn't a water block.
+ continue;
+ }
+
+ if (a_ChunkDesc.GetBlockMeta(x, Height, z) != 0)
+ {
+ // The water block isn't a source block.
+ continue;
+ }
+
+ a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE);
+ } // for x
} // for z
}
diff --git a/src/Generating/FinishGen.h b/src/Generating/FinishGen.h
index ae6dee590..70696c4f8 100644
--- a/src/Generating/FinishGen.h
+++ b/src/Generating/FinishGen.h
@@ -118,6 +118,29 @@ protected:
+class cFinishGenVines :
+ public cFinishGen
+{
+public:
+ cFinishGenVines(int a_Seed, int a_Level) :
+ m_Noise(a_Seed),
+ m_Level(a_Level)
+ {
+ }
+
+ bool IsJungleVariant(EMCSBiome a_Biome);
+
+protected:
+ cNoise m_Noise;
+ int m_Level;
+
+ virtual void GenFinish(cChunkDesc & a_ChunkDesc) override;
+};
+
+
+
+
+
class cFinishGenSoulsandRims :
public cFinishGen
{
diff --git a/src/Generating/HeiGen.cpp b/src/Generating/HeiGen.cpp
index 61d087c17..54567cb4d 100644
--- a/src/Generating/HeiGen.cpp
+++ b/src/Generating/HeiGen.cpp
@@ -10,6 +10,66 @@
#include "DistortedHeightmap.h"
#include "EndGen.h"
#include "Noise3DGenerator.h"
+#include "ProtIntGen.h"
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cHeiGenSteppy:
+
+class cHeiGenSteppy:
+ public cTerrainHeightGen
+{
+public:
+ cHeiGenSteppy(int a_Seed) :
+ m_Seed(a_Seed)
+ {
+ m_Gen =
+ std::make_shared<cProtIntGenWeightAvg<16, 1, 0>>(
+ std::make_shared<cProtIntGenSmooth> (a_Seed + 1,
+ std::make_shared<cProtIntGenZoom> (a_Seed + 2,
+ std::make_shared<cProtIntGenSmooth> (a_Seed + 3,
+ std::make_shared<cProtIntGenZoom> (a_Seed + 4,
+ std::make_shared<cProtIntGenAddRnd> (a_Seed + 5, 1,
+ std::make_shared<cProtIntGenSmooth> (a_Seed + 6,
+ std::make_shared<cProtIntGenZoom> (a_Seed + 7,
+ std::make_shared<cProtIntGenRndBetween> (a_Seed + 8, 60,
+ std::make_shared<cProtIntGenAddRnd> (a_Seed + 9, 1,
+ std::make_shared<cProtIntGenSmooth> (a_Seed + 1,
+ std::make_shared<cProtIntGenZoom> (a_Seed + 2,
+ std::make_shared<cProtIntGenRndBetween> (a_Seed + 3, 60,
+ std::make_shared<cProtIntGenSmooth> (a_Seed + 4,
+ std::make_shared<cProtIntGenZoom> (a_Seed + 5,
+ std::make_shared<cProtIntGenRndBetween> (a_Seed + 6, 60,
+ std::make_shared<cProtIntGenRndChoice> (a_Seed + 7, 10, 50, 50,
+ std::make_shared<cProtIntGenSmooth> (a_Seed + 8,
+ std::make_shared<cProtIntGenZoom> (a_Seed + 9,
+ std::make_shared<cProtIntGenRndChoice> (a_Seed + 1, 10, 50, 50,
+ std::make_shared<cProtIntGenAddRnd> (a_Seed + 2, 2,
+ std::make_shared<cProtIntGenZoom> (a_Seed + 3,
+ std::make_shared<cProtIntGenZoom> (a_Seed + 4,
+ std::make_shared<cProtIntGenChoice> (a_Seed + 5, 10)
+ )))))))))))))))))))))));
+ }
+
+ // cTerrainHeightGen overrides:
+ virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override
+ {
+ int heights[cChunkDef::Width * cChunkDef::Width];
+ m_Gen->GetInts(a_ChunkX * cChunkDef::Width, a_ChunkZ * cChunkDef::Width, cChunkDef::Width, cChunkDef::Width, heights);
+ for (size_t i = 0; i < ARRAYCOUNT(heights); i++)
+ {
+ a_HeightMap[i] = static_cast<HEIGHTTYPE>(std::max(std::min(60 + heights[i], cChunkDef::Height - 60), 40));
+ }
+ }
+
+protected:
+ int m_Seed;
+
+ SharedPtr<cProtIntGen> m_Gen;
+};
@@ -821,6 +881,10 @@ cTerrainHeightGenPtr cTerrainHeightGen::CreateHeightGen(cIniFile & a_IniFile, cB
// Return an empty pointer, the caller will create the proper generator:
return cTerrainHeightGenPtr();
}
+ else if (NoCaseCompare(HeightGenName, "Steppy") == 0)
+ {
+ res = std::make_shared<cHeiGenSteppy>(a_Seed);
+ }
else if (NoCaseCompare(HeightGenName, "Noise3D") == 0)
{
// Not a heightmap-based generator, but it used to be accessible via HeightGen, so we need to skip making the default out of it
diff --git a/src/Generating/ProtIntGen.h b/src/Generating/ProtIntGen.h
index 73ed27096..e709222fe 100644
--- a/src/Generating/ProtIntGen.h
+++ b/src/Generating/ProtIntGen.h
@@ -318,6 +318,350 @@ protected:
+/** Averages the values of the underlying 2 * 2 neighbors. */
+class cProtIntGenAvgValues :
+ public cProtIntGen
+{
+ typedef cProtIntGen super;
+
+public:
+ cProtIntGenAvgValues(Underlying a_Underlying) :
+ m_Underlying(a_Underlying)
+ {
+ }
+
+
+ virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override
+ {
+ // Generate the underlying values:
+ int lowerSizeX = a_SizeX + 1;
+ int lowerSizeZ = a_SizeZ + 1;
+ ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize);
+ int lowerData[m_BufferSize];
+ m_Underlying->GetInts(a_MinX, a_MinZ, lowerSizeX, lowerSizeZ, lowerData);
+
+ // Average - add all 4 "neighbors" and divide by 4:
+ for (int z = 0; z < a_SizeZ; z++)
+ {
+ for (int x = 0; x < a_SizeX; x++)
+ {
+ int idxLower = x + lowerSizeX * z;
+ a_Values[x + a_SizeX * z] = (
+ lowerData[idxLower] + lowerData[idxLower + 1] +
+ lowerData[idxLower + lowerSizeX] + lowerData[idxLower + lowerSizeX + 1]
+ ) / 4;
+ }
+ }
+ }
+
+protected:
+ Underlying m_Underlying;
+};
+
+
+
+
+
+/** Averages the values of the underlying 4 * 4 neighbors. */
+class cProtIntGenAvg4Values :
+ public cProtIntGen
+{
+ typedef cProtIntGen super;
+
+public:
+ cProtIntGenAvg4Values(Underlying a_Underlying) :
+ m_Underlying(a_Underlying)
+ {
+ }
+
+
+ virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override
+ {
+ // Generate the underlying values:
+ int lowerSizeX = a_SizeX + 4;
+ int lowerSizeZ = a_SizeZ + 4;
+ ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize);
+ int lowerData[m_BufferSize];
+ m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData);
+
+ // Calculate the weighted average of all 16 "neighbors":
+ for (int z = 0; z < a_SizeZ; z++)
+ {
+ for (int x = 0; x < a_SizeX; x++)
+ {
+ int idxLower1 = x + lowerSizeX * z;
+ int idxLower2 = idxLower1 + lowerSizeX;
+ int idxLower3 = idxLower1 + 2 * lowerSizeX;
+ int idxLower4 = idxLower1 + 3 * lowerSizeX;
+ a_Values[x + a_SizeX * z] = (
+ 1 * lowerData[idxLower1] + 2 * lowerData[idxLower1 + 1] + 2 * lowerData[idxLower1 + 2] + 1 * lowerData[idxLower1 + 3] +
+ 2 * lowerData[idxLower2] + 32 * lowerData[idxLower2 + 1] + 32 * lowerData[idxLower2 + 2] + 2 * lowerData[idxLower2 + 3] +
+ 2 * lowerData[idxLower3] + 32 * lowerData[idxLower3 + 1] + 32 * lowerData[idxLower3 + 2] + 2 * lowerData[idxLower3 + 3] +
+ 1 * lowerData[idxLower4] + 2 * lowerData[idxLower4 + 1] + 2 * lowerData[idxLower4 + 2] + 1 * lowerData[idxLower4 + 3]
+ ) / 148;
+ }
+ }
+ }
+
+protected:
+ Underlying m_Underlying;
+};
+
+
+
+
+
+/** Averages the values of the underlying 3 * 3 neighbors with custom weight. */
+template <int WeightCenter, int WeightCardinal, int WeightDiagonal>
+class cProtIntGenWeightAvg :
+ public cProtIntGen
+{
+ typedef cProtIntGen super;
+
+public:
+ cProtIntGenWeightAvg(Underlying a_Underlying) :
+ m_Underlying(a_Underlying)
+ {
+ }
+
+
+ virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override
+ {
+ // Generate the underlying values:
+ int lowerSizeX = a_SizeX + 3;
+ int lowerSizeZ = a_SizeZ + 3;
+ ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize);
+ int lowerData[m_BufferSize];
+ m_Underlying->GetInts(a_MinX, a_MinZ, lowerSizeX, lowerSizeZ, lowerData);
+
+ // Calculate the weighted average the neighbors:
+ for (int z = 0; z < a_SizeZ; z++)
+ {
+ for (int x = 0; x < a_SizeX; x++)
+ {
+ int idxLower1 = x + lowerSizeX * z;
+ int idxLower2 = idxLower1 + lowerSizeX;
+ int idxLower3 = idxLower1 + 2 * lowerSizeX;
+ a_Values[x + a_SizeX * z] = (
+ WeightDiagonal * lowerData[idxLower1] + WeightCardinal * lowerData[idxLower1 + 1] + WeightDiagonal * lowerData[idxLower1 + 2] +
+ WeightCardinal * lowerData[idxLower2] + WeightCenter * lowerData[idxLower2 + 1] + WeightCardinal * lowerData[idxLower2 + 2] +
+ WeightDiagonal * lowerData[idxLower3] + WeightCardinal * lowerData[idxLower3 + 1] + WeightDiagonal * lowerData[idxLower3 + 2]
+ ) / (4 * WeightDiagonal + 4 * WeightCardinal + WeightCenter);
+ }
+ }
+ }
+
+protected:
+ Underlying m_Underlying;
+};
+
+
+
+
+
+/** Replaces random values of the underlying data with random integers in the specified range [Min .. Min + Range). */
+class cProtIntGenRndChoice :
+ public cProtIntGenWithNoise
+{
+ typedef cProtIntGenWithNoise super;
+
+public:
+ cProtIntGenRndChoice(int a_Seed, int a_ChancePct, int a_Min, int a_Range, Underlying a_Underlying) :
+ super(a_Seed),
+ m_ChancePct(a_ChancePct),
+ m_Min(a_Min),
+ m_Range(a_Range),
+ m_Underlying(a_Underlying)
+ {
+ }
+
+
+ virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override
+ {
+ // Generate the underlying values:
+ m_Underlying->GetInts(a_MinX, a_MinZ, a_SizeX, a_SizeZ, a_Values);
+
+ // Replace random values:
+ for (int z = 0; z < a_SizeZ; z++)
+ {
+ int BaseZ = a_MinZ + z;
+ for (int x = 0; x < a_SizeX; x++)
+ {
+ if (((super::m_Noise.IntNoise2DInt(BaseZ, a_MinX + x) / 13) % 101) < m_ChancePct)
+ {
+ a_Values[x + a_SizeX * z] = m_Min + (super::m_Noise.IntNoise2DInt(a_MinX + x, BaseZ) / 7) % m_Range;
+ }
+ } // for x
+ } // for z
+ }
+
+protected:
+ int m_ChancePct;
+ int m_Min;
+ int m_Range;
+ Underlying m_Underlying;
+};
+
+
+
+
+
+/** Adds a random value in range [-a_HalfRange, +a_HalfRange] to each of the underlying values. */
+class cProtIntGenAddRnd :
+ public cProtIntGenWithNoise
+{
+ typedef cProtIntGenWithNoise super;
+
+public:
+ cProtIntGenAddRnd(int a_Seed, int a_HalfRange, Underlying a_Underlying) :
+ super(a_Seed),
+ m_Range(a_HalfRange * 2 + 1),
+ m_HalfRange(a_HalfRange),
+ m_Underlying(a_Underlying)
+ {
+ }
+
+
+ virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override
+ {
+ // Generate the underlying values:
+ m_Underlying->GetInts(a_MinX, a_MinZ, a_SizeX, a_SizeZ, a_Values);
+
+ // Add the random values:
+ for (int z = 0; z < a_SizeZ; z++)
+ {
+ int NoiseZ = a_MinZ + z;
+ for (int x = 0; x < a_SizeX; x++)
+ {
+ int noiseVal = ((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % m_Range) - m_HalfRange;
+ a_Values[x + z * a_SizeX] += noiseVal;
+ }
+ }
+ }
+
+protected:
+ int m_Range;
+ int m_HalfRange;
+ Underlying m_Underlying;
+};
+
+
+
+
+
+/** Replaces random underlying values with the average of the neighbors. */
+class cProtIntGenRndAvg :
+ public cProtIntGenWithNoise
+{
+ typedef cProtIntGenWithNoise super;
+
+public:
+ cProtIntGenRndAvg(int a_Seed, int a_AvgChancePct, Underlying a_Underlying) :
+ super(a_Seed),
+ m_AvgChancePct(a_AvgChancePct),
+ m_Underlying(a_Underlying)
+ {
+ }
+
+
+ virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override
+ {
+ // Generate the underlying values:
+ int lowerSizeX = a_SizeX + 2;
+ int lowerSizeZ = a_SizeZ + 2;
+ ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize);
+ int lowerData[m_BufferSize];
+ m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData);
+
+ // Average random values:
+ for (int z = 0; z < a_SizeZ; z++)
+ {
+ int NoiseZ = a_MinZ + z;
+ for (int x = 0; x < a_SizeX; x++)
+ {
+ int idxLower = x + 1 + lowerSizeX * (z + 1);
+ if (((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % 100) > m_AvgChancePct)
+ {
+ // Average the 4 neighbors:
+ a_Values[x + z * a_SizeX] = (
+ lowerData[idxLower - 1] + lowerData[idxLower + 1] +
+ lowerData[idxLower - lowerSizeX] + lowerData[idxLower + lowerSizeX]
+ ) / 4;
+ }
+ else
+ {
+ // Keep the underlying value:
+ a_Values[x + z * a_SizeX] = lowerData[idxLower];
+ }
+ }
+ }
+ }
+
+protected:
+ int m_AvgChancePct;
+ Underlying m_Underlying;
+};
+
+
+
+
+
+/** Replaces random underlying values with a random value in between the max and min of the neighbors. */
+class cProtIntGenRndBetween :
+ public cProtIntGenWithNoise
+{
+ typedef cProtIntGenWithNoise super;
+
+public:
+ cProtIntGenRndBetween(int a_Seed, int a_AvgChancePct, Underlying a_Underlying) :
+ super(a_Seed),
+ m_AvgChancePct(a_AvgChancePct),
+ m_Underlying(a_Underlying)
+ {
+ }
+
+
+ virtual void GetInts(int a_MinX, int a_MinZ, int a_SizeX, int a_SizeZ, int * a_Values) override
+ {
+ // Generate the underlying values:
+ int lowerSizeX = a_SizeX + 2;
+ int lowerSizeZ = a_SizeZ + 2;
+ ASSERT(lowerSizeX * lowerSizeZ <= m_BufferSize);
+ int lowerData[m_BufferSize];
+ m_Underlying->GetInts(a_MinX - 1, a_MinZ - 1, lowerSizeX, lowerSizeZ, lowerData);
+
+ // Average random values:
+ for (int z = 0; z < a_SizeZ; z++)
+ {
+ int NoiseZ = a_MinZ + z;
+ for (int x = 0; x < a_SizeX; x++)
+ {
+ int idxLower = x + 1 + lowerSizeX * (z + 1);
+ if (((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ) / 7) % 100) > m_AvgChancePct)
+ {
+ // Chose a value in between the min and max neighbor:
+ int min = std::min(std::min(lowerData[idxLower - 1], lowerData[idxLower + 1]), std::min(lowerData[idxLower - lowerSizeX], lowerData[idxLower + lowerSizeX]));
+ int max = std::max(std::max(lowerData[idxLower - 1], lowerData[idxLower + 1]), std::max(lowerData[idxLower - lowerSizeX], lowerData[idxLower + lowerSizeX]));
+ a_Values[x + z * a_SizeX] = min + ((super::m_Noise.IntNoise2DInt(a_MinX + x, NoiseZ + 10) / 7) % (max - min + 1));
+ }
+ else
+ {
+ // Keep the underlying value:
+ a_Values[x + z * a_SizeX] = lowerData[idxLower];
+ }
+ }
+ }
+ }
+
+protected:
+ int m_AvgChancePct;
+ Underlying m_Underlying;
+};
+
+
+
+
+
/** Converts land biomes at the edge of an ocean into the respective beach biome. */
class cProtIntGenBeaches :
public cProtIntGen
diff --git a/src/Generating/Trees.cpp b/src/Generating/Trees.cpp
index a10e0f4f1..9e72a688f 100644
--- a/src/Generating/Trees.cpp
+++ b/src/Generating/Trees.cpp
@@ -224,9 +224,6 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No
case biMegaTaiga:
case biMegaTaigaHills:
case biExtremeHillsPlus:
- case biMesa:
- case biMesaPlateauF:
- case biMesaPlateau:
case biSunflowerPlains:
case biDesertM:
case biExtremeHillsM:
@@ -239,9 +236,6 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No
case biMegaSpruceTaiga:
case biMegaSpruceTaigaHills:
case biExtremeHillsPlusM:
- case biMesaBryce:
- case biMesaPlateauFM:
- case biMesaPlateauM:
{
// TODO: These need their special trees
GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks);
@@ -264,6 +258,16 @@ void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_No
return;
}
+ case biMesa:
+ case biMesaPlateauF:
+ case biMesaPlateau:
+ case biMesaBryce:
+ case biMesaPlateauFM:
+ case biMesaPlateauM:
+ {
+ GetSmallAppleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks);
+ }
+
case biDesert:
case biDesertHills:
case biRiver:
diff --git a/src/Globals.h b/src/Globals.h
index 654ede95f..7c2ab38d8 100644
--- a/src/Globals.h
+++ b/src/Globals.h
@@ -379,8 +379,10 @@ void inline LOG(const char * a_Format, ...)
#define assert_test(x) ( !!(x) || (assert(!#x), exit(1), 0))
#endif
-// Unified shared ptr from before C++11. Also no silly undercores.
+// Unified ptr types from before C++11. Also no silly undercores.
#define SharedPtr std::shared_ptr
+#define WeakPtr std::weak_ptr
+#define UniquePtr std::unique_ptr
diff --git a/src/HTTPServer/SslHTTPConnection.cpp b/src/HTTPServer/SslHTTPConnection.cpp
index f09daac8f..f8dea0731 100644
--- a/src/HTTPServer/SslHTTPConnection.cpp
+++ b/src/HTTPServer/SslHTTPConnection.cpp
@@ -25,6 +25,15 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce
+cSslHTTPConnection::~cSslHTTPConnection()
+{
+ m_Ssl.NotifyClose();
+}
+
+
+
+
+
void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
// Process the received data:
diff --git a/src/HTTPServer/SslHTTPConnection.h b/src/HTTPServer/SslHTTPConnection.h
index dc54b1eff..c461a3a24 100644
--- a/src/HTTPServer/SslHTTPConnection.h
+++ b/src/HTTPServer/SslHTTPConnection.h
@@ -25,6 +25,8 @@ public:
/** Creates a new connection on the specified server.
Sends the specified cert as the server certificate, uses the private key for decryption. */
cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey);
+
+ ~cSslHTTPConnection();
protected:
cBufferedSslContext m_Ssl;
diff --git a/src/Items/ItemDoor.h b/src/Items/ItemDoor.h
index dacf286e5..71143d5a8 100644
--- a/src/Items/ItemDoor.h
+++ b/src/Items/ItemDoor.h
@@ -40,7 +40,7 @@ public:
}
// The door needs a compatible block below it:
- if ((a_BlockY > 0) && !cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ)))
+ if (!cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ)))
{
return false;
}
@@ -62,10 +62,10 @@ public:
return false;
}
}
-
+
// Check the two blocks that will get replaced by the door:
- BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ);
- BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 2, a_BlockZ);
+ BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ);
+ BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ);
if (
!cBlockDoorHandler::CanReplaceBlock(LowerBlockType) ||
!cBlockDoorHandler::CanReplaceBlock(UpperBlockType))
@@ -77,19 +77,32 @@ public:
NIBBLETYPE LowerBlockMeta = cBlockDoorHandler::PlayerYawToMetaData(a_Player.GetYaw());
Vector3i RelDirToOutside = cBlockDoorHandler::GetRelativeDirectionToOutside(LowerBlockMeta);
Vector3i LeftNeighborPos = RelDirToOutside;
- LeftNeighborPos.TurnCCW();
+ LeftNeighborPos.TurnCW();
LeftNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ);
Vector3i RightNeighborPos = RelDirToOutside;
- RightNeighborPos.TurnCW();
+ RightNeighborPos.TurnCCW();
RightNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ);
// Decide whether the hinge is on the left (default) or on the right:
NIBBLETYPE UpperBlockMeta = 0x08;
+ BLOCKTYPE LeftNeighborBlock = a_World.GetBlock(LeftNeighborPos);
+ BLOCKTYPE RightNeighborBlock = a_World.GetBlock(RightNeighborPos);
+ /*
+ // DEBUG:
+ LOGD("Door being placed at {%d, %d, %d}", a_BlockX, a_BlockY, a_BlockZ);
+ LOGD("RelDirToOutside: {%d, %d, %d}", RelDirToOutside.x, RelDirToOutside.y, RelDirToOutside.z);
+ LOGD("Left neighbor at {%d, %d, %d}: %d (%s)", LeftNeighborPos.x, LeftNeighborPos.y, LeftNeighborPos.z, LeftNeighborBlock, ItemTypeToString(LeftNeighborBlock).c_str());
+ LOGD("Right neighbor at {%d, %d, %d}: %d (%s)", RightNeighborPos.x, RightNeighborPos.y, RightNeighborPos.z, RightNeighborBlock, ItemTypeToString(RightNeighborBlock).c_str());
+ */
if (
- cBlockDoorHandler::IsDoorBlockType(a_World.GetBlock(LeftNeighborPos)) || // The block to the left is a door block
- cBlockInfo::IsSolid(a_World.GetBlock(RightNeighborPos)) // The block to the right is solid
+ cBlockDoorHandler::IsDoorBlockType(LeftNeighborBlock) || // The block to the left is a door block
+ (
+ cBlockInfo::IsSolid(RightNeighborBlock) && // The block to the right is solid...
+ !cBlockDoorHandler::IsDoorBlockType(RightNeighborBlock) // ... but not a door
+ )
)
{
+ // DEBUG: LOGD("Setting hinge to right side");
UpperBlockMeta = 0x09; // Upper block | hinge on right
}
@@ -106,7 +119,3 @@ public:
return true;
}
} ;
-
-
-
-
diff --git a/src/MobSpawner.cpp b/src/MobSpawner.cpp
index 0a32d17ef..541135996 100644
--- a/src/MobSpawner.cpp
+++ b/src/MobSpawner.cpp
@@ -110,7 +110,8 @@ eMonsterType cMobSpawner::ChooseMobType(EMCSBiome a_Biome)
if (allowedMobsSize > 0)
{
std::set<eMonsterType>::iterator itr = allowedMobs.begin();
- int iRandom = m_Random.NextInt((int)allowedMobsSize, a_Biome);
+ static int Counter = 0;
+ int iRandom = m_Random.NextInt((int)allowedMobsSize, Counter++);
for (int i = 0; i < iRandom; i++)
{
diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt
index 6f609c519..81b37ef0e 100644
--- a/src/OSSupport/CMakeLists.txt
+++ b/src/OSSupport/CMakeLists.txt
@@ -18,6 +18,7 @@ SET (SRCS
ServerHandleImpl.cpp
StackTrace.cpp
TCPLinkImpl.cpp
+ UDPEndpointImpl.cpp
)
SET (HDRS
@@ -36,6 +37,7 @@ SET (HDRS
ServerHandleImpl.h
StackTrace.h
TCPLinkImpl.h
+ UDPEndpointImpl.h
)
if(NOT MSVC)
diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp
index 3a2997ffd..0944153be 100644
--- a/src/OSSupport/HostnameLookup.cpp
+++ b/src/OSSupport/HostnameLookup.cpp
@@ -69,12 +69,24 @@ void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a
case AF_INET: // IPv4
{
sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr->ai_addr);
+ if (!Self->m_Callbacks->OnNameResolvedV4(Self->m_Hostname, sin))
+ {
+ // Callback indicated that the IP shouldn't be serialized to a string, just continue with the next address:
+ HasResolved = true;
+ continue;
+ }
evutil_inet_ntop(AF_INET, &(sin->sin_addr), IP, sizeof(IP));
break;
}
case AF_INET6: // IPv6
{
sockaddr_in6 * sin = reinterpret_cast<sockaddr_in6 *>(a_Addr->ai_addr);
+ if (!Self->m_Callbacks->OnNameResolvedV6(Self->m_Hostname, sin))
+ {
+ // Callback indicated that the IP shouldn't be serialized to a string, just continue with the next address:
+ HasResolved = true;
+ continue;
+ }
evutil_inet_ntop(AF_INET6, &(sin->sin6_addr), IP, sizeof(IP));
break;
}
diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h
index cdf6ba0e9..5dd596223 100644
--- a/src/OSSupport/Network.h
+++ b/src/OSSupport/Network.h
@@ -90,6 +90,9 @@ public:
Sends the RST packet, queued outgoing and incoming data is lost. */
virtual void Close(void) = 0;
+ /** Returns the callbacks that are used. */
+ cCallbacksPtr GetCallbacks(void) const { return m_Callbacks; }
+
protected:
/** Callbacks to be used for the various situations. */
cCallbacksPtr m_Callbacks;
@@ -127,6 +130,64 @@ public:
+/** Interface that provides methods available on UDP communication endpoints. */
+class cUDPEndpoint
+{
+public:
+ /** Interface for the callbacks for events that can happen on the endpoint. */
+ class cCallbacks
+ {
+ public:
+ // Force a virtual destructor in all descendants:
+ virtual ~cCallbacks() {}
+
+ /** Called when an error occurs on the endpoint. */
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0;
+
+ /** Called when there is an incoming datagram from a remote host. */
+ virtual void OnReceivedData(const char * a_Data, size_t a_Size, const AString & a_RemoteHost, UInt16 a_RemotePort) = 0;
+ };
+
+
+ // Force a virtual destructor for all descendants:
+ virtual ~cUDPEndpoint() {}
+
+ /** Closes the underlying socket.
+ Note that there still might be callbacks in-flight after this method returns. */
+ virtual void Close(void) = 0;
+
+ /** Returns true if the endpoint is open. */
+ virtual bool IsOpen(void) const = 0;
+
+ /** Returns the local port to which the underlying socket is bound. */
+ virtual UInt16 GetPort(void) const = 0;
+
+ /** Sends the specified payload in a single UDP datagram to the specified host+port combination.
+ Note that in order to send to a broadcast address, you need to call EnableBroadcasts() first. */
+ virtual bool Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) = 0;
+
+ /** Marks the socket as capable of sending broadcast, using whatever OS API is needed.
+ Without this call, sending to a broadcast address using Send() may fail. */
+ virtual void EnableBroadcasts(void) = 0;
+
+protected:
+ /** The callbacks used for various events on the endpoint. */
+ cCallbacks & m_Callbacks;
+
+
+ /** Creates a new instance of an endpoint, with the specified callbacks. */
+ cUDPEndpoint(cCallbacks & a_Callbacks):
+ m_Callbacks(a_Callbacks)
+ {
+ }
+};
+
+typedef SharedPtr<cUDPEndpoint> cUDPEndpointPtr;
+
+
+
+
+
class cNetwork
{
public:
@@ -180,9 +241,22 @@ public:
/** Called when the hostname is successfully resolved into an IP address.
May be called multiple times if a name resolves to multiple addresses.
- a_IP may be either an IPv4 or an IPv6 address with their proper formatting. */
+ a_IP may be either an IPv4 or an IPv6 address with their proper formatting.
+ Each call to OnNameResolved() is preceded by a call to either OnNameResolvedV4() or OnNameResolvedV6(). */
virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0;
+ /** Called when the hostname is successfully resolved into an IPv4 address.
+ May be called multiple times if a name resolves to multiple addresses.
+ Each call to OnNameResolvedV4 is followed by OnNameResolved with the IP address serialized to a string.
+ If this callback returns false, the OnNameResolved() call is skipped for this address. */
+ virtual bool OnNameResolvedV4(const AString & a_Name, const sockaddr_in * a_IP) { return true; }
+
+ /** Called when the hostname is successfully resolved into an IPv6 address.
+ May be called multiple times if a name resolves to multiple addresses.
+ Each call to OnNameResolvedV4 is followed by OnNameResolved with the IP address serialized to a string.
+ If this callback returns false, the OnNameResolved() call is skipped for this address. */
+ virtual bool OnNameResolvedV6(const AString & a_Name, const sockaddr_in6 * a_IP) { return true; }
+
/** Called when an error is encountered while resolving.
If an error is reported, the OnFinished() callback is not called. */
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0;
@@ -239,6 +313,11 @@ public:
const AString & a_IP,
cResolveNameCallbacksPtr a_Callbacks
);
+
+ /** Opens up an UDP endpoint for sending and receiving UDP datagrams on the specified port.
+ If a_Port is 0, the OS is free to assign any port number it likes to the endpoint.
+ Returns the endpoint object that can be interacted with. */
+ static cUDPEndpointPtr CreateUDPEndpoint(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks);
};
diff --git a/src/OSSupport/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp
index 000b17641..358e24438 100644
--- a/src/OSSupport/NetworkSingleton.cpp
+++ b/src/OSSupport/NetworkSingleton.cpp
@@ -63,8 +63,7 @@ cNetworkSingleton::cNetworkSingleton(void):
}
// Create the event loop thread:
- std::thread EventLoopThread(RunEventLoop, this);
- EventLoopThread.detach();
+ m_EventLoopThread = std::thread(RunEventLoop, this);
}
@@ -98,7 +97,7 @@ void cNetworkSingleton::Terminate(void)
// Wait for the LibEvent event loop to terminate:
event_base_loopbreak(m_EventBase);
- m_EventLoopTerminated.Wait();
+ m_EventLoopThread.join();
// Remove all objects:
{
@@ -143,7 +142,6 @@ void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg)
void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self)
{
event_base_loop(a_Self->m_EventBase, EVLOOP_NO_EXIT_ON_EMPTY);
- a_Self->m_EventLoopTerminated.Set();
}
diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h
index e27e19012..0536a1c82 100644
--- a/src/OSSupport/NetworkSingleton.h
+++ b/src/OSSupport/NetworkSingleton.h
@@ -116,12 +116,12 @@ protected:
/** Mutex protecting all containers against multithreaded access. */
cCriticalSection m_CS;
- /** Event that gets signalled when the event loop terminates. */
- cEvent m_EventLoopTerminated;
-
/** Set to true if Terminate has been called. */
volatile bool m_HasTerminated;
+ /** The thread in which the main LibEvent loop runs. */
+ std::thread m_EventLoopThread;
+
/** Initializes the LibEvent internals. */
cNetworkSingleton(void);
diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp
index 5fc5662e1..44ace448b 100644
--- a/src/OSSupport/ServerHandleImpl.cpp
+++ b/src/OSSupport/ServerHandleImpl.cpp
@@ -125,6 +125,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
bool NeedsTwoSockets = false;
int err;
evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+
if (!IsValidSocket(MainSock))
{
// Failed to create IPv6 socket, create an IPv4 one instead:
@@ -138,6 +139,16 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
return false;
}
+ // Allow the port to be reused right after the socket closes:
+ if (evutil_make_listen_socket_reuseable(MainSock) != 0)
+ {
+ m_ErrorCode = EVUTIL_SOCKET_ERROR();
+ Printf(m_ErrorMsg, "Port %d cannot be made reusable: %d (%s). Restarting the server might not work.",
+ a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)
+ );
+ LOG("%s", m_ErrorMsg.c_str());
+ }
+
// Bind to all interfaces:
sockaddr_in name;
memset(&name, 0, sizeof(name));
@@ -164,6 +175,16 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
#endif
+ // Allow the port to be reused right after the socket closes:
+ if (evutil_make_listen_socket_reuseable(MainSock) != 0)
+ {
+ m_ErrorCode = EVUTIL_SOCKET_ERROR();
+ Printf(m_ErrorMsg, "Port %d cannot be made reusable: %d (%s). Restarting the server might not work.",
+ a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)
+ );
+ LOG("%s", m_ErrorMsg.c_str());
+ }
+
// Bind to all interfaces:
sockaddr_in6 name;
memset(&name, 0, sizeof(name));
@@ -193,6 +214,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
}
m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock);
m_IsListening = true;
+
if (!NeedsTwoSockets)
{
return true;
@@ -201,6 +223,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
// If a secondary socket is required (WinXP dual-stack), create it here:
LOGD("Creating a second socket for IPv4");
evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
if (!IsValidSocket(SecondSock))
{
err = EVUTIL_SOCKET_ERROR();
@@ -208,6 +231,16 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
return true; // Report as success, the primary socket is working
}
+ // Allow the port to be reused right after the socket closes:
+ if (evutil_make_listen_socket_reuseable(SecondSock) != 0)
+ {
+ m_ErrorCode = EVUTIL_SOCKET_ERROR();
+ Printf(m_ErrorMsg, "Port %d cannot be made reusable (second socket): %d (%s). Restarting the server might not work.",
+ a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)
+ );
+ LOG("%s", m_ErrorMsg.c_str());
+ }
+
// Make the secondary socket nonblocking:
if (evutil_make_socket_nonblocking(SecondSock) != 0)
{
@@ -233,7 +266,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
if (listen(SecondSock, 0) != 0)
{
err = EVUTIL_SOCKET_ERROR();
- LOGD("Cannot listen on on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err));
+ LOGD("Cannot listen on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err));
evutil_closesocket(SecondSock);
return true; // Report as success, the primary socket is working
}
diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp
index f97db7582..c6f1978ad 100644
--- a/src/OSSupport/TCPLinkImpl.cpp
+++ b/src/OSSupport/TCPLinkImpl.cpp
@@ -7,6 +7,7 @@
#include "TCPLinkImpl.h"
#include "NetworkSingleton.h"
#include "ServerHandleImpl.h"
+#include "event2/buffer.h"
@@ -17,7 +18,10 @@
cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks):
super(a_LinkCallbacks),
- m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE))
+ m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)),
+ m_LocalPort(0),
+ m_RemotePort(0),
+ m_ShouldShutdown(false)
{
LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
}
@@ -29,7 +33,10 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks):
cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen):
super(a_LinkCallbacks),
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)),
- m_Server(a_Server)
+ m_Server(a_Server),
+ m_LocalPort(0),
+ m_RemotePort(0),
+ m_ShouldShutdown(false)
{
LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
@@ -111,7 +118,7 @@ void cTCPLinkImpl::Enable(cTCPLinkImplPtr a_Self)
m_Self = a_Self;
// Set the LibEvent callbacks and enable processing:
- bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this);
+ bufferevent_setcb(m_BufferEvent, ReadCallback, WriteCallback, EventCallback, this);
bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE);
}
@@ -121,6 +128,11 @@ void cTCPLinkImpl::Enable(cTCPLinkImplPtr a_Self)
bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length)
{
+ if (m_ShouldShutdown)
+ {
+ LOGD("%s: Cannot send data, the link is already shut down.", __FUNCTION__);
+ return false;
+ }
return (bufferevent_write(m_BufferEvent, a_Data, a_Length) == 0);
}
@@ -130,12 +142,15 @@ bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length)
void cTCPLinkImpl::Shutdown(void)
{
- #ifdef _WIN32
- shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND);
- #else
- shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR);
- #endif
- bufferevent_disable(m_BufferEvent, EV_WRITE);
+ // If there's no outgoing data, shutdown the socket directly:
+ if (evbuffer_get_length(bufferevent_get_output(m_BufferEvent)) == 0)
+ {
+ DoActualShutdown();
+ return;
+ }
+
+ // There's still outgoing data in the LibEvent buffer, schedule a shutdown when it's written to OS's TCP stack:
+ m_ShouldShutdown = true;
}
@@ -181,6 +196,24 @@ void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self)
+void cTCPLinkImpl::WriteCallback(bufferevent * a_BufferEvent, void * a_Self)
+{
+ ASSERT(a_Self != nullptr);
+ auto Self = static_cast<cTCPLinkImpl *>(a_Self);
+ ASSERT(Self->m_Callbacks != nullptr);
+
+ // If there's no more data to write and the link has been scheduled for shutdown, do the shutdown:
+ auto OutLen = evbuffer_get_length(bufferevent_get_output(Self->m_BufferEvent));
+ if ((OutLen == 0) && (Self->m_ShouldShutdown))
+ {
+ Self->DoActualShutdown();
+ }
+}
+
+
+
+
+
void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self)
{
LOGD("cTCPLink event callback for link %p, BEV %p; what = 0x%02x", a_Self, a_BufferEvent, a_What);
@@ -221,6 +254,8 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void
// Pending connection succeeded, call the connection callback:
if (a_What & BEV_EVENT_CONNECTED)
{
+ Self->UpdateLocalAddress();
+ Self->UpdateRemoteAddress();
if (Self->m_ConnectCallbacks != nullptr)
{
Self->m_ConnectCallbacks->OnConnected(*Self);
@@ -228,8 +263,6 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void
Self->m_ConnectCallbacks.reset();
return;
}
- Self->UpdateLocalAddress();
- Self->UpdateRemoteAddress();
}
// If the connection has been closed, call the link callback and remove the connection:
@@ -316,6 +349,20 @@ void cTCPLinkImpl::UpdateRemoteAddress(void)
+void cTCPLinkImpl::DoActualShutdown(void)
+{
+ #ifdef _WIN32
+ shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND);
+ #else
+ shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR);
+ #endif
+ bufferevent_disable(m_BufferEvent, EV_WRITE);
+}
+
+
+
+
+
////////////////////////////////////////////////////////////////////////////////
// cNetwork API:
diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h
index 735e8ed9d..bea21aeff 100644
--- a/src/OSSupport/TCPLinkImpl.h
+++ b/src/OSSupport/TCPLinkImpl.h
@@ -94,6 +94,11 @@ protected:
Initialized in Enable(), cleared in Close() and EventCallback(RemoteClosed). */
cTCPLinkImplPtr m_Self;
+ /** If true, Shutdown() has been called and is in queue.
+ No more data is allowed to be sent via Send() and after all the currently buffered
+ data is sent to the OS TCP stack, the socket gets shut down. */
+ bool m_ShouldShutdown;
+
/** Creates a new link to be queued to connect to a specified host:port.
Used for outgoing connections created using cNetwork::Connect().
@@ -104,6 +109,9 @@ protected:
/** Callback that LibEvent calls when there's data available from the remote peer. */
static void ReadCallback(bufferevent * a_BufferEvent, void * a_Self);
+ /** Callback that LibEvent calls when the remote peer can receive more data. */
+ static void WriteCallback(bufferevent * a_BufferEvent, void * a_Self);
+
/** Callback that LibEvent calls when there's a non-data-related event on the socket. */
static void EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self);
@@ -115,6 +123,10 @@ protected:
/** Updates m_RemoteIP and m_RemotePort based on the metadata read from the socket. */
void UpdateRemoteAddress(void);
+
+ /** Calls shutdown on the link and disables LibEvent writing.
+ Called after all data from LibEvent buffers is sent to the OS TCP stack and shutdown() has been called before. */
+ void DoActualShutdown(void);
};
diff --git a/src/OSSupport/UDPEndpointImpl.cpp b/src/OSSupport/UDPEndpointImpl.cpp
new file mode 100644
index 000000000..ece521ab8
--- /dev/null
+++ b/src/OSSupport/UDPEndpointImpl.cpp
@@ -0,0 +1,608 @@
+
+// UDPEndpointImpl.cpp
+
+// Implements the cUDPEndpointImpl class representing an implementation of an endpoint in UDP communication
+
+#include "Globals.h"
+#include "UDPEndpointImpl.h"
+#include "NetworkSingleton.h"
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Globals:
+
+static bool IsValidSocket(evutil_socket_t a_Socket)
+{
+ #ifdef _WIN32
+ return (a_Socket != INVALID_SOCKET);
+ #else // _WIN32
+ return (a_Socket >= 0);
+ #endif // else _WIN32
+}
+
+
+
+
+
+/** Converts a_SrcAddr in IPv4 format to a_DstAddr in IPv6 format (using IPv4-mapped IPv6). */
+static void ConvertIPv4ToMappedIPv6(sockaddr_in & a_SrcAddr, sockaddr_in6 & a_DstAddr)
+{
+ memset(&a_DstAddr, 0, sizeof(a_DstAddr));
+ a_DstAddr.sin6_family = AF_INET6;
+ a_DstAddr.sin6_addr.s6_addr[10] = 0xff;
+ a_DstAddr.sin6_addr.s6_addr[11] = 0xff;
+ a_DstAddr.sin6_addr.s6_addr[12] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 0) & 0xff);
+ a_DstAddr.sin6_addr.s6_addr[13] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 8) & 0xff);
+ a_DstAddr.sin6_addr.s6_addr[14] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 16) & 0xff);
+ a_DstAddr.sin6_addr.s6_addr[15] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 24) & 0xff);
+ a_DstAddr.sin6_port = a_SrcAddr.sin_port;
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cUDPSendAfterLookup:
+
+/** A hostname-to-IP resolver callback that sends the data stored within to the resolved IP address.
+This is used for sending UDP datagrams to hostnames, so that the cUDPEndpoint::Send() doesn't block.
+Instead an instance of this callback is queued for resolving and the data is sent once the IP is resolved. */
+class cUDPSendAfterLookup:
+ public cNetwork::cResolveNameCallbacks
+{
+public:
+ cUDPSendAfterLookup(const AString & a_Data, UInt16 a_Port, evutil_socket_t a_MainSock, evutil_socket_t a_SecondSock, bool a_IsMainSockIPv6):
+ m_Data(a_Data),
+ m_Port(a_Port),
+ m_MainSock(a_MainSock),
+ m_SecondSock(a_SecondSock),
+ m_IsMainSockIPv6(a_IsMainSockIPv6),
+ m_HasIPv4(false),
+ m_HasIPv6(false)
+ {
+ }
+
+protected:
+ /** The data to send after the hostname is resolved. */
+ AString m_Data;
+
+ /** The port to which to send the data. */
+ UInt16 m_Port;
+
+ /** The primary socket to use for sending. */
+ evutil_socket_t m_MainSock;
+
+ /** The secondary socket to use for sending, if needed by the OS. */
+ evutil_socket_t m_SecondSock;
+
+ /** True if m_MainSock is an IPv6 socket. */
+ bool m_IsMainSockIPv6;
+
+ /** The IPv4 address resolved, if any. */
+ sockaddr_in m_AddrIPv4;
+
+ /** Set to true if the name resolved to an IPv4 address. */
+ bool m_HasIPv4;
+
+ /** The IPv6 address resolved, if any. */
+ sockaddr_in6 m_AddrIPv6;
+
+ /** Set to true if the name resolved to an IPv6 address. */
+ bool m_HasIPv6;
+
+
+ // cNetwork::cResolveNameCallbacks overrides:
+ virtual void OnNameResolved(const AString & a_Name, const AString & a_PI) override
+ {
+ // Not needed
+ }
+
+ virtual bool OnNameResolvedV4(const AString & a_Name, const sockaddr_in * a_IP) override
+ {
+ if (!m_HasIPv4)
+ {
+ m_AddrIPv4 = *a_IP;
+ m_AddrIPv4.sin_port = htons(m_Port);
+ m_HasIPv4 = true;
+ }
+
+ // Don't want OnNameResolved() callback
+ return false;
+ }
+
+ virtual bool OnNameResolvedV6(const AString & a_Name, const sockaddr_in6 * a_IP) override
+ {
+ if (!m_HasIPv6)
+ {
+ m_AddrIPv6 = *a_IP;
+ m_AddrIPv6.sin6_port = htons(m_Port);
+ m_HasIPv6 = true;
+ }
+
+ // Don't want OnNameResolved() callback
+ return false;
+ }
+
+ virtual void OnFinished(void) override
+ {
+ // Send the actual data, through the correct socket and using the correct resolved address:
+ if (m_IsMainSockIPv6)
+ {
+ if (m_HasIPv6)
+ {
+ sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv6), static_cast<socklen_t>(sizeof(m_AddrIPv6)));
+ }
+ else if (m_HasIPv4)
+ {
+ // If the secondary socket is valid, it is an IPv4 socket, so use that:
+ if (m_SecondSock != -1)
+ {
+ sendto(m_SecondSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv4), static_cast<socklen_t>(sizeof(m_AddrIPv4)));
+ }
+ else
+ {
+ // Need an address conversion from IPv4 to IPv6-mapped-IPv4:
+ ConvertIPv4ToMappedIPv6(m_AddrIPv4, m_AddrIPv6);
+ sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv6), static_cast<socklen_t>(sizeof(m_AddrIPv6)));
+ }
+ }
+ else
+ {
+ LOGD("UDP endpoint queued sendto: Name not resolved");
+ return;
+ }
+ }
+ else // m_IsMainSockIPv6
+ {
+ // Main socket is IPv4 only, only allow IPv4 dst address:
+ if (!m_HasIPv4)
+ {
+ LOGD("UDP endpoint queued sendto: Name not resolved to IPv4 for an IPv4-only socket");
+ return;
+ }
+ sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv4), static_cast<socklen_t>(sizeof(m_AddrIPv4)));
+ }
+ }
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ // Nothing needed
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cUDPEndpointImpl:
+
+cUDPEndpointImpl::cUDPEndpointImpl(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks):
+ super(a_Callbacks),
+ m_Port(0),
+ m_MainSock(-1),
+ m_IsMainSockIPv6(true),
+ m_SecondarySock(-1),
+ m_MainEvent(nullptr),
+ m_SecondaryEvent(nullptr)
+{
+ Open(a_Port);
+}
+
+
+
+
+
+void cUDPEndpointImpl::Close(void)
+{
+ if (m_Port == 0)
+ {
+ // Already closed
+ return;
+ }
+
+ // Close the LibEvent handles:
+ if (m_MainEvent != nullptr)
+ {
+ event_free(m_MainEvent);
+ m_MainEvent = nullptr;
+ }
+ if (m_SecondaryEvent != nullptr)
+ {
+ event_free(m_SecondaryEvent);
+ m_SecondaryEvent = nullptr;
+ }
+
+ // Close the OS sockets:
+ evutil_closesocket(m_MainSock);
+ m_MainSock = -1;
+ evutil_closesocket(m_SecondarySock);
+ m_SecondarySock = -1;
+
+ // Mark as closed:
+ m_Port = 0;
+}
+
+
+
+
+
+bool cUDPEndpointImpl::IsOpen(void) const
+{
+ return (m_Port != 0);
+}
+
+
+
+
+
+UInt16 cUDPEndpointImpl::GetPort(void) const
+{
+ return m_Port;
+}
+
+
+
+
+
+bool cUDPEndpointImpl::Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port)
+{
+ // If a_Host is an IP address, send the data directly:
+ sockaddr_storage sa;
+ int salen = static_cast<int>(sizeof(sa));
+ memset(&sa, 0, sizeof(sa));
+ if (evutil_parse_sockaddr_port(a_Host.c_str(), reinterpret_cast<sockaddr *>(&sa), &salen) != 0)
+ {
+ // a_Host is a hostname, we need to do a lookup first:
+ auto queue = std::make_shared<cUDPSendAfterLookup>(a_Payload, a_Port, m_MainSock, m_SecondarySock, m_IsMainSockIPv6);
+ return cNetwork::HostnameToIP(a_Host, queue);
+ }
+
+ // a_Host is an IP address and has been parsed into "sa"
+ // Insert the correct port and send data:
+ int NumSent;
+ switch (sa.ss_family)
+ {
+ case AF_INET:
+ {
+ reinterpret_cast<sockaddr_in *>(&sa)->sin_port = htons(a_Port);
+ if (m_IsMainSockIPv6)
+ {
+ if (IsValidSocket(m_SecondarySock))
+ {
+ // The secondary socket, which is always IPv4, is present:
+ NumSent = static_cast<int>(sendto(m_SecondarySock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen)));
+ }
+ else
+ {
+ // Need to convert IPv4 to IPv6 address before sending:
+ sockaddr_in6 IPv6;
+ ConvertIPv4ToMappedIPv6(*reinterpret_cast<sockaddr_in *>(&sa), IPv6);
+ NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&IPv6), static_cast<socklen_t>(sizeof(IPv6))));
+ }
+ }
+ else
+ {
+ NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen)));
+ }
+ break;
+ }
+
+ case AF_INET6:
+ {
+ reinterpret_cast<sockaddr_in6 *>(&sa)->sin6_port = htons(a_Port);
+ NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen)));
+ break;
+ }
+ default:
+ {
+ LOGD("UDP sendto: Invalid address family for address \"%s\".", a_Host.c_str());
+ return false;
+ }
+ }
+ return (NumSent > 0);
+}
+
+
+
+
+
+void cUDPEndpointImpl::EnableBroadcasts(void)
+{
+ ASSERT(IsOpen());
+
+ // Enable broadcasts on the main socket:
+ // Some OSes use ints, others use chars, so we try both
+ int broadcastInt = 1;
+ char broadcastChar = 1;
+ // (Note that Windows uses const char * for option values, while Linux uses const void *)
+ if (setsockopt(m_MainSock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&broadcastInt), sizeof(broadcastInt)) == -1)
+ {
+ if (setsockopt(m_MainSock, SOL_SOCKET, SO_BROADCAST, &broadcastChar, sizeof(broadcastChar)) == -1)
+ {
+ int err = EVUTIL_SOCKET_ERROR();
+ LOGWARNING("Cannot enable broadcasts on port %d: %d (%s)", m_Port, err, evutil_socket_error_to_string(err));
+ return;
+ }
+
+ // Enable broadcasts on the secondary socket, if opened (use char, it worked for primary):
+ if (IsValidSocket(m_SecondarySock))
+ {
+ if (setsockopt(m_SecondarySock, SOL_SOCKET, SO_BROADCAST, &broadcastChar, sizeof(broadcastChar)) == -1)
+ {
+ int err = EVUTIL_SOCKET_ERROR();
+ LOGWARNING("Cannot enable broadcasts on port %d (secondary): %d (%s)", m_Port, err, evutil_socket_error_to_string(err));
+ }
+ }
+ return;
+ }
+
+ // Enable broadcasts on the secondary socket, if opened (use int, it worked for primary):
+ if (IsValidSocket(m_SecondarySock))
+ {
+ if (setsockopt(m_SecondarySock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&broadcastInt), sizeof(broadcastInt)) == -1)
+ {
+ int err = EVUTIL_SOCKET_ERROR();
+ LOGWARNING("Cannot enable broadcasts on port %d (secondary): %d (%s)", m_Port, err, evutil_socket_error_to_string(err));
+ }
+ }
+}
+
+
+
+
+
+void cUDPEndpointImpl::Open(UInt16 a_Port)
+{
+ ASSERT(m_Port == 0); // Must not be already open
+
+ // Make sure the cNetwork internals are innitialized:
+ cNetworkSingleton::Get();
+
+ // Set up the main socket:
+ // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available.
+ bool NeedsTwoSockets = false;
+ m_IsMainSockIPv6 = true;
+ m_MainSock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+
+ int err;
+ if (!IsValidSocket(m_MainSock))
+ {
+ // Failed to create IPv6 socket, create an IPv4 one instead:
+ m_IsMainSockIPv6 = false;
+ err = EVUTIL_SOCKET_ERROR();
+ LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err));
+ m_MainSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (!IsValidSocket(m_MainSock))
+ {
+ err = EVUTIL_SOCKET_ERROR();
+ m_Callbacks.OnError(err, Printf("Cannot create UDP socket for port %d: %s", a_Port, evutil_socket_error_to_string(err)));
+ return;
+ }
+
+ // Allow the port to be reused right after the socket closes:
+ if (evutil_make_listen_socket_reuseable(m_MainSock) != 0)
+ {
+ err = EVUTIL_SOCKET_ERROR();
+ LOG("UDP Port %d cannot be made reusable: %d (%s). Restarting the server might not work.",
+ a_Port, err, evutil_socket_error_to_string(err)
+ );
+ }
+
+ // Bind to all interfaces:
+ sockaddr_in name;
+ memset(&name, 0, sizeof(name));
+ name.sin_family = AF_INET;
+ name.sin_port = ntohs(a_Port);
+ if (bind(m_MainSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0)
+ {
+ err = EVUTIL_SOCKET_ERROR();
+ m_Callbacks.OnError(err, Printf("Cannot bind UDP port %d: %s", a_Port, evutil_socket_error_to_string(err)));
+ evutil_closesocket(m_MainSock);
+ return;
+ }
+ }
+ else
+ {
+ // IPv6 socket created, switch it into "dualstack" mode:
+ UInt32 Zero = 0;
+ #ifdef _WIN32
+ // WinXP doesn't support this feature, so if the setting fails, create another socket later on:
+ int res = setsockopt(m_MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
+ err = EVUTIL_SOCKET_ERROR();
+ NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT));
+ #else
+ setsockopt(m_MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
+ #endif
+
+ // Allow the port to be reused right after the socket closes:
+ if (evutil_make_listen_socket_reuseable(m_MainSock) != 0)
+ {
+ err = EVUTIL_SOCKET_ERROR();
+ LOG("UDP Port %d cannot be made reusable: %d (%s). Restarting the server might not work.",
+ a_Port, err, evutil_socket_error_to_string(err)
+ );
+ }
+
+ // Bind to all interfaces:
+ sockaddr_in6 name;
+ memset(&name, 0, sizeof(name));
+ name.sin6_family = AF_INET6;
+ name.sin6_port = ntohs(a_Port);
+ if (bind(m_MainSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0)
+ {
+ err = EVUTIL_SOCKET_ERROR();
+ m_Callbacks.OnError(err, Printf("Cannot bind to UDP port %d: %s", a_Port, evutil_socket_error_to_string(err)));
+ evutil_closesocket(m_MainSock);
+ return;
+ }
+ }
+ if (evutil_make_socket_nonblocking(m_MainSock) != 0)
+ {
+ err = EVUTIL_SOCKET_ERROR();
+ m_Callbacks.OnError(err, Printf("Cannot make socket on UDP port %d nonblocking: %s", a_Port, evutil_socket_error_to_string(err)));
+ evutil_closesocket(m_MainSock);
+ return;
+ }
+ m_MainEvent = event_new(cNetworkSingleton::Get().GetEventBase(), m_MainSock, EV_READ | EV_PERSIST, RawCallback, this);
+ event_add(m_MainEvent, nullptr);
+
+ // Read the actual port number on which the socket is listening:
+ {
+ sockaddr_storage name;
+ socklen_t namelen = static_cast<socklen_t>(sizeof(name));
+ getsockname(m_MainSock, reinterpret_cast<sockaddr *>(&name), &namelen);
+ switch (name.ss_family)
+ {
+ case AF_INET:
+ {
+ sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(&name);
+ m_Port = ntohs(sin->sin_port);
+ break;
+ }
+ case AF_INET6:
+ {
+ sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(&name);
+ m_Port = ntohs(sin6->sin6_port);
+ break;
+ }
+ }
+ }
+
+ // If we don't need to create another socket, bail out now:
+ if (!NeedsTwoSockets)
+ {
+ return;
+ }
+
+ // If a secondary socket is required (WinXP dual-stack), create it here:
+ LOGD("Creating a second UDP socket for IPv4");
+ m_SecondarySock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ if (!IsValidSocket(m_SecondarySock))
+ {
+ // Don't report as an error, the primary socket is working
+ err = EVUTIL_SOCKET_ERROR();
+ LOGD("Socket creation failed for secondary UDP socket for port %d: %d, %s", m_Port, err, evutil_socket_error_to_string(err));
+ return;
+ }
+
+ // Allow the port to be reused right after the socket closes:
+ if (evutil_make_listen_socket_reuseable(m_SecondarySock) != 0)
+ {
+ // Don't report as an error, the primary socket is working
+ err = EVUTIL_SOCKET_ERROR();
+ LOGD("UDP Port %d cannot be made reusable (second socket): %d (%s). Restarting the server might not work.",
+ a_Port, err, evutil_socket_error_to_string(err)
+ );
+ evutil_closesocket(m_SecondarySock);
+ m_SecondarySock = -1;
+ return;
+ }
+
+ // Make the secondary socket nonblocking:
+ if (evutil_make_socket_nonblocking(m_SecondarySock) != 0)
+ {
+ // Don't report as an error, the primary socket is working
+ err = EVUTIL_SOCKET_ERROR();
+ LOGD("evutil_make_socket_nonblocking() failed for secondary UDP socket: %d, %s", err, evutil_socket_error_to_string(err));
+ evutil_closesocket(m_SecondarySock);
+ m_SecondarySock = -1;
+ return;
+ }
+
+ // Bind to all IPv4 interfaces:
+ sockaddr_in name;
+ memset(&name, 0, sizeof(name));
+ name.sin_family = AF_INET;
+ name.sin_port = ntohs(m_Port);
+ if (bind(m_SecondarySock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0)
+ {
+ // Don't report as an error, the primary socket is working
+ err = EVUTIL_SOCKET_ERROR();
+ LOGD("Cannot bind secondary socket to UDP port %d: %d (%s)", m_Port, err, evutil_socket_error_to_string(err));
+ evutil_closesocket(m_SecondarySock);
+ m_SecondarySock = -1;
+ return;
+ }
+
+ m_SecondaryEvent = event_new(cNetworkSingleton::Get().GetEventBase(), m_SecondarySock, EV_READ | EV_PERSIST, RawCallback, this);
+ event_add(m_SecondaryEvent, nullptr);
+}
+
+
+
+
+
+void cUDPEndpointImpl::RawCallback(evutil_socket_t a_Socket, short a_What, void * a_Self)
+{
+ cUDPEndpointImpl * Self = reinterpret_cast<cUDPEndpointImpl *>(a_Self);
+ Self->Callback(a_Socket, a_What);
+}
+
+
+
+
+
+void cUDPEndpointImpl::Callback(evutil_socket_t a_Socket, short a_What)
+{
+ if ((a_What & EV_READ) != 0)
+ {
+ // Receive datagram from the socket:
+ char buf[64 KiB];
+ socklen_t buflen = static_cast<socklen_t>(sizeof(buf));
+ sockaddr_storage sa;
+ socklen_t salen = static_cast<socklen_t>(sizeof(sa));
+ auto len = recvfrom(a_Socket, buf, buflen, 0, reinterpret_cast<sockaddr *>(&sa), &salen);
+ if (len >= 0)
+ {
+ // Convert the remote IP address to a string:
+ char RemoteHost[128];
+ UInt16 RemotePort;
+ switch (sa.ss_family)
+ {
+ case AF_INET:
+ {
+ auto sin = reinterpret_cast<sockaddr_in *>(&sa);
+ evutil_inet_ntop(sa.ss_family, &sin->sin_addr, RemoteHost, sizeof(RemoteHost));
+ RemotePort = ntohs(sin->sin_port);
+ break;
+ }
+ case AF_INET6:
+ {
+ auto sin = reinterpret_cast<sockaddr_in6 *>(&sa);
+ evutil_inet_ntop(sa.ss_family, &sin->sin6_addr, RemoteHost, sizeof(RemoteHost));
+ RemotePort = ntohs(sin->sin6_port);
+ break;
+ }
+ default:
+ {
+ return;
+ }
+ }
+
+ // Call the callback:
+ m_Callbacks.OnReceivedData(buf, static_cast<size_t>(len), RemoteHost, RemotePort);
+ }
+ }
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cNetwork API:
+
+cUDPEndpointPtr cNetwork::CreateUDPEndpoint(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks)
+{
+ return std::make_shared<cUDPEndpointImpl>(a_Port, a_Callbacks);
+}
+
+
+
+
diff --git a/src/OSSupport/UDPEndpointImpl.h b/src/OSSupport/UDPEndpointImpl.h
new file mode 100644
index 000000000..75942b0cf
--- /dev/null
+++ b/src/OSSupport/UDPEndpointImpl.h
@@ -0,0 +1,81 @@
+
+// UDPEndpointImpl.h
+
+// Declares the cUDPEndpointImpl class representing an implementation of an endpoint in UDP communication
+
+
+
+
+
+#pragma once
+
+#include "Network.h"
+#include <event2/event.h>
+
+
+
+
+
+// fwd:
+class cUDPEndpointImpl;
+typedef SharedPtr<cUDPEndpointImpl> cUDPEndpointImplPtr;
+
+
+
+
+
+class cUDPEndpointImpl:
+ public cUDPEndpoint
+{
+ typedef cUDPEndpoint super;
+
+public:
+ /** Creates a new instance of the endpoint, with the specified callbacks.
+ Tries to open on the specified port; if it fails, the endpoint is left in the "closed" state.
+ If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. */
+ cUDPEndpointImpl(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks);
+
+ // cUDPEndpoint overrides:
+ virtual void Close(void) override;
+ virtual bool IsOpen(void) const override;
+ virtual UInt16 GetPort(void) const override;
+ virtual bool Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) override;
+ virtual void EnableBroadcasts(void) override;
+
+protected:
+ /** The local port on which the endpoint is open.
+ If this is zero, it means the endpoint is closed - either opening has failed, or it has been closed explicitly. */
+ UInt16 m_Port;
+
+ /** The primary underlying OS socket. */
+ evutil_socket_t m_MainSock;
+
+ /** True if m_MainSock is in the IPv6 namespace (needs IPv6 addresses for sending). */
+ bool m_IsMainSockIPv6;
+
+ /** The secondary OS socket (if primary doesn't support dualstack). */
+ evutil_socket_t m_SecondarySock;
+
+ /** The LibEvent handle for the primary socket. */
+ event * m_MainEvent;
+
+ /** The LibEvent handle for the secondary socket. */
+ event * m_SecondaryEvent;
+
+
+ /** Creates and opens the socket on the specified port.
+ If a_Port is 0, the OS is free to assign any port number it likes to the endpoint.
+ If the opening fails, the OnError() callback is called and the endpoint is left "closed" (IsOpen() returns false). */
+ void Open(UInt16 a_Port);
+
+ /** The callback that LibEvent calls when an event occurs on one of the sockets.
+ Calls Callback() on a_Self. */
+ static void RawCallback(evutil_socket_t a_Socket, short a_What, void * a_Self);
+
+ /** The callback that is called when an event occurs on one of the sockets. */
+ void Callback(evutil_socket_t a_Socket, short a_What);
+};
+
+
+
+
diff --git a/src/PolarSSL++/CryptoKey.cpp b/src/PolarSSL++/CryptoKey.cpp
index 7c4f021b3..9354ddf50 100644
--- a/src/PolarSSL++/CryptoKey.cpp
+++ b/src/PolarSSL++/CryptoKey.cpp
@@ -45,7 +45,7 @@ cCryptoKey::cCryptoKey(const AString & a_PrivateKeyData, const AString & a_Passw
if (res != 0)
{
LOGWARNING("Failed to parse private key: -0x%x", res);
- ASSERT(!"Cannot parse PubKey");
+ ASSERT(!"Cannot parse PrivKey");
return;
}
}
diff --git a/src/PolarSSL++/SslContext.cpp b/src/PolarSSL++/SslContext.cpp
index 66dfefc65..5ac4bc227 100644
--- a/src/PolarSSL++/SslContext.cpp
+++ b/src/PolarSSL++/SslContext.cpp
@@ -7,6 +7,7 @@
#include "SslContext.h"
#include "EntropyContext.h"
#include "CtrDrbgContext.h"
+#include "polarssl/debug.h"
@@ -69,6 +70,8 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> &
// These functions allow us to debug SSL and certificate problems, but produce way too much output,
// so they're disabled until someone needs them
ssl_set_dbg(&m_Ssl, &SSLDebugMessage, this);
+ debug_set_threshold(2);
+
ssl_set_verify(&m_Ssl, &SSLVerifyCert, this);
//*/
diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp
index 169367949..f78c2e54b 100644
--- a/src/Protocol/Protocol17x.cpp
+++ b/src/Protocol/Protocol17x.cpp
@@ -679,8 +679,8 @@ void cProtocol172::SendMapDecorators(int a_ID, const cMapDecoratorList & a_Decor
for (cMapDecoratorList::const_iterator it = a_Decorators.begin(); it != a_Decorators.end(); ++it)
{
- ASSERT((it->GetPixelX() >= 0) && (it->GetPixelX() < 256));
- ASSERT((it->GetPixelZ() >= 0) && (it->GetPixelZ() < 256));
+ ASSERT(it->GetPixelX() < 256);
+ ASSERT(it->GetPixelZ() < 256);
Pkt.WriteByte(static_cast<Byte>((it->GetType() << 4) | static_cast<Byte>(it->GetRot() & 0xf)));
Pkt.WriteByte(static_cast<Byte>(it->GetPixelX()));
Pkt.WriteByte(static_cast<Byte>(it->GetPixelZ()));
@@ -694,7 +694,7 @@ void cProtocol172::SendMapDecorators(int a_ID, const cMapDecoratorList & a_Decor
void cProtocol172::SendMapInfo(int a_ID, unsigned int a_Scale)
{
ASSERT(m_State == 3); // In game mode?
- ASSERT((a_Scale >= 0) && (a_Scale < 256));
+ ASSERT(a_Scale < 256);
cPacketizer Pkt(*this, 0x34);
Pkt.WriteVarInt(static_cast<UInt32>(a_ID));
@@ -1757,7 +1757,10 @@ void cProtocol172::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer)
void cProtocol172::HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer)
{
short EncKeyLength, EncNonceLength;
- a_ByteBuffer.ReadBEShort(EncKeyLength);
+ if (!a_ByteBuffer.ReadBEShort(EncKeyLength))
+ {
+ return;
+ }
if ((EncKeyLength < 0) || (EncKeyLength > MAX_ENC_LEN))
{
LOGD("Invalid Encryption Key length: %d. Kicking client.", EncKeyLength);
diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp
index 7d954a297..22280f800 100644
--- a/src/Protocol/Protocol18x.cpp
+++ b/src/Protocol/Protocol18x.cpp
@@ -386,7 +386,7 @@ void cProtocol180::SendEntityLook(const cEntity & a_Entity)
Pkt.WriteVarInt(a_Entity.GetUniqueID());
Pkt.WriteByteAngle(a_Entity.GetYaw());
Pkt.WriteByteAngle(a_Entity.GetPitch());
- Pkt.WriteBool(true); // TODO: IsOnGround() on entities
+ Pkt.WriteBool(a_Entity.IsOnGround());
}
@@ -429,7 +429,7 @@ void cProtocol180::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char
Pkt.WriteByte(a_RelX);
Pkt.WriteByte(a_RelY);
Pkt.WriteByte(a_RelZ);
- Pkt.WriteBool(true); // TODO: IsOnGround() on entities
+ Pkt.WriteBool(a_Entity.IsOnGround());
}
@@ -447,7 +447,7 @@ void cProtocol180::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX,
Pkt.WriteByte(a_RelZ);
Pkt.WriteByteAngle(a_Entity.GetYaw());
Pkt.WriteByteAngle(a_Entity.GetPitch());
- Pkt.WriteBool(true); // TODO: IsOnGround() on entities
+ Pkt.WriteBool(a_Entity.IsOnGround());
}
@@ -874,11 +874,15 @@ void cProtocol180::SendPlayerListUpdatePing(const cPlayer & a_Player)
{
ASSERT(m_State == 3); // In game mode?
- cPacketizer Pkt(*this, 0x38); // Playerlist Item packet
- Pkt.WriteVarInt(2);
- Pkt.WriteVarInt(1);
- Pkt.WriteUUID(a_Player.GetUUID());
- Pkt.WriteVarInt((UInt32)a_Player.GetClientHandle()->GetPing());
+ auto ClientHandle = a_Player.GetClientHandlePtr();
+ if (ClientHandle != nullptr)
+ {
+ cPacketizer Pkt(*this, 0x38); // Playerlist Item packet
+ Pkt.WriteVarInt(2);
+ Pkt.WriteVarInt(1);
+ Pkt.WriteUUID(a_Player.GetUUID());
+ Pkt.WriteVarInt(static_cast<UInt32>(ClientHandle->GetPing()));
+ }
}
@@ -947,7 +951,7 @@ void cProtocol180::SendPlayerMoveLook(void)
Pkt.WriteDouble(Player->GetPosX());
// The "+ 0.001" is there because otherwise the player falls through the block they were standing on.
- Pkt.WriteDouble(Player->GetStance() + 0.001);
+ Pkt.WriteDouble(Player->GetPosY() + 0.001);
Pkt.WriteDouble(Player->GetPosZ());
Pkt.WriteFloat((float)Player->GetYaw());
@@ -976,7 +980,7 @@ void cProtocol180::SendPlayerSpawn(const cPlayer & a_Player)
Pkt.WriteVarInt(a_Player.GetUniqueID());
Pkt.WriteUUID(cMojangAPI::MakeUUIDShort(a_Player.GetUUID()));
Pkt.WriteFPInt(a_Player.GetPosX());
- Pkt.WriteFPInt(a_Player.GetPosY());
+ Pkt.WriteFPInt(a_Player.GetPosY() + 0.001); // The "+ 0.001" is there because otherwise the player falls through the block they were standing on.
Pkt.WriteFPInt(a_Player.GetPosZ());
Pkt.WriteByteAngle(a_Player.GetYaw());
Pkt.WriteByteAngle(a_Player.GetPitch());
@@ -1305,7 +1309,7 @@ void cProtocol180::SendTeleportEntity(const cEntity & a_Entity)
Pkt.WriteFPInt(a_Entity.GetPosZ());
Pkt.WriteByteAngle(a_Entity.GetYaw());
Pkt.WriteByteAngle(a_Entity.GetPitch());
- Pkt.WriteBool(true); // TODO: IsOnGrond() on entities
+ Pkt.WriteBool(a_Entity.IsOnGround());
}
diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp
index a76e9461a..cc8b8d3f5 100755
--- a/src/WorldStorage/WSSAnvil.cpp
+++ b/src/WorldStorage/WSSAnvil.cpp
@@ -1007,21 +1007,28 @@ cBlockEntity * cWSSAnvil::LoadFlowerPotFromNBT(const cParsedNBT & a_NBT, int a_T
}
std::unique_ptr<cFlowerPotEntity> FlowerPot(new cFlowerPotEntity(a_BlockX, a_BlockY, a_BlockZ, m_World));
- short ItemType = 0, ItemData = 0;
+ cItem Item;
int currentLine = a_NBT.FindChildByName(a_TagIdx, "Item");
if (currentLine >= 0)
{
- ItemType = (short) a_NBT.GetInt(currentLine);
+ if (a_NBT.GetType(currentLine) == TAG_String)
+ {
+ StringToItem(a_NBT.GetString(currentLine), Item);
+ }
+ else if (a_NBT.GetType(currentLine) == TAG_Int)
+ {
+ Item.m_ItemType = (short) a_NBT.GetInt(currentLine);
+ }
}
currentLine = a_NBT.FindChildByName(a_TagIdx, "Data");
- if (currentLine >= 0)
+ if ((currentLine >= 0) && (a_NBT.GetType(currentLine) == TAG_Int))
{
- ItemData = (short) a_NBT.GetInt(currentLine);
+ Item.m_ItemDamage = (short) a_NBT.GetInt(currentLine);
}
- FlowerPot->SetItem(cItem(ItemType, 1, ItemData));
+ FlowerPot->SetItem(Item);
return FlowerPot.release();
}
@@ -3136,8 +3143,11 @@ bool cWSSAnvil::cMCAFile::SetChunkData(const cChunkCoords & a_Chunk, const AStri
// Add padding to 4K boundary:
size_t BytesWritten = a_Data.size() + MCA_CHUNK_HEADER_LENGTH;
- static const char Padding[4095] = {0};
- m_File.Write(Padding, 4096 - (BytesWritten % 4096));
+ if (BytesWritten % 4096 != 0)
+ {
+ static const char Padding[4095] = {0};
+ m_File.Write(Padding, 4096 - (BytesWritten % 4096));
+ }
// Store the header:
ChunkSize = ((u_long)a_Data.size() + MCA_CHUNK_HEADER_LENGTH + 4095) / 4096; // Round data size *up* to nearest 4KB sector, make it a sector number