mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2024-12-22 16:30:23 +00:00
Started adding CoopNet
This commit is contained in:
parent
2f4008f9c9
commit
04032a14af
14 changed files with 267 additions and 35 deletions
24
Makefile
24
Makefile
|
@ -534,7 +534,7 @@ SRC_DIRS := src src/engine src/game src/audio src/bass_audio src/menu src/buffer
|
|||
BIN_DIRS := bin bin/$(VERSION)
|
||||
|
||||
# PC files
|
||||
SRC_DIRS += src/pc src/pc/gfx src/pc/audio src/pc/controller src/pc/fs src/pc/fs/packtypes src/pc/mods src/pc/network src/pc/network/packets src/pc/network/socket src/pc/utils src/pc/utils/miniz src/pc/djui src/pc/lua src/pc/lua/utils
|
||||
SRC_DIRS += src/pc src/pc/gfx src/pc/audio src/pc/controller src/pc/fs src/pc/fs/packtypes src/pc/mods src/pc/network src/pc/network/packets src/pc/network/socket src/pc/network/coopnet src/pc/utils src/pc/utils/miniz src/pc/djui src/pc/lua src/pc/lua/utils
|
||||
|
||||
#ifeq ($(DISCORDRPC),1)
|
||||
# SRC_DIRS += src/pc/discord
|
||||
|
@ -794,7 +794,7 @@ INCLUDE_DIRS := include $(BUILD_DIR) $(BUILD_DIR)/include src .
|
|||
ifeq ($(TARGET_N64),1)
|
||||
INCLUDE_DIRS += include/libc
|
||||
else
|
||||
INCLUDE_DIRS += sound lib/lua/include $(EXTRA_INCLUDES)
|
||||
INCLUDE_DIRS += sound lib/lua/include lib/coopnet/include $(EXTRA_INCLUDES)
|
||||
endif
|
||||
|
||||
# Connfigure backend flags
|
||||
|
@ -971,6 +971,26 @@ else
|
|||
LDFLAGS += -Llib/lua/linux -l:liblua53.a -ldl
|
||||
endif
|
||||
|
||||
# coopnet
|
||||
LDFLAGS += -Llib/coopnet/linux -l:libcoopnet.a -l:libjuice.a
|
||||
#ifeq ($(WINDOWS_BUILD),1)
|
||||
# ifeq ($(TARGET_BITS), 32)
|
||||
# LDFLAGS += -Llib/coopnet/win32 -l:libcoopnet.a
|
||||
# else
|
||||
# LDFLAGS += -Llib/coopnet/win64 -l:libcoopnet.a
|
||||
# endif
|
||||
#else ifeq ($(OSX_BUILD),1)
|
||||
# LDFLAGS += -L./lib/coopnet/mac/ -l coopnet
|
||||
#else ifeq ($(TARGET_RPI),1)
|
||||
# ifneq (,$(findstring aarch64,$(machine)))
|
||||
# LDFLAGS += -Llib/coopnet/linux -l:libcoopnet-arm64.a
|
||||
# else
|
||||
# LDFLAGS += -Llib/coopnet/linux -l:libcoopnet-arm.a
|
||||
# endif
|
||||
#else
|
||||
# LDFLAGS += -Llib/coopnet/linux -l:libcoopnet.a
|
||||
#endif
|
||||
|
||||
# Network/Discord/Bass (ugh, needs cleanup)
|
||||
ifeq ($(WINDOWS_BUILD),1)
|
||||
LDFLAGS += -L"ws2_32" -lwsock32
|
||||
|
|
|
@ -176,6 +176,7 @@ SERVER_TITLE = "SERVER"
|
|||
HOST_TITLE = "HOST"
|
||||
DISCORD = "Discord"
|
||||
DIRECT_CONNECTION = "Direct Connection"
|
||||
COOPNET = "CoopNet"
|
||||
NETWORK_SYSTEM = "Network system"
|
||||
PORT = "Port"
|
||||
SAVE_SLOT = "Save Slot"
|
||||
|
|
|
@ -176,6 +176,7 @@ SERVER_TITLE = "SERVEUR"
|
|||
HOST_TITLE = "HÉBERGER"
|
||||
DISCORD = "Discord"
|
||||
DIRECT_CONNECTION = "Connexion Directe"
|
||||
COOPNET = "CoopNet"
|
||||
NETWORK_SYSTEM = "Mode d'hébergement"
|
||||
PORT = "Port"
|
||||
SAVE_SLOT = "Sauvegarde"
|
||||
|
|
|
@ -176,6 +176,7 @@ SERVER_TITLE = "SERVER"
|
|||
HOST_TITLE = "VERANSTALTEN"
|
||||
DISCORD = "Discord"
|
||||
DIRECT_CONNECTION = "Direkte Verbindung"
|
||||
COOPNET = "CoopNet"
|
||||
NETWORK_SYSTEM = "Netzwerk-System"
|
||||
PORT = "Port"
|
||||
SAVE_SLOT = "Save Slot"
|
||||
|
|
|
@ -175,6 +175,7 @@ AMOUNT_OF_PLAYERS = "Quantidade de jogadores"
|
|||
SERVER_TITLE = "SERVIDOR"
|
||||
HOST_TITLE = "HOSTEAR"
|
||||
DISCORD = "Discord"
|
||||
COOPNET = "CoopNet"
|
||||
DIRECT_CONNECTION = "Conexão Direta"
|
||||
NETWORK_SYSTEM = "Sistema de Rede"
|
||||
PORT = "Porta"
|
||||
|
|
|
@ -175,6 +175,7 @@ AMOUNT_OF_PLAYERS = "Número de jugadores"
|
|||
SERVER_TITLE = "SERVIDOR"
|
||||
HOST_TITLE = "ALOJAR"
|
||||
DISCORD = "Discord"
|
||||
COOPNET = "CoopNet"
|
||||
DIRECT_CONNECTION = "Conexión Directa"
|
||||
NETWORK_SYSTEM = "Modo de conexión"
|
||||
PORT = "Puerto"
|
||||
|
|
61
lib/coopnet/include/libcoopnet.h
Normal file
61
lib/coopnet/include/libcoopnet.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#ifndef LIBCOOPNET_H
|
||||
#define LIBCOOPNET_H
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
COOPNET_OK,
|
||||
COOPNET_FAILED,
|
||||
COOPNET_DISCONNECTED,
|
||||
} CoopNetRc;
|
||||
|
||||
enum MPacketErrorNumber {
|
||||
MERR_NONE,
|
||||
MERR_LOBBY_NOT_FOUND,
|
||||
MERR_LOBBY_JOIN_FULL,
|
||||
MERR_LOBBY_JOIN_FAILED,
|
||||
MERR_MAX,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
void (*OnConnected)(uint64_t aUserId);
|
||||
void (*OnDisconnected)(void);
|
||||
void (*OnLobbyCreated)(uint64_t aLobbyId, const char* aGame, const char* aVersion, const char* aTitle, uint16_t aMaxConnections);
|
||||
void (*OnLobbyJoined)(uint64_t aLobbyId, uint64_t aUserId, uint64_t aOwnerId);
|
||||
void (*OnLobbyLeft)(uint64_t aLobbyId, uint64_t aUserId);
|
||||
void (*OnLobbyListGot)(uint64_t aLobbyId, uint64_t aOwnerId, uint16_t aConnections, uint16_t aMaxConnections, const char* aGame, const char* aVersion, const char* aTitle);
|
||||
void (*OnReceive)(uint64_t aFromUserId, const uint8_t* aData, uint64_t aSize);
|
||||
void (*OnError)(enum MPacketErrorNumber aErrorNumber);
|
||||
void (*OnPeerConnected)(uint64_t aPeerId);
|
||||
void (*OnPeerDisconnected)(uint64_t aPeerId);
|
||||
} CoopNetCallbacks;
|
||||
|
||||
typedef struct {
|
||||
bool SkipWinsockInit;
|
||||
} CoopNetSettings;
|
||||
|
||||
extern CoopNetCallbacks gCoopNetCallbacks;
|
||||
extern CoopNetSettings gCoopNetSettings;
|
||||
|
||||
bool coopnet_is_connected(void);
|
||||
CoopNetRc coopnet_begin(const char* aHost, uint32_t aPort);
|
||||
CoopNetRc coopnet_shutdown(void);
|
||||
CoopNetRc coopnet_update(void);
|
||||
CoopNetRc coopnet_lobby_create(const char* aGame, const char* aVersion, const char* aTitle, uint16_t aMaxConnections);
|
||||
CoopNetRc coopnet_lobby_join(uint64_t aLobbyId);
|
||||
CoopNetRc coopnet_lobby_leave(uint64_t aLobbyId);
|
||||
CoopNetRc coopnet_lobby_list_get(const char* aGame);
|
||||
CoopNetRc coopnet_send(const uint8_t* aData, uint64_t aDataLength);
|
||||
CoopNetRc coopnet_send_to(uint64_t aPeerId, const uint8_t* aData, uint64_t aDataLength);
|
||||
CoopNetRc coopnet_unpeer(uint64_t aPeerId);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
#endif
|
|
@ -12,16 +12,16 @@
|
|||
#include "pc/configfile.h"
|
||||
#include "pc/cheats.h"
|
||||
|
||||
#ifdef DISCORD_SDK
|
||||
#define DJUI_HOST_NS_IS_SOCKET (configNetworkSystem == 1)
|
||||
#else
|
||||
#define DJUI_HOST_NS_IS_SOCKET (true)
|
||||
#endif
|
||||
|
||||
struct DjuiInputbox* sInputboxPort = NULL;
|
||||
|
||||
static void djui_panel_host_network_system_change(UNUSED struct DjuiBase* base) {
|
||||
djui_base_set_enabled(&sInputboxPort->base, DJUI_HOST_NS_IS_SOCKET);
|
||||
#ifndef DISCORD_SDK
|
||||
struct DjuiSelectionbox* selectionbox = (struct DjuiSelectionbox*) base;
|
||||
if (selectionbox->value == NS_DISCORD) {
|
||||
selectionbox->value = NS_SOCKET;
|
||||
}
|
||||
#endif
|
||||
djui_base_set_enabled(&sInputboxPort->base, (configNetworkSystem == NS_SOCKET));
|
||||
}
|
||||
|
||||
static bool djui_panel_host_port_valid(void) {
|
||||
|
@ -77,15 +77,12 @@ void djui_panel_host_create(struct DjuiBase* caller) {
|
|||
: DLANG(HOST, HOST_TITLE));
|
||||
struct DjuiBase* body = djui_three_panel_get_body(panel);
|
||||
{
|
||||
#ifdef DISCORD_SDK
|
||||
char* nChoices[2] = { DLANG(HOST, DISCORD), DLANG(HOST, DIRECT_CONNECTION) };
|
||||
struct DjuiSelectionbox* selectionbox1 = djui_selectionbox_create(body, DLANG(HOST, NETWORK_SYSTEM), nChoices, 2, &configNetworkSystem, djui_panel_host_network_system_change);
|
||||
char* nChoices[] = { DLANG(HOST, DISCORD), DLANG(HOST, DIRECT_CONNECTION), DLANG(HOST, COOPNET) };
|
||||
struct DjuiSelectionbox* selectionbox1 = djui_selectionbox_create(body, DLANG(HOST, NETWORK_SYSTEM), nChoices, 3, &configNetworkSystem, djui_panel_host_network_system_change);
|
||||
if (gNetworkType == NT_SERVER) {
|
||||
djui_base_set_enabled(&selectionbox1->base, false);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
struct DjuiRect* rect1 = djui_rect_container_create(body, 32);
|
||||
{
|
||||
struct DjuiText* text1 = djui_text_create(&rect1->base, DLANG(HOST, PORT));
|
||||
|
@ -107,10 +104,8 @@ void djui_panel_host_create(struct DjuiBase* caller) {
|
|||
djui_interactable_hook_value_change(&inputbox1->base, djui_panel_host_port_text_change);
|
||||
if (gNetworkType == NT_SERVER) {
|
||||
djui_base_set_enabled(&inputbox1->base, false);
|
||||
#ifdef DISCORD_SDK
|
||||
} else {
|
||||
djui_base_set_enabled(&inputbox1->base, DJUI_HOST_NS_IS_SOCKET);
|
||||
#endif
|
||||
djui_base_set_enabled(&inputbox1->base, (configNetworkSystem == NS_SOCKET));
|
||||
}
|
||||
sInputboxPort = inputbox1;
|
||||
}
|
||||
|
|
|
@ -22,15 +22,10 @@ void djui_panel_do_host(void) {
|
|||
update_all_mario_stars();
|
||||
|
||||
#ifndef DISCORD_SDK
|
||||
configNetworkSystem = 1;
|
||||
network_set_system(NS_SOCKET);
|
||||
#else
|
||||
if (configNetworkSystem == 0) {
|
||||
network_set_system(NS_DISCORD);
|
||||
} else {
|
||||
network_set_system(NS_SOCKET);
|
||||
}
|
||||
if (configNetworkSystem == NS_DISCORD) { configNetworkSystem = NS_COOPNET; }
|
||||
#endif
|
||||
if (configNetworkSystem >= NS_MAX) { configNetworkSystem = NS_MAX; }
|
||||
network_set_system(configNetworkSystem);
|
||||
|
||||
network_init(NT_SERVER);
|
||||
djui_panel_modlist_create(NULL);
|
||||
|
@ -56,14 +51,11 @@ void djui_panel_host_message_create(struct DjuiBase* caller) {
|
|||
char* warningMessage = NULL;
|
||||
bool hideHostButton = false;
|
||||
|
||||
#ifdef DISCORD_SDK
|
||||
if (!configNetworkSystem) {
|
||||
if (configNetworkSystem == NS_DISCORD) {
|
||||
warningLines = gDiscordFailed ? 5 : 13;
|
||||
warningMessage = gDiscordFailed ? DLANG(HOST_MESSAGE, WARN_DISCORD2) : DLANG(HOST_MESSAGE, WARN_DISCORD);
|
||||
hideHostButton = gDiscordFailed;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
warningLines = 5;
|
||||
warningMessage = calloc(256, sizeof(char));
|
||||
sprintf(warningMessage, DLANG(HOST_MESSAGE, WARN_SOCKET), configHostPort);
|
||||
|
@ -93,10 +85,7 @@ void djui_panel_host_message_create(struct DjuiBase* caller) {
|
|||
}
|
||||
|
||||
djui_panel_add(caller, panel, NULL);
|
||||
#ifdef DISCORD_SDK
|
||||
if (configNetworkSystem)
|
||||
#endif
|
||||
{
|
||||
if (configNetworkSystem != NS_DISCORD) {
|
||||
free(warningMessage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ void djui_panel_join_do_join(struct DjuiBase* caller) {
|
|||
}
|
||||
network_reset_reconnect_and_rehost();
|
||||
djui_panel_join_ip_text_set_new();
|
||||
network_set_system(NS_SOCKET);
|
||||
network_set_system(NS_COOPNET); // DO NOT COMMIT
|
||||
network_init(NT_CLIENT);
|
||||
djui_panel_join_message_create(caller);
|
||||
}
|
||||
|
|
152
src/pc/network/coopnet/coopnet.c
Normal file
152
src/pc/network/coopnet/coopnet.c
Normal file
|
@ -0,0 +1,152 @@
|
|||
#include "libcoopnet.h"
|
||||
#include "coopnet.h"
|
||||
#include "pc/network/network.h"
|
||||
#include "pc/debuglog.h"
|
||||
|
||||
#define HOST "localhost"
|
||||
#define PORT 34197
|
||||
|
||||
static uint64_t sLocalUserId = 0;
|
||||
static uint64_t sLocalLobbyId = 0;
|
||||
static uint64_t sLocalLobbyOwnerId = 0;
|
||||
static uint64_t sNetworkUserIds[MAX_PLAYERS] = { 0 };
|
||||
static enum NetworkType sNetworkType;
|
||||
|
||||
static u8 coopnet_user_id_to_local_index(uint64_t userId) {
|
||||
for (int i = 1; i < MAX_PLAYERS; i++) {
|
||||
if (gNetworkPlayers[i].connected && sNetworkUserIds[i] == userId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return UNKNOWN_LOCAL_INDEX;
|
||||
}
|
||||
|
||||
static void coopnet_on_connected(uint64_t userId) {
|
||||
sLocalUserId = userId;
|
||||
if (sNetworkType == NT_SERVER) {
|
||||
coopnet_lobby_create("sm64ex-coop", "beta 999", "n/a", 16);
|
||||
} else {
|
||||
coopnet_lobby_join(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void coopnet_on_peer_connected(UNUSED uint64_t peerId) {
|
||||
}
|
||||
|
||||
static void coopnet_on_peer_disconnected(uint64_t peerId) {
|
||||
u8 localIndex = coopnet_user_id_to_local_index(peerId);
|
||||
if (localIndex != UNKNOWN_LOCAL_INDEX && gNetworkPlayers[localIndex].connected) {
|
||||
network_player_disconnected(gNetworkPlayers[localIndex].globalIndex);
|
||||
}
|
||||
}
|
||||
|
||||
static void coopnet_on_receive(uint64_t userId, const uint8_t* data, uint64_t dataLength) {
|
||||
sNetworkUserIds[0] = userId;
|
||||
u8 localIndex = coopnet_user_id_to_local_index(userId);
|
||||
network_receive(localIndex, &userId, (u8*)data, dataLength);
|
||||
}
|
||||
|
||||
static void coopnet_on_lobby_joined(uint64_t lobbyId, uint64_t userId, uint64_t ownerId) {
|
||||
LOG_INFO("coopnet_on_lobby_joined!");
|
||||
sNetworkUserIds[0] = ownerId;
|
||||
sLocalLobbyId = lobbyId;
|
||||
sLocalLobbyOwnerId = ownerId;
|
||||
if (userId == sLocalUserId && sNetworkType == NT_CLIENT) {
|
||||
network_send_mod_list_request();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void coopnet_on_lobby_left(uint64_t lobbyId, uint64_t userId) {
|
||||
LOG_INFO("coopnet_on_lobby_left!");
|
||||
if (lobbyId == sLocalLobbyId && userId == sLocalUserId) {
|
||||
network_shutdown(false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ns_coopnet_initialize(enum NetworkType networkType) {
|
||||
if (coopnet_is_connected()) {
|
||||
coopnet_shutdown();
|
||||
}
|
||||
|
||||
gCoopNetCallbacks.OnConnected = coopnet_on_connected;
|
||||
gCoopNetCallbacks.OnReceive = coopnet_on_receive;
|
||||
gCoopNetCallbacks.OnLobbyJoined = coopnet_on_lobby_joined;
|
||||
gCoopNetCallbacks.OnLobbyLeft = coopnet_on_lobby_left;
|
||||
gCoopNetCallbacks.OnPeerConnected = coopnet_on_peer_connected;
|
||||
gCoopNetCallbacks.OnPeerDisconnected = coopnet_on_peer_disconnected;
|
||||
sNetworkType = networkType;
|
||||
|
||||
return (coopnet_begin(HOST, PORT) == COOPNET_OK);
|
||||
}
|
||||
|
||||
static s64 ns_coopnet_get_id(u8 localIndex) {
|
||||
if (localIndex == 0) { return (s64)sLocalUserId; }
|
||||
return (s64)sNetworkUserIds[localIndex];
|
||||
}
|
||||
|
||||
static char* ns_coopnet_get_id_str(u8 localIndex) {
|
||||
static char id_str[22] = { 0 };
|
||||
if (localIndex == UNKNOWN_LOCAL_INDEX) {
|
||||
snprintf(id_str, 22, "???");
|
||||
} else {
|
||||
snprintf(id_str, 22, "%lld", (long long int)ns_coopnet_get_id(localIndex));
|
||||
}
|
||||
return id_str;
|
||||
}
|
||||
|
||||
static void ns_coopnet_save_id(u8 localIndex, s64 networkId) {
|
||||
SOFT_ASSERT(localIndex > 0);
|
||||
SOFT_ASSERT(localIndex < MAX_PLAYERS);
|
||||
sNetworkUserIds[localIndex] = (networkId == 0) ? sNetworkUserIds[0] : (u64)networkId;
|
||||
}
|
||||
|
||||
static void ns_coopnet_clear_id(u8 localIndex) {
|
||||
if (localIndex == 0) { return; }
|
||||
SOFT_ASSERT(localIndex < MAX_PLAYERS);
|
||||
sNetworkUserIds[localIndex] = 0;
|
||||
}
|
||||
|
||||
static void* ns_coopnet_dup_addr(u8 localIndex) {
|
||||
void* address = malloc(sizeof(u64));
|
||||
memcpy(address, &sNetworkUserIds[localIndex], sizeof(u64));
|
||||
return address;
|
||||
}
|
||||
|
||||
static bool ns_coopnet_match_addr(void* addr1, void* addr2) {
|
||||
return !memcmp(addr1, addr2, sizeof(u64));
|
||||
}
|
||||
|
||||
static void ns_coopnet_update(void) {
|
||||
coopnet_update();
|
||||
}
|
||||
|
||||
static int ns_coopnet_network_send(u8 localIndex, void* address, u8* data, u16 dataLength) {
|
||||
if (!coopnet_is_connected()) { return 1; }
|
||||
//if (gCurLobbyId == 0) { return 2; }
|
||||
|
||||
u64 userId = sNetworkUserIds[localIndex];
|
||||
if (localIndex == 0 && address != NULL) { userId = *(u64*)address; }
|
||||
coopnet_send_to(userId, data, dataLength);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ns_coopnet_shutdown(void) {
|
||||
coopnet_shutdown();
|
||||
}
|
||||
|
||||
struct NetworkSystem gNetworkSystemCoopNet = {
|
||||
.initialize = ns_coopnet_initialize,
|
||||
.get_id = ns_coopnet_get_id,
|
||||
.get_id_str = ns_coopnet_get_id_str,
|
||||
.save_id = ns_coopnet_save_id,
|
||||
.clear_id = ns_coopnet_clear_id,
|
||||
.dup_addr = ns_coopnet_dup_addr,
|
||||
.match_addr = ns_coopnet_match_addr,
|
||||
.update = ns_coopnet_update,
|
||||
.send = ns_coopnet_network_send,
|
||||
.shutdown = ns_coopnet_shutdown,
|
||||
.requireServerBroadcast = false,
|
||||
.name = "CoopNet",
|
||||
};
|
6
src/pc/network/coopnet/coopnet.h
Normal file
6
src/pc/network/coopnet/coopnet.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef COOPNET_H
|
||||
#define COOPNET_H
|
||||
|
||||
extern struct NetworkSystem gNetworkSystemCoopNet;
|
||||
|
||||
#endif
|
|
@ -1,4 +1,5 @@
|
|||
#include "socket/socket.h"
|
||||
#include "coopnet/coopnet.h"
|
||||
#include <stdio.h>
|
||||
#include "network.h"
|
||||
#include "object_fields.h"
|
||||
|
@ -89,6 +90,7 @@ void network_set_system(enum NetworkSystemType nsType) {
|
|||
#ifdef DISCORD_SDK
|
||||
case NS_DISCORD: gNetworkSystem = &gNetworkSystemDiscord; break;
|
||||
#endif
|
||||
case NS_COOPNET: gNetworkSystem = &gNetworkSystemCoopNet; break;
|
||||
default: LOG_ERROR("Unknown network system: %d", nsType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ extern struct MarioState gMarioStates[];
|
|||
enum NetworkSystemType {
|
||||
NS_SOCKET,
|
||||
NS_DISCORD,
|
||||
NS_COOPNET,
|
||||
NS_MAX,
|
||||
};
|
||||
|
||||
struct NetworkSystem {
|
||||
|
|
Loading…
Reference in a new issue