From 2e37ce01c9dcdc5932cf5fe47a1ade0e0d2b7cf8 Mon Sep 17 00:00:00 2001
From: B3n30 <bene_thomas@web.de>
Date: Fri, 7 Jul 2017 21:34:15 +0200
Subject: [PATCH] Implement basic virtual Room support based on enet (#2803)

* Added support for network with ENet lib,

connecting is possible, but data can't be sent, yet.

* fixup! Added support for network with ENet lib,

* fixup! CLang

* fixup! Added support for network with ENet lib,

* fixup! Added support for network with ENet lib,

* fixup! Clang format

* More fixups!

* Moved ENetHost* and ENetPeer* into pimpl classes

* fixup! Moved ENetHost* and ENetPeer* into pimpl classes

* fixup! Clang again

* fixup! Moved ENetHost* and ENetPeer* into pimpl classes

* fixup! Moved ENetHost* and ENetPeer* into pimpl classes

* fixup! Moved ENetHost* and ENetPeer* into pimpl classes
---
 .gitmodules                    |  3 ++
 externals/CMakeLists.txt       |  4 ++
 externals/enet                 |  1 +
 src/CMakeLists.txt             |  1 +
 src/citra_qt/CMakeLists.txt    |  2 +-
 src/citra_qt/bootmanager.cpp   |  3 ++
 src/common/logging/backend.cpp |  1 +
 src/common/logging/log.h       |  1 +
 src/network/CMakeLists.txt     | 16 ++++++++
 src/network/network.cpp        | 50 +++++++++++++++++++++++
 src/network/network.h          | 25 ++++++++++++
 src/network/room.cpp           | 60 +++++++++++++++++++++++++++
 src/network/room.h             | 60 +++++++++++++++++++++++++++
 src/network/room_member.cpp    | 74 ++++++++++++++++++++++++++++++++++
 src/network/room_member.h      | 65 +++++++++++++++++++++++++++++
 15 files changed, 365 insertions(+), 1 deletion(-)
 create mode 160000 externals/enet
 create mode 100644 src/network/CMakeLists.txt
 create mode 100644 src/network/network.cpp
 create mode 100644 src/network/network.h
 create mode 100644 src/network/room.cpp
 create mode 100644 src/network/room.h
 create mode 100644 src/network/room_member.cpp
 create mode 100644 src/network/room_member.h

diff --git a/.gitmodules b/.gitmodules
index 36caa59f81..ac0df914d8 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -25,3 +25,6 @@
 [submodule "fmt"]
     path = externals/fmt
     url = https://github.com/fmtlib/fmt.git
+[submodule "externals/enet"]
+    path = externals/enet
+    url = https://github.com/lsalzman/enet
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 02e02350c8..cc47166fc2 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -48,3 +48,7 @@ if (ARCHITECTURE_x86_64)
     target_include_directories(xbyak INTERFACE ./xbyak/xbyak)
     target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
 endif()
+
+# ENet
+add_subdirectory(enet)
+target_include_directories(enet INTERFACE ./enet/include)
diff --git a/externals/enet b/externals/enet
new file mode 160000
index 0000000000..9d9ba122d4
--- /dev/null
+++ b/externals/enet
@@ -0,0 +1 @@
+Subproject commit 9d9ba122d4818f7ae1aef2197933ac696edb2331
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a45439481c..655bd83aaf 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,6 +5,7 @@ add_subdirectory(common)
 add_subdirectory(core)
 add_subdirectory(video_core)
 add_subdirectory(audio_core)
+add_subdirectory(network)
 add_subdirectory(input_common)
 add_subdirectory(tests)
 if (ENABLE_SDL2)
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 4841cbf05e..9572d3e288 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -91,7 +91,7 @@ if (APPLE)
 else()
     add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
 endif()
-target_link_libraries(citra-qt PRIVATE audio_core common core input_common video_core)
+target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
 target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets)
 target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
 
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index a8a4aed8b3..30554890f2 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -17,6 +17,7 @@
 #include "core/settings.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
+#include "network/network.h"
 
 EmuThread::EmuThread(GRenderWindow* render_window)
     : exec_step(false), running(false), stop_run(false), render_window(render_window) {}
@@ -110,10 +111,12 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
     setWindowTitle(QString::fromStdString(window_title));
 
     InputCommon::Init();
+    Network::Init();
 }
 
 GRenderWindow::~GRenderWindow() {
     InputCommon::Shutdown();
+    Network::Shutdown();
 }
 
 void GRenderWindow::moveContext() {
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 42f6a99188..0e4b85a763 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -72,6 +72,7 @@ namespace Log {
     SUB(Audio, DSP)                                                                                \
     SUB(Audio, Sink)                                                                               \
     CLS(Input)                                                                                     \
+    CLS(Network)                                                                                   \
     CLS(Loader)
 
 // GetClassName is a macro defined by Windows.h, grrr...
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 1b905f66c5..8f13b80b3e 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -90,6 +90,7 @@ enum class Class : ClassType {
     Audio_Sink,        ///< Emulator audio output backend
     Loader,            ///< ROM loader
     Input,             ///< Input emulation
+    Network,           ///< Network emulation
     Count              ///< Total number of logging classes
 };
 
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
new file mode 100644
index 0000000000..aeabe430ea
--- /dev/null
+++ b/src/network/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(SRCS
+            network.cpp
+            room.cpp
+            room_member.cpp
+            )
+
+set(HEADERS
+            network.h
+            room.h
+            room_member.h
+            )
+
+create_directory_groups(${SRCS} ${HEADERS})
+
+add_library(network STATIC ${SRCS} ${HEADERS})
+target_link_libraries(network PRIVATE common enet)
diff --git a/src/network/network.cpp b/src/network/network.cpp
new file mode 100644
index 0000000000..51b5d6a9fa
--- /dev/null
+++ b/src/network/network.cpp
@@ -0,0 +1,50 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "enet/enet.h"
+#include "network/network.h"
+
+namespace Network {
+
+static std::shared_ptr<RoomMember> g_room_member; ///< RoomMember (Client) for network games
+static std::shared_ptr<Room> g_room;              ///< Room (Server) for network games
+// TODO(B3N30): Put these globals into a networking class
+
+bool Init() {
+    if (enet_initialize() != 0) {
+        LOG_ERROR(Network, "Error initalizing ENet");
+        return false;
+    }
+    g_room = std::make_shared<Room>();
+    g_room_member = std::make_shared<RoomMember>();
+    LOG_DEBUG(Network, "initialized OK");
+    return true;
+}
+
+std::weak_ptr<Room> GetRoom() {
+    return g_room;
+}
+
+std::weak_ptr<RoomMember> GetRoomMember() {
+    return g_room_member;
+}
+
+void Shutdown() {
+    if (g_room_member) {
+        if (g_room_member->IsConnected())
+            g_room_member->Leave();
+        g_room_member.reset();
+    }
+    if (g_room) {
+        if (g_room->GetState() == Room::State::Open)
+            g_room->Destroy();
+        g_room.reset();
+    }
+    enet_deinitialize();
+    LOG_DEBUG(Network, "shutdown OK");
+}
+
+} // namespace Network
diff --git a/src/network/network.h b/src/network/network.h
new file mode 100644
index 0000000000..6d002d693f
--- /dev/null
+++ b/src/network/network.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "network/room.h"
+#include "network/room_member.h"
+
+namespace Network {
+
+/// Initializes and registers the network device, the room, and the room member.
+bool Init();
+
+/// Returns a pointer to the room handle
+std::weak_ptr<Room> GetRoom();
+
+/// Returns a pointer to the room member handle
+std::weak_ptr<RoomMember> GetRoomMember();
+
+/// Unregisters the network device, the room, and the room member and shut them down.
+void Shutdown();
+
+} // namespace Network
diff --git a/src/network/room.cpp b/src/network/room.cpp
new file mode 100644
index 0000000000..48de2f5cb8
--- /dev/null
+++ b/src/network/room.cpp
@@ -0,0 +1,60 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "enet/enet.h"
+#include "network/room.h"
+
+namespace Network {
+
+/// Maximum number of concurrent connections allowed to this room.
+static constexpr u32 MaxConcurrentConnections = 10;
+
+class Room::RoomImpl {
+public:
+    ENetHost* server = nullptr; ///< Network interface.
+
+    std::atomic<State> state{State::Closed}; ///< Current state of the room.
+    RoomInformation room_information;        ///< Information about this room.
+};
+
+Room::Room() : room_impl{std::make_unique<RoomImpl>()} {}
+
+Room::~Room() = default;
+
+void Room::Create(const std::string& name, const std::string& server_address, u16 server_port) {
+    ENetAddress address;
+    address.host = ENET_HOST_ANY;
+    enet_address_set_host(&address, server_address.c_str());
+    address.port = server_port;
+
+    room_impl->server = enet_host_create(&address, MaxConcurrentConnections, NumChannels, 0, 0);
+    // TODO(B3N30): Allow specifying the maximum number of concurrent connections.
+    room_impl->state = State::Open;
+
+    room_impl->room_information.name = name;
+    room_impl->room_information.member_slots = MaxConcurrentConnections;
+
+    // TODO(B3N30): Start the receiving thread
+}
+
+Room::State Room::GetState() const {
+    return room_impl->state;
+}
+
+const RoomInformation& Room::GetRoomInformation() const {
+    return room_impl->room_information;
+}
+
+void Room::Destroy() {
+    room_impl->state = State::Closed;
+    // TODO(B3n30): Join the receiving thread
+
+    if (room_impl->server) {
+        enet_host_destroy(room_impl->server);
+    }
+    room_impl->room_information = {};
+    room_impl->server = nullptr;
+}
+
+} // namespace Network
diff --git a/src/network/room.h b/src/network/room.h
new file mode 100644
index 0000000000..70c64d5f17
--- /dev/null
+++ b/src/network/room.h
@@ -0,0 +1,60 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+
+namespace Network {
+
+constexpr u16 DefaultRoomPort = 1234;
+constexpr size_t NumChannels = 1; // Number of channels used for the connection
+
+struct RoomInformation {
+    std::string name; ///< Name of the server
+    u32 member_slots; ///< Maximum number of members in this room
+};
+
+/// This is what a server [person creating a server] would use.
+class Room final {
+public:
+    enum class State : u8 {
+        Open,   ///< The room is open and ready to accept connections.
+        Closed, ///< The room is not opened and can not accept connections.
+    };
+
+    Room();
+    ~Room();
+
+    /**
+     * Gets the current state of the room.
+     */
+    State GetState() const;
+
+    /**
+     * Gets the room information of the room.
+     */
+    const RoomInformation& GetRoomInformation() const;
+
+    /**
+     * Creates the socket for this room. Will bind to default address if
+     * server is empty string.
+     */
+    void Create(const std::string& name, const std::string& server = "",
+                u16 server_port = DefaultRoomPort);
+
+    /**
+     * Destroys the socket
+     */
+    void Destroy();
+
+private:
+    class RoomImpl;
+    std::unique_ptr<RoomImpl> room_impl;
+};
+
+} // namespace Network
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
new file mode 100644
index 0000000000..c87f009f43
--- /dev/null
+++ b/src/network/room_member.cpp
@@ -0,0 +1,74 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "enet/enet.h"
+#include "network/room_member.h"
+
+namespace Network {
+
+constexpr u32 ConnectionTimeoutMs = 5000;
+
+class RoomMember::RoomMemberImpl {
+public:
+    ENetHost* client = nullptr; ///< ENet network interface.
+    ENetPeer* server = nullptr; ///< The server peer the client is connected to
+
+    std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
+
+    std::string nickname; ///< The nickname of this member.
+};
+
+RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {
+    room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
+    ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
+}
+
+RoomMember::~RoomMember() {
+    ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
+    enet_host_destroy(room_member_impl->client);
+}
+
+RoomMember::State RoomMember::GetState() const {
+    return room_member_impl->state;
+}
+
+void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
+                      u16 client_port) {
+    ENetAddress address{};
+    enet_address_set_host(&address, server_addr);
+    address.port = server_port;
+
+    room_member_impl->server =
+        enet_host_connect(room_member_impl->client, &address, NumChannels, 0);
+
+    if (!room_member_impl->server) {
+        room_member_impl->state = State::Error;
+        return;
+    }
+
+    ENetEvent event{};
+    int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs);
+    if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
+        room_member_impl->nickname = nick;
+        room_member_impl->state = State::Joining;
+        // TODO(B3N30): Send a join request with the nickname to the server
+        // TODO(B3N30): Start the receive thread
+    } else {
+        room_member_impl->state = State::CouldNotConnect;
+    }
+}
+
+bool RoomMember::IsConnected() const {
+    return room_member_impl->state == State::Joining || room_member_impl->state == State::Joined;
+}
+
+void RoomMember::Leave() {
+    enet_peer_disconnect(room_member_impl->server, 0);
+    room_member_impl->state = State::Idle;
+    // TODO(B3N30): Close the receive thread
+    enet_peer_reset(room_member_impl->server);
+}
+
+} // namespace Network
diff --git a/src/network/room_member.h b/src/network/room_member.h
new file mode 100644
index 0000000000..177622b690
--- /dev/null
+++ b/src/network/room_member.h
@@ -0,0 +1,65 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+#include "network/room.h"
+
+namespace Network {
+
+/**
+ * This is what a client [person joining a server] would use.
+ * It also has to be used if you host a game yourself (You'd create both, a Room and a
+ * RoomMembership for yourself)
+ */
+class RoomMember final {
+public:
+    enum class State : u8 {
+        Idle,    ///< Default state
+        Error,   ///< Some error [permissions to network device missing or something]
+        Joining, ///< The client is attempting to join a room.
+        Joined,  ///< The client is connected to the room and is ready to send/receive packets.
+        LostConnection, ///< Connection closed
+
+        // Reasons why connection was rejected
+        NameCollision,  ///< Somebody is already using this name
+        MacCollision,   ///< Somebody is already using that mac-address
+        CouldNotConnect ///< The room is not responding to a connection attempt
+    };
+
+    RoomMember();
+    ~RoomMember();
+
+    /**
+     * Returns the status of our connection to the room.
+     */
+    State GetState() const;
+
+    /**
+     * Returns whether we're connected to a server or not.
+     */
+    bool IsConnected() const;
+
+    /**
+     * Attempts to join a room at the specified address and port, using the specified nickname.
+     * This may fail if the username is already taken.
+     */
+    void Join(const std::string& nickname, const char* server_addr = "127.0.0.1",
+              const u16 serverPort = DefaultRoomPort, const u16 clientPort = 0);
+
+    /**
+     * Leaves the current room.
+     */
+    void Leave();
+
+private:
+    class RoomMemberImpl;
+    std::unique_ptr<RoomMemberImpl> room_member_impl;
+};
+
+} // namespace Network
-- 
GitLab