diff --git a/nel/include/nel/net/inet_host.h b/nel/include/nel/net/inet_host.h
index b8e5a3b60b..266d4de81e 100644
--- a/nel/include/nel/net/inet_host.h
+++ b/nel/include/nel/net/inet_host.h
@@ -81,6 +81,10 @@ class CInetHost
/// Returns the first address
inline const CInetAddress &address() const { return m_Addresses[0]; }
+ /// Hostname
+ const std::string &hostname() const { return m_Hostname; }
+
+ /// Port
inline uint16 port() const { return m_Addresses[0].port(); }
void setPort(uint16 port);
diff --git a/ryzom/client/src/CMakeLists.txt b/ryzom/client/src/CMakeLists.txt
index 061b304a22..cb8ad7bb82 100644
--- a/ryzom/client/src/CMakeLists.txt
+++ b/ryzom/client/src/CMakeLists.txt
@@ -66,6 +66,8 @@ IF(WITH_RYZOM_CLIENT)
session_*.cpp session_*.h
steam_client.cpp steam_client.h
string_manager_client.cpp string_manager_client.h
+ *_connection.cpp *_connection.h
+ *download*.cpp *download*.h
)
SOURCE_GROUP("network" FILES ${RZCLIENT_NETWORK})
FILE(GLOB RZCLIENT_GLOBALS
@@ -73,6 +75,37 @@ IF(WITH_RYZOM_CLIENT)
global.cpp global.h
)
SOURCE_GROUP("globals" FILES ${RZCLIENT_GLOBALS})
+ FILE(GLOB RZCLIENT_ANIMATION
+ animation.cpp animation.h
+ animation_*.cpp animation_*.h
+ )
+ SOURCE_GROUP("animation" FILES ${RZCLIENT_ANIMATION})
+ FILE(GLOB RZCLIENT_FX
+ fx*.cpp fx*.h
+ *_fx*.cpp *_fx*.h
+ projectile_*.cpp projectile_*.h
+ )
+ SOURCE_GROUP("fx" FILES ${RZCLIENT_FX})
+ FILE(GLOB RZCLIENT_WEATHER
+ sky.cpp sky.h
+ sky_*.cpp sky_*.h
+ weather.cpp weather.h
+ weather_*.cpp weather_*.h
+ precipitation.cpp precipitation.h
+ precipitation_*.cpp precipitation_*.h
+ light_cycle_*.cpp light_cycle_*.h
+ )
+ SOURCE_GROUP("weather" FILES ${RZCLIENT_WEATHER})
+ FILE(GLOB RZCLIENT_OUTPOSTS
+ outpost.cpp outpost.h
+ outpost_*.cpp outpost_*.h
+ )
+ SOURCE_GROUP("outposts" FILES ${RZCLIENT_OUTPOSTS})
+ FILE(GLOB RZCLIENT_DECALS
+ decal.cpp decal.h
+ decal*.cpp decal_*.h
+ )
+ SOURCE_GROUP("decals" FILES ${RZCLIENT_DECALS})
# on Mac, create a .App Bundle
if(APPLE)
diff --git a/ryzom/client/src/quic_connection.cpp b/ryzom/client/src/quic_connection.cpp
new file mode 100644
index 0000000000..8001cf2884
--- /dev/null
+++ b/ryzom/client/src/quic_connection.cpp
@@ -0,0 +1,437 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2023 Jan BOON (Kaetemi)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "quic_connection.h"
+
+#include "nel/misc/mutex.h"
+#include "nel/misc/buf_fifo.h"
+#include "nel/misc/string_view.h"
+#include "nel/misc/string_common.h"
+
+#include "config.h"
+
+#ifdef NL_MSQUIC_AVAILABLE
+#include
+
+#ifdef NL_OS_WINDOWS
+#pragma warning(push)
+#pragma warning(disable : 6553) // Annotation does not apply to value type.
+#include
+#pragma warning(pop)
+#endif
+
+#define MsQuic m->Api
+
+using namespace NLMISC;
+using namespace NLNET;
+
+class CQuicConnectionImpl
+{
+public:
+ CQuicConnectionImpl()
+ : Api(NULL)
+ , Registration(NULL)
+ , Configuration(NULL)
+ , State(CQuicConnection::Disconnected)
+ , ShutdownFlag(false)
+ , MaxSendLength(NULL)
+ {
+ }
+
+ const QUIC_API_TABLE *Api;
+ HQUIC Registration;
+ HQUIC Configuration;
+ HQUIC Connection;
+
+ CMutex BufferMutex;
+ NLMISC::CBufFIFO Buffer;
+
+ CMutex StateMutex;
+ CQuicConnection::TState State;
+
+ CMutex ShutdownMutex; // Don't have fancy atomics and synchronization primitives here, so mutex everything!
+ bool ShutdownFlag;
+
+ uint32 MaxSendLength;
+
+ static QUIC_STATUS
+#ifdef _Function_class_
+ _Function_class_(QUIC_CONNECTION_CALLBACK)
+#endif
+ connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *ev);
+};
+
+CQuicConnection::CQuicConnection()
+#ifdef NL_CPP14
+ : m(std::make_unique())
+#else
+ : m(new CQuicConnectionImpl())
+#endif
+{
+ // Open library
+ QUIC_STATUS status = MsQuicOpenVersion(QUIC_API_VERSION_2, (const void **)&MsQuic);
+ if (QUIC_FAILED(status))
+ {
+ nlwarning("MsQuicOpenVersion failed with status 0x%x", status);
+ return;
+ }
+
+ // Registration, this creates the worker threads
+ QUIC_REGISTRATION_CONFIG regConfig = { 0 };
+ regConfig.AppName = "Ryzom Core (Client)";
+ regConfig.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY;
+ status = MsQuic->RegistrationOpen(®Config, &m->Registration);
+ if (QUIC_FAILED(status))
+ {
+ nlwarning("MsQuic->RegistrationOpen failed with status 0x%x", status);
+ release();
+ return;
+ }
+}
+
+CQuicConnection::~CQuicConnection()
+{
+ disconnect();
+ release();
+}
+
+void CQuicConnection::connect(const NLNET::CInetHost &addr)
+{
+ disconnect();
+
+ if (!MsQuic)
+ {
+ nlwarning("QUIC API not available");
+ }
+
+ {
+ CAutoMutex lock(m->StateMutex);
+ m->State = Connecting;
+ }
+
+ {
+ CAutoMutex lock(m->ShutdownMutex);
+ m->ShutdownFlag = false;
+ }
+
+ static const char *protocolName = "ryzomcore4";
+ static const QUIC_BUFFER alpn = { sizeof(protocolName) - 1, (uint8_t *)protocolName };
+
+ // Configuration, initialized in start, but destroyed on release only (may attempt more than once)
+ QUIC_STATUS status = QUIC_STATUS_SUCCESS;
+ if (!m->Configuration)
+ {
+ QUIC_SETTINGS settings = { 0 };
+ settings.DatagramReceiveEnabled = TRUE;
+ settings.IsSet.DatagramReceiveEnabled = TRUE;
+ settings.MigrationEnabled = TRUE;
+ settings.IsSet.MigrationEnabled = TRUE;
+ settings.PeerBidiStreamCount = 0;
+ settings.IsSet.PeerBidiStreamCount = TRUE;
+ settings.PeerUnidiStreamCount = 0; // TODO: Configured from msg.xml
+ settings.IsSet.PeerUnidiStreamCount = TRUE;
+ // settings.SendBufferingEnabled = TRUE;
+ // settings.IsSet.SendBufferingEnabled = TRUE;
+ // settings.GreaseQuicBitEnabled = TRUE;
+ // settings.IsSet.GreaseQuicBitEnabled = TRUE;
+ // settings.MinimumMtu = m_MsgSize + size of QUIC header; // Probably violates QUIC protocol if we do this, also no need
+ // settings.IsSet.MinimumMtu = TRUE;
+ status = MsQuic->ConfigurationOpen(m->Registration, &alpn, 1, &settings, sizeof(settings), NULL, &m->Configuration);
+ if (QUIC_FAILED(status))
+ {
+ disconnect();
+ nlwarning("MsQuic->ConfigurationOpen failed with status 0x%x", status);
+ return;
+ }
+
+ // Load credentials for client, client doesn't need a certificate
+ QUIC_CREDENTIAL_CONFIG credConfig;
+ memset(&credConfig, 0, sizeof(credConfig));
+ credConfig.Flags = QUIC_CREDENTIAL_FLAG_CLIENT;
+ credConfig.Type = QUIC_CREDENTIAL_TYPE_NONE;
+ status = MsQuic->ConfigurationLoadCredential(m->Configuration, &credConfig);
+ if (QUIC_FAILED(status))
+ {
+ disconnect();
+ MsQuic->ConfigurationClose(m->Configuration);
+ m->Configuration = nullptr;
+ nlwarning("MsQuic->ConfigurationLoadCredential failed with status 0x%x", status);
+ return;
+ }
+ }
+
+ // Connect to the specified address
+ status = MsQuic->ConnectionOpen(m->Registration, CQuicConnectionImpl::connectionCallback, (void *)this, &m->Connection);
+ if (QUIC_FAILED(status))
+ {
+ disconnect();
+ nlwarning("MsQuic->ConnectionOpen failed with status 0x%x", status);
+ return;
+ }
+
+ // Start the connection
+ status = MsQuic->ConnectionStart(m->Connection, m->Configuration, QUIC_ADDRESS_FAMILY_UNSPEC, nlUtf8ToMbcs(addr.hostname()), addr.port());
+ if (QUIC_FAILED(status))
+ {
+ disconnect();
+ nlwarning("MsQuic->ConnectionStart to %s failed with status 0x%x", addr.toStringLong().c_str(), status);
+ return;
+ }
+}
+
+void CQuicConnection::disconnect()
+{
+ // Stop connection
+ if (m->Connection)
+ {
+ MsQuic->ConnectionShutdown(m->Connection, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0);
+ try
+ {
+ for (;;) // Spin wait because we don't have fancy mechanisms when supporting legacy code base
+ {
+ CAutoMutex lock(m->ShutdownMutex);
+ if (m->ShutdownFlag)
+ break;
+ nlSleep(1);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ nlwarning("Exception while waiting for connection shutdown: %s", e.what());
+ }
+
+ MsQuic->ConnectionClose(m->Connection);
+ m->Connection = NULL;
+ }
+
+ {
+ CAutoMutex lock(m->StateMutex);
+ m->State = Disconnected;
+ }
+}
+
+void CQuicConnection::release()
+{
+ // Close configuration
+ if (m->Configuration)
+ {
+ MsQuic->ConfigurationClose(m->Configuration);
+ m->Configuration = NULL;
+ }
+
+ // Close registration
+ if (m->Registration)
+ {
+ MsQuic->RegistrationClose(m->Registration);
+ m->Registration = NULL;
+ }
+
+ // Close library
+ if (MsQuic)
+ {
+ MsQuicClose(MsQuic);
+ MsQuic = NULL;
+ }
+}
+
+CQuicConnection::TState CQuicConnection::state() const
+{
+ CAutoMutex lock(m->StateMutex);
+ return m->State;
+}
+
+QUIC_STATUS
+#ifdef _Function_class_
+_Function_class_(QUIC_CONNECTION_CALLBACK)
+#endif
+ CQuicConnectionImpl::connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *ev)
+{
+ CQuicConnection *self = (CQuicConnection *)context;
+ CQuicConnectionImpl *m = self->m.get();
+ QUIC_STATUS status = QUIC_STATUS_NOT_SUPPORTED;
+ switch (ev->Type)
+ {
+ case QUIC_CONNECTION_EVENT_CONNECTED: {
+ nlinfo("Connected");
+ nlassert(CStringView((const char *)ev->CONNECTED.NegotiatedAlpn, ev->CONNECTED.NegotiatedAlpnLength) == "ryzomcore4");
+ // MsQuic->ConnectionSendResumptionTicket(connection, QUIC_SEND_RESUMPTION_FLAG_NONE, 0, NULL); // What does this even do?
+ {
+ CAutoMutex lock(m->StateMutex);
+ m->State = CQuicConnection::Connected;
+ }
+ status = QUIC_STATUS_SUCCESS;
+ break;
+ }
+ case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT: {
+ nlinfo("Shutdown initiated by transport");
+ {
+ CAutoMutex lock(m->StateMutex);
+ m->State = CQuicConnection::Disconnected;
+ }
+ status = QUIC_STATUS_SUCCESS;
+ break;
+ }
+ case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: {
+ nlinfo("Shutdown initiated by peer");
+ {
+ CAutoMutex lock(m->StateMutex);
+ m->State = CQuicConnection::Disconnected;
+ }
+ status = QUIC_STATUS_SUCCESS;
+ break;
+ }
+ case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: {
+ nlinfo("Shutdown complete");
+ {
+ CAutoMutex lock(m->StateMutex);
+ m->State = CQuicConnection::Disconnected;
+ }
+ {
+ CAutoMutex lock(m->ShutdownMutex);
+ m->ShutdownFlag = true;
+ }
+ status = QUIC_STATUS_SUCCESS;
+ break;
+ }
+ case QUIC_CONNECTION_EVENT_DATAGRAM_RECEIVED:
+ nlinfo("Datagram received");
+ // YES PLEASE
+ self->datagramReceived(ev->DATAGRAM_RECEIVED.Buffer->Buffer, ev->DATAGRAM_RECEIVED.Buffer->Length);
+ status = QUIC_STATUS_SUCCESS;
+ break;
+ case QUIC_CONNECTION_EVENT_DATAGRAM_STATE_CHANGED:
+ nlinfo("Datagram state changed");
+ m->MaxSendLength = ev->DATAGRAM_STATE_CHANGED.SendEnabled ? ev->DATAGRAM_STATE_CHANGED.MaxSendLength : 0;
+ status = QUIC_STATUS_SUCCESS;
+ break;
+ case QUIC_CONNECTION_EVENT_LOCAL_ADDRESS_CHANGED:
+ case QUIC_CONNECTION_EVENT_PEER_ADDRESS_CHANGED:
+ case QUIC_CONNECTION_EVENT_IDEAL_PROCESSOR_CHANGED:
+ case QUIC_CONNECTION_EVENT_DATAGRAM_SEND_STATE_CHANGED:
+ case QUIC_CONNECTION_EVENT_RESUMED:
+ case QUIC_CONNECTION_EVENT_PEER_CERTIFICATE_RECEIVED:
+ case QUIC_CONNECTION_EVENT_STREAMS_AVAILABLE: // TODO: Match with msg.xml
+ // Don't care
+ status = QUIC_STATUS_SUCCESS;
+ break;
+ case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED:
+ case QUIC_CONNECTION_EVENT_PEER_NEEDS_STREAMS:
+ case QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED:
+ // Not supported
+ break;
+ }
+ return status;
+}
+
+void CQuicConnection::sendDatagram(const uint8 *buffer, uint32 size)
+{
+ if (m->Connection)
+ {
+ QUIC_BUFFER buf;
+ buf.Buffer = (uint8 *)buffer;
+ buf.Length = size;
+ MsQuic->DatagramSend(m->Connection, &buf, 1, QUIC_SEND_FLAG_NONE, this);
+ }
+}
+
+bool CQuicConnection::datagramAvailable()
+{
+ CAutoMutex lock(m->BufferMutex);
+ return !m->Buffer.empty();
+}
+
+bool CQuicConnection::receiveDatagram(NLMISC::CBitMemStream &msgin)
+{
+ CAutoMutex lock(m->BufferMutex);
+ if (!m->Buffer.empty())
+ return false;
+ uint8 *buffer;
+ uint32 size;
+ m->Buffer.front(buffer, size);
+ msgin.clear();
+ memcpy(msgin.bufferToFill(size), buffer, size);
+ return true;
+}
+
+void CQuicConnection::datagramReceived(const uint8 *buffer, uint32 length)
+{
+ CAutoMutex lock(m->BufferMutex);
+ m->Buffer.push(buffer, length);
+}
+
+#else
+
+using namespace NLMISC;
+using namespace NLNET;
+
+class CQuicConnectionImpl
+{
+public:
+ int Dummy;
+};
+
+CQuicConnection::CQuicConnection()
+#ifdef NL_CPP14
+ : m(std::make_unique())
+#else
+ : m(new CQuicConnectionImpl())
+#endif
+{
+}
+
+CQuicConnection::~CQuicConnection()
+{
+}
+
+void CQuicConnection::connect(const NLNET::CInetHost &addr)
+{
+}
+
+void CQuicConnection::disconnect()
+{
+}
+
+void CQuicConnection::release()
+{
+}
+
+CQuicConnection::TState CQuicConnection::state() const
+{
+ return Disconnected;
+}
+
+void CQuicConnection::sendDatagram(const uint8 *buffer, uint32 size)
+{
+}
+
+bool CQuicConnection::datagramAvailable()
+{
+ return false;
+}
+
+bool CQuicConnection::receiveDatagram(NLMISC::CBitMemStream &msgin)
+{
+ return false;
+}
+
+void CQuicConnection::datagramReceived(const uint8 *buffer, uint32 length)
+{
+}
+
+#endif
+
+/* end of file */
diff --git a/ryzom/client/src/quic_connection.h b/ryzom/client/src/quic_connection.h
new file mode 100644
index 0000000000..3b217f083d
--- /dev/null
+++ b/ryzom/client/src/quic_connection.h
@@ -0,0 +1,75 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2023 Jan BOON (Kaetemi)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef CL_QUIC_CONNECTION_H
+#define CL_QUIC_CONNECTION_H
+
+#include "nel/misc/types_nl.h"
+
+#include
+
+#include "nel/misc/bit_mem_stream.h"
+#include "nel/net/inet_host.h"
+
+class CQuicConnectionImpl;
+
+class CQuicConnection
+{
+public:
+ enum TState
+ {
+ Disconnected,
+ Connecting,
+ Connected
+ };
+
+ CQuicConnection();
+ ~CQuicConnection();
+
+ /// Connect
+ void connect(const NLNET::CInetHost &addr); // const CInetAddress &addr);
+
+ /// Shutdown and close gracefully, this object can be reused immediately for a new connection
+ void disconnect();
+
+ /// Release. Instance is useless after this call
+ void release();
+
+ /// Check if still connecting or connected
+ TState state() const;
+
+ /// Send a datagram, fancier than a telegram, but not as reliable
+ void sendDatagram(const uint8 *buffer, uint32 size);
+
+ /// Check if any datagram has been received
+ bool datagramAvailable();
+
+ /// Receive a datagram
+ bool receiveDatagram(NLMISC::CBitMemStream &msgin);
+
+private:
+ friend CQuicConnectionImpl;
+
+ /// Internal implementation specific
+ CUniquePtr m;
+
+ /// Received datagram
+ void datagramReceived(const uint8 *buffer, uint32 length);
+};
+
+#endif /* NL_QUIC_TRANSCEIVER_H */
+
+/* end of file */