From f8bffd3b2a04830d30e1be8b5ba727fd89713f7c Mon Sep 17 00:00:00 2001 From: MysterD Date: Fri, 18 Sep 2020 23:06:26 -0700 Subject: [PATCH] Added player connected/disconnected events Changed synchronizing text to be more descriptive Added 'player connected', 'player disconnected', 'network shutdown' chat messages Prevented someone from joining through Discord while in another lobby Added the distinction of sending a packet to all vs to a specific player Enforced lobby size of 2, multiple joiners in a direct connection will be booted Stored network destination for each player Detected network drops --- build-windows-visual-studio/sm64ex.vcxproj | 7 + .../sm64ex.vcxproj.filters | 21 +++ src/game/chat.c | 23 +-- src/game/chat.h | 8 +- src/game/ingame_menu.c | 41 ++++-- src/game/level_update.c | 6 +- src/pc/controller/controller_keyboard_debug.c | 6 +- src/pc/network/discord/activity.c | 8 +- src/pc/network/discord/discord.c | 2 + src/pc/network/discord/discord_network.c | 51 +++++-- src/pc/network/discord/discord_network.h | 7 +- src/pc/network/discord/lobby.c | 18 ++- src/pc/network/network.c | 37 ++++- src/pc/network/network.h | 12 +- src/pc/network/network_player.c | 131 ++++++++++++++++++ src/pc/network/network_player.h | 36 +++++ src/pc/network/packets/packet.c | 14 +- src/pc/network/packets/packet.h | 26 +++- src/pc/network/packets/packet_join.c | 45 ++++-- src/pc/network/packets/packet_keep_alive.c | 15 ++ src/pc/network/packets/packet_kick.c | 31 +++++ src/pc/network/packets/packet_leaving.c | 43 ++++++ src/pc/network/packets/packet_reliable.c | 6 +- src/pc/network/socket/socket.c | 48 +++++-- src/pc/network/socket/socket_windows.h | 1 - 25 files changed, 564 insertions(+), 79 deletions(-) create mode 100644 src/pc/network/network_player.c create mode 100644 src/pc/network/network_player.h create mode 100644 src/pc/network/packets/packet_keep_alive.c create mode 100644 src/pc/network/packets/packet_kick.c create mode 100644 src/pc/network/packets/packet_leaving.c diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj index 068cde8d3..853d89c6c 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj +++ b/build-windows-visual-studio/sm64ex.vcxproj @@ -3958,12 +3958,18 @@ + + + + + + @@ -4327,6 +4333,7 @@ + diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters index 0369aef02..b0fa94429 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj.filters +++ b/build-windows-visual-studio/sm64ex.vcxproj.filters @@ -15048,6 +15048,24 @@ Source Files\src\pc\network\packets + + Source Files\src\pc\network + + + Source Files\src\pc\network\packets + + + Source Files\src\pc\network\packets + + + Source Files\src\pc\network\packets + + + Source Files\src\pc\network\packets + + + Source Files\src\pc\network\packets + @@ -15997,5 +16015,8 @@ Header Files\src\game + + Header Files\src\pc\network + \ No newline at end of file diff --git a/src/game/chat.c b/src/game/chat.c index dd04f9914..2a30afacd 100644 --- a/src/game/chat.c +++ b/src/game/chat.c @@ -13,6 +13,7 @@ #include "pc/network/network.h" #include "audio_defines.h" #include "audio/external.h" +#include "menu/file_select.h" #define CHAT_DIALOG_MAX 96 #define CHAT_MESSAGES_MAX 16 @@ -90,14 +91,21 @@ static void render_chat_message(struct ChatMessage* chatMessage, u8 index) { gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); } -void chat_add_message(char* ascii, u8 isLocal) { - message[onMessageIndex].dialog[0] = isLocal ? 0xFD : 0xFA; - message[onMessageIndex].dialog[1] = 0x9E; - str_ascii_to_dialog(ascii, &message[onMessageIndex].dialog[2], MIN(strlen(ascii), CHAT_DIALOG_MAX - 3)); - message[onMessageIndex].life = CHAT_LIFE_MAX; - message[onMessageIndex].isLocal = isLocal ? 1 : 0; +void chat_add_message(char* ascii, enum ChatMessageType chatMessageType) { + u8 character = '?'; + switch (chatMessageType) { + case CMT_LOCAL: character = 0xFD; break; + case CMT_REMOTE: character = 0xFA; break; + case CMT_SYSTEM: character = 0xF9; break; + } + struct ChatMessage* msg = &message[onMessageIndex]; + msg->dialog[0] = character; + msg->dialog[1] = 0x9E; + str_ascii_to_dialog(ascii, &msg->dialog[2], MIN(strlen(ascii), CHAT_DIALOG_MAX - 3)); + msg->life = (sSelectedFileNum != 0) ? CHAT_LIFE_MAX : CHAT_LIFE_MAX / 3; + msg->isLocal = (chatMessageType == CMT_LOCAL); onMessageIndex = (onMessageIndex + 1) % CHAT_MESSAGES_MAX; - play_sound(isLocal ? SOUND_MENU_MESSAGE_DISAPPEAR : SOUND_MENU_MESSAGE_APPEAR, gDefaultSoundArgs); + play_sound(msg->isLocal ? SOUND_MENU_MESSAGE_DISAPPEAR : SOUND_MENU_MESSAGE_APPEAR, gDefaultSoundArgs); } static void chat_stop_input(void) { @@ -118,7 +126,6 @@ void chat_start_input(void) { keyboard_start_text_input(TIM_SINGLE_LINE, CHAT_DIALOG_MAX - 3, chat_stop_input, chat_send_input); } - void render_chat(void) { u8 count = 0; if (sInChatInput) { diff --git a/src/game/chat.h b/src/game/chat.h index caa105b66..e7f91ab62 100644 --- a/src/game/chat.h +++ b/src/game/chat.h @@ -1,8 +1,14 @@ #ifndef CHAT_H #define CHAT_H +enum ChatMessageType { + CMT_LOCAL, + CMT_REMOTE, + CMT_SYSTEM, +}; + void render_chat(void); -void chat_add_message(char* ascii, u8 isLocal); +void chat_add_message(char* ascii, enum ChatMessageType chatMessageType); void chat_start_input(void); #endif \ No newline at end of file diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index ca95ffa43..d793ed111 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -25,6 +25,7 @@ #include "types.h" #include "macros.h" #include "pc/cheats.h" +#include "pc/network/network.h" #ifdef BETTERCAMERA #include "bettercamera.h" #endif @@ -2823,25 +2824,45 @@ s16 render_pause_courses_and_castle(void) { } s16 render_sync_level_screen(void) { + char* message; + + if (gNetworkType == NT_SERVER) { + message = network_player_any_connected() ? "Waiting for player..." : "Waiting for player to connect..."; + } else { + message = network_player_any_connected() ? "Waiting for player..." : "Not connected to anyone.\nPlease restart the game."; + } + + static f32 alphaScalar = 0.0f; + static clock_t lastDisplay = 0; + f32 elapsed = (clock() - lastDisplay) / (f32)CLOCKS_PER_SEC; + if (elapsed > 1.0f) { + alphaScalar = 0; + } else if (alphaScalar < 1.0f) { + alphaScalar += 0.3f; + if (alphaScalar > 1) { alphaScalar = 1; } + } + u8 alpha = (((f32)fabs(sin(gGlobalTimer / 20.0f)) * alphaScalar) * 255); + lastDisplay = clock(); + // black screen create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(0), 240.0f, 0); - create_dl_scale_matrix(MENU_MTX_NOPUSH, - GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT / 130.0f, 3.0f, 1.0f); + create_dl_scale_matrix(MENU_MTX_NOPUSH, GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT / 130.0f, 3.0f, 1.0f); gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box); gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + // print text + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + f32 textWidth = get_generic_ascii_string_width(message); + f32 textHeight = get_generic_ascii_string_height(message); - // synchronizing text - u8 colorFade = sins(gDialogColorFadeTimer) * 50.0f + 200.0f; - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); - gDPSetEnvColor(gDisplayListHead++, colorFade, colorFade, colorFade, 255); + f32 xPos = (SCREEN_WIDTH - textWidth) / 2.0f; + f32 yPos = (SCREEN_HEIGHT + textHeight) / 2.0f; - #define TEXT_SYNCHRONIZING 0x1C,0x22,0x17,0x0C,0x11,0x1B,0x18,0x17,0x12,0x02,0x12,0x17,0x10,0xFF - u8 synchronizing[] = { TEXT_SYNCHRONIZING }; + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, alpha); + print_generic_ascii_string(xPos, yPos, message); - print_hud_lut_string(HUD_LUT_GLOBAL, 80, 200, synchronizing); - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); return 0; } diff --git a/src/game/level_update.c b/src/game/level_update.c index c66f84f15..9bac8d59f 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -1046,11 +1046,11 @@ s32 play_mode_normal(void) { if (sCurrPlayMode == PLAY_MODE_NORMAL) { if (!gReceiveWarp) { if (sWarpDest.type == WARP_TYPE_CHANGE_LEVEL) { - set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL); + set_play_mode(PLAY_MODE_SYNC_LEVEL); network_send_level_warp_begin(); } else if (sTransitionTimer != 0) { if (sWarpDest.type == WARP_TYPE_CHANGE_AREA) { - set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_AREA); + set_play_mode(PLAY_MODE_SYNC_LEVEL); network_send_level_warp_begin(); } else { set_play_mode(PLAY_MODE_CHANGE_AREA); @@ -1085,7 +1085,7 @@ s32 play_mode_paused(void) { fade_into_special_warp(0, 0); gSavedCourseNum = COURSE_NONE; } - set_play_mode((gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL); + set_play_mode(PLAY_MODE_SYNC_LEVEL); network_send_level_warp_begin(); } else if (gPauseScreenMode == 3) { // We should only be getting "int 3" to here diff --git a/src/pc/controller/controller_keyboard_debug.c b/src/pc/controller/controller_keyboard_debug.c index 477615ac2..f2ec48367 100644 --- a/src/pc/controller/controller_keyboard_debug.c +++ b/src/pc/controller/controller_keyboard_debug.c @@ -31,7 +31,7 @@ static void debug_warp_level(u8 level) { sWarpDest.nodeId = node->destNode; sWarpDest.arg = 0; - sCurrPlayMode = (gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL; + sCurrPlayMode = PLAY_MODE_SYNC_LEVEL; network_send_level_warp_begin(); return; } @@ -47,7 +47,7 @@ static void debug_warp_level(u8 level) { sWarpDest.nodeId = node->destNode; sWarpDest.arg = 0; - sCurrPlayMode = (gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL; + sCurrPlayMode = PLAY_MODE_SYNC_LEVEL; network_send_level_warp_begin(); return; } @@ -82,7 +82,7 @@ static void debug_warp_area() { sWarpDest.nodeId = node->destNode; sWarpDest.arg = 0; - sCurrPlayMode = (gNetworkType != NT_NONE) ? PLAY_MODE_SYNC_LEVEL : PLAY_MODE_CHANGE_LEVEL; + sCurrPlayMode = PLAY_MODE_SYNC_LEVEL; network_send_level_warp_begin(); return; } diff --git a/src/pc/network/discord/activity.c b/src/pc/network/discord/activity.c index c949da951..e4fd58b27 100644 --- a/src/pc/network/discord/activity.c +++ b/src/pc/network/discord/activity.c @@ -13,8 +13,13 @@ static void on_activity_update_callback(UNUSED void* data, enum EDiscordResult r } static void on_activity_join_callback(UNUSED void* data, enum EDiscordResult result, struct DiscordLobby* lobby) { - LOG_INFO("> on_activity_join_callback returned %d, lobby %lld", result, lobby->id); + LOG_INFO("> on_activity_join_callback returned %d, lobby %lld, owner %lld", result, lobby->id, lobby->owner_id); DISCORD_REQUIRE(result); + if (gNetworkType != NT_NONE) { + LOG_ERROR("Joined lobby when already connected somewhere!"); + exit(0); + return; + } network_init(NT_CLIENT); gCurActivity.type = DiscordActivityType_Playing; @@ -27,6 +32,7 @@ static void on_activity_join_callback(UNUSED void* data, enum EDiscordResult res discord_network_init(lobby->id); discord_activity_update(false); + gNetworkUserIds[0] = lobby->owner_id; network_send_join_request(); } diff --git a/src/pc/network/discord/discord.c b/src/pc/network/discord/discord.c index e39845e96..7c30fc03b 100644 --- a/src/pc/network/discord/discord.c +++ b/src/pc/network/discord/discord.c @@ -122,6 +122,8 @@ static void ns_discord_shutdown(void) { struct NetworkSystem gNetworkSystemDiscord = { .initialize = ns_discord_initialize, + .save_id = ns_discord_save_id, + .clear_id = ns_discord_clear_id, .update = ns_discord_update, .send = ns_discord_network_send, .shutdown = ns_discord_shutdown, diff --git a/src/pc/network/discord/discord_network.c b/src/pc/network/discord/discord_network.c index a2eead7fb..8a7a8afbb 100644 --- a/src/pc/network/discord/discord_network.c +++ b/src/pc/network/discord/discord_network.c @@ -2,33 +2,60 @@ #include "lobby.h" #include "pc/debuglog.h" -int ns_discord_network_send(u8* data, u16 dataLength) { +int64_t gNetworkUserIds[MAX_PLAYERS] = { 0 }; + +u8 discord_user_id_to_local_index(int64_t userId) { + for (int i = 1; i < MAX_PLAYERS; i++) { + if (gNetworkPlayers[i].connected && gNetworkUserIds[i] == userId) { + return i; + } + } + return UNKNOWN_LOCAL_INDEX; +} + +int ns_discord_network_send(u8 localIndex, u8* data, u16 dataLength) { if (!gDiscordInitialized) { return 1; } if (gCurLobbyId == 0) { return 2; } - int32_t memberCount = 0; - DISCORD_REQUIRE(app.lobbies->member_count(app.lobbies, gCurLobbyId, &memberCount)); - if (memberCount <= 1) { return 3; } - - for (int i = 0; i < memberCount; i++) { - DiscordUserId userId; - DISCORD_REQUIRE(app.lobbies->get_member_user_id(app.lobbies, gCurLobbyId, i, &userId)); - if (userId == app.userId) { continue; } - DISCORD_REQUIRE(app.lobbies->send_network_message(app.lobbies, gCurLobbyId, userId, 0, data, dataLength)); - } + DISCORD_REQUIRE(app.lobbies->send_network_message(app.lobbies, gCurLobbyId, gNetworkUserIds[localIndex], 0, data, dataLength)); return 0; } void discord_network_on_message(UNUSED void* eventData, int64_t lobbyId, int64_t userId, uint8_t channelId, uint8_t* data, uint32_t dataLength) { - network_receive((u8*)data, (u16)dataLength); + gNetworkUserIds[0] = userId; + + u8 localIndex = UNKNOWN_LOCAL_INDEX; + for (int i = 1; i < MAX_PLAYERS; i++) { + if (gNetworkUserIds[i] == userId) { + localIndex = i; + break; + } + } + + network_receive(localIndex, (u8*)data, (u16)dataLength); } void discord_network_flush(void) { app.lobbies->flush_network(app.lobbies); } +void ns_discord_save_id(u8 localId) { + assert(localId > 0); + assert(localId < MAX_PLAYERS); + gNetworkUserIds[localId] = gNetworkUserIds[0]; + LOG_INFO("saved user id %d == %lld", localId, gNetworkUserIds[localId]); +} + +void ns_discord_clear_id(u8 localId) { + assert(localId > 0); + assert(localId < MAX_PLAYERS); + gNetworkUserIds[localId] = 0; + LOG_INFO("cleared user id %d == %lld", localId, gNetworkUserIds[localId]); +} + void discord_network_init(int64_t lobbyId) { DISCORD_REQUIRE(app.lobbies->connect_network(app.lobbies, lobbyId)); DISCORD_REQUIRE(app.lobbies->open_network_channel(app.lobbies, lobbyId, 0, false)); + LOG_INFO("network initialized"); } void discord_network_shutdown(void) { diff --git a/src/pc/network/discord/discord_network.h b/src/pc/network/discord/discord_network.h index 582e1195e..e09660f88 100644 --- a/src/pc/network/discord/discord_network.h +++ b/src/pc/network/discord/discord_network.h @@ -2,9 +2,14 @@ #define DISCORD_NETWORK_H #include "discord.h" -int ns_discord_network_send(u8* data, u16 dataLength); +extern int64_t gNetworkUserIds[MAX_PLAYERS]; + +u8 discord_user_id_to_local_index(int64_t userId); +int ns_discord_network_send(u8 localIndex, u8* data, u16 dataLength); void discord_network_on_message(UNUSED void* eventData, int64_t lobbyId, int64_t userId, uint8_t channelId, uint8_t* data, uint32_t dataLength); void discord_network_flush(void); +void ns_discord_save_id(u8 localId); +void ns_discord_clear_id(u8 localId); void discord_network_init(int64_t lobbyId); void discord_network_shutdown(void); diff --git a/src/pc/network/discord/lobby.c b/src/pc/network/discord/lobby.c index e2949a115..d556dad72 100644 --- a/src/pc/network/discord/lobby.c +++ b/src/pc/network/discord/lobby.c @@ -7,13 +7,13 @@ static bool isHosting = false; DiscordLobbyId gCurLobbyId = 0; static void on_lobby_create_callback(UNUSED void* data, enum EDiscordResult result, struct DiscordLobby* lobby) { - LOG_INFO("> on_lobby_update returned %d\n", (int)result); - LOG_INFO("Lobby id: %lld\n", lobby->id); - LOG_INFO("Lobby type: %u\n", lobby->type); - LOG_INFO("Lobby owner id: %lld\n", lobby->owner_id); - LOG_INFO("Lobby secret: %s\n", lobby->secret); - LOG_INFO("Lobby capacity: %u\n", lobby->capacity); - LOG_INFO("Lobby locked: %d\n", lobby->locked); + LOG_INFO("> on_lobby_update returned %d", (int)result); + LOG_INFO("Lobby id: %lld", lobby->id); + LOG_INFO("Lobby type: %u", lobby->type); + LOG_INFO("Lobby owner id: %lld", lobby->owner_id); + LOG_INFO("Lobby secret: %s", lobby->secret); + LOG_INFO("Lobby capacity: %u", lobby->capacity); + LOG_INFO("Lobby locked: %d", lobby->locked); gCurActivity.type = DiscordActivityType_Playing; snprintf(gCurActivity.party.id, 128, "%lld", lobby->id); @@ -47,6 +47,10 @@ static void on_member_update(UNUSED void* data, int64_t lobbyId, int64_t userId) static void on_member_disconnect(UNUSED void* data, int64_t lobbyId, int64_t userId) { LOG_INFO("> on_member_disconnect lobby: %lld, user: %lld", lobbyId, userId); + u8 localIndex = discord_user_id_to_local_index(userId); + if (localIndex != UNKNOWN_LOCAL_INDEX && gNetworkPlayers[localIndex].connected) { + network_player_disconnected(gNetworkPlayers[localIndex].globalIndex); + } gCurActivity.party.size.current_size--; discord_activity_update(isHosting); } diff --git a/src/pc/network/network.c b/src/pc/network/network.c index d2cef0eb9..f229089d9 100644 --- a/src/pc/network/network.c +++ b/src/pc/network/network.c @@ -16,6 +16,7 @@ struct NetworkSystem* gNetworkSystem = &gNetworkSystemDiscord; #define LOADING_LEVEL_THRESHOLD 10 u8 networkLoadingLevel = 0; bool gNetworkLevelLoaded = false; +clock_t gLastNetworkSend = 0; struct ServerSettings gServerSettings = { .playerInteractions = PLAYER_INTERACTIONS_SOLID, @@ -53,6 +54,12 @@ bool network_init(enum NetworkType inNetworkType) { // set network type gNetworkType = inNetworkType; + if (gNetworkType == NT_SERVER) { + network_player_connected(NPT_LOCAL, 0); + extern u8* gOverrideEeprom; + gOverrideEeprom = NULL; + } + LOG_INFO("initialized"); return true; @@ -71,12 +78,14 @@ void network_on_loaded_level(void) { } } -void network_send(struct Packet* p) { +void network_send_to(u8 localIndex, struct Packet* p) { // sanity checks if (gNetworkType == NT_NONE) { return; } if (p->error) { LOG_ERROR("packet error!"); return; } if (gNetworkSystem == NULL) { LOG_ERROR("no network system attached"); return; } + p->localIndex = localIndex; + // remember reliable packets network_remember_reliable(p); @@ -85,20 +94,35 @@ void network_send(struct Packet* p) { memcpy(&p->buffer[p->dataLength], &hash, sizeof(u32)); // send - int rc = gNetworkSystem->send(p->buffer, p->cursor + sizeof(u32)); + int rc = gNetworkSystem->send(localIndex, p->buffer, p->cursor + sizeof(u32)); if (rc != NO_ERROR) { return; } p->sent = true; + + gLastNetworkSend = clock(); } -void network_receive(u8* data, u16 dataLength) { +void network_send(struct Packet* p) { + for (int i = 1; i < MAX_PLAYERS; i++) { + if (!gNetworkPlayers[i].connected) { continue; } + p->localIndex = i; + network_send_to(i, p); + } +} + +void network_receive(u8 localIndex, u8* data, u16 dataLength) { // receive packet struct Packet p = { + .localIndex = localIndex, .cursor = 3, .buffer = { 0 }, .dataLength = dataLength, }; memcpy(p.buffer, data, dataLength); + if (localIndex != UNKNOWN_LOCAL_INDEX && localIndex != 0) { + gNetworkPlayers[localIndex].lastReceived = clock(); + } + // subtract and check hash p.dataLength -= sizeof(u32); if (!packet_check_hash(&p)) { @@ -121,7 +145,8 @@ void network_update(void) { } // send out update packets - if (gNetworkType != NT_NONE) { + if (gNetworkType != NT_NONE && network_player_any_connected()) { + network_player_update(); if (sCurrPlayMode == PLAY_MODE_NORMAL || sCurrPlayMode == PLAY_MODE_PAUSED) { network_update_player(); network_update_objects(); @@ -141,8 +166,10 @@ void network_update(void) { void network_shutdown(void) { if (gNetworkType == NT_NONE) { return; } - if (gNetworkSystem == NULL) { LOG_ERROR("no network system attached"); return; } + + if (gNetworkPlayerLocal != NULL) { network_send_leaving(gNetworkPlayerLocal->globalIndex); } + network_player_shutdown(); gNetworkSystem->shutdown(); gNetworkType = NT_NONE; diff --git a/src/pc/network/network.h b/src/pc/network/network.h index 5e2758538..151b3caf5 100644 --- a/src/pc/network/network.h +++ b/src/pc/network/network.h @@ -5,6 +5,7 @@ #include #include #include +#include "network_player.h" #include "packets/packet.h" #include "../cliopts.h" @@ -28,8 +29,10 @@ enum NetworkSystemType { struct NetworkSystem { bool (*initialize)(enum NetworkType); + void (*save_id)(u8 localIndex); + void (*clear_id)(u8 localIndex); void (*update)(void); - int (*send)(u8* data, u16 dataLength); + int (*send)(u8 localIndex, u8* data, u16 dataLength); void (*shutdown)(void); }; @@ -66,18 +69,21 @@ struct ServerSettings { }; // Networking-specific externs -extern bool gNetworkLevelLoaded; +extern struct NetworkSystem* gNetworkSystem; extern enum NetworkType gNetworkType; +extern bool gNetworkLevelLoaded; extern struct SyncObject gSyncObjects[]; extern struct ServerSettings gServerSettings; +extern clock_t gLastNetworkSend; // network.c void network_set_system(enum NetworkSystemType nsType); bool network_init(enum NetworkType inNetworkType); void network_on_init_level(void); void network_on_loaded_level(void); +void network_send_to(u8 localIndex, struct Packet* p); void network_send(struct Packet* p); -void network_receive(u8* data, u16 dataLength); +void network_receive(u8 localIndex, u8* data, u16 dataLength); void network_update(void); void network_shutdown(void); diff --git a/src/pc/network/network_player.c b/src/pc/network/network_player.c new file mode 100644 index 000000000..c7ea8bec0 --- /dev/null +++ b/src/pc/network/network_player.c @@ -0,0 +1,131 @@ +#include +#include "network_player.h" +#include "game/chat.h" +#include "pc/debuglog.h" + +struct NetworkPlayer gNetworkPlayers[MAX_PLAYERS] = { 0 }; +struct NetworkPlayer* gNetworkPlayerLocal = NULL; +struct NetworkPlayer* gNetworkPlayerServer = NULL; + +bool network_player_any_connected(void) { + for (int i = 1; i < MAX_PLAYERS; i++) { + if (gNetworkPlayers[i].connected) { return true; } + } + return false; +} + +void network_player_update(void) { + float elapsed = (clock() - gLastNetworkSend) / (float)CLOCKS_PER_SEC; + if (elapsed > NETWORK_PLAYER_TIMEOUT / 3.0f) { + network_send_keep_alive(); + } + + if (gNetworkType == NT_SERVER) { + for (int i = 1; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected) { continue; } + float elapsed = (clock() - np->lastReceived) / (float)CLOCKS_PER_SEC; + if (elapsed > NETWORK_PLAYER_TIMEOUT) { + network_player_disconnected(i); + } + } + } else if (gNetworkType == NT_CLIENT) { + bool connectionAlive = false; + for (int i = 1; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected) { continue; } + float elapsed = (clock() - np->lastReceived) / (float)CLOCKS_PER_SEC; + if (elapsed <= NETWORK_PLAYER_TIMEOUT * 1.5f) { + connectionAlive = true; + break; + } + } + if (!connectionAlive) { + network_shutdown(); + } + } +} + +u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex) { + if (type == NPT_LOCAL) { + gNetworkPlayers[0].connected = true; + gNetworkPlayers[0].type = type; + gNetworkPlayers[0].localIndex = 0; + gNetworkPlayers[0].globalIndex = globalIndex; + gNetworkPlayerLocal = &gNetworkPlayers[0]; + return 0; + } + + if (globalIndex != UNKNOWN_GLOBAL_INDEX) { + for (int i = 1; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected) { continue; } + if (np->globalIndex != globalIndex) { continue; } + np->localIndex = i; + np->lastReceived = clock(); + gNetworkSystem->save_id(i); + LOG_ERROR("player connected, reusing local %d, global %d, duplicate event?", i, globalIndex); + return i; + } + } + + for (int i = 1; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (np->connected) { continue; } + np->connected = true; + np->localIndex = i; + np->globalIndex = (gNetworkType == NT_SERVER) ? i : globalIndex; + np->type = type; + np->lastReceived = clock(); + gNetworkSystem->save_id(i); + if (type == NPT_SERVER) { gNetworkPlayerServer = np; } + chat_add_message("player connected", CMT_SYSTEM); + LOG_INFO("player connected, local %d, global %d", i, globalIndex); + return i; + } + + LOG_ERROR("player connected, but unable to allocate!"); + return UNKNOWN_GLOBAL_INDEX; +} + +u8 network_player_disconnected(u8 globalIndex) { + if (globalIndex == 0) { + if (gNetworkType == NT_SERVER) { + LOG_ERROR("player disconnected, but it's local.. this shouldn't happen!"); + return UNKNOWN_GLOBAL_INDEX; + } else { + network_shutdown(); + } + } + + if (globalIndex == UNKNOWN_GLOBAL_INDEX) { + LOG_ERROR("player disconnected, but unknown global index!"); + return UNKNOWN_GLOBAL_INDEX; + } + + for (int i = 1; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected) { continue; } + if (np->globalIndex != globalIndex) { continue; } + if (gNetworkType == NT_SERVER) { network_send_leaving(np->globalIndex); } + np->connected = false; + gNetworkSystem->clear_id(i); + LOG_INFO("player disconnected, local %d, global %d", i, globalIndex); + chat_add_message("player disconnected", CMT_SYSTEM); + return i; + } + return UNKNOWN_GLOBAL_INDEX; +} + +void network_player_shutdown(void) { + gNetworkPlayerLocal = NULL; + gNetworkPlayerServer = NULL; + for (int i = 1; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* networkPlayer = &gNetworkPlayers[i]; + networkPlayer->connected = false; + gNetworkSystem->clear_id(i); + } + + chat_add_message("network shutdown", CMT_SYSTEM); + LOG_INFO("cleared all network players"); +} diff --git a/src/pc/network/network_player.h b/src/pc/network/network_player.h new file mode 100644 index 000000000..a2c1c0974 --- /dev/null +++ b/src/pc/network/network_player.h @@ -0,0 +1,36 @@ +#ifndef NETWORK_PLAYER_H +#define NETWORK_PLAYER_H + +#include +#include "network.h" + +#define UNKNOWN_LOCAL_INDEX ((u8)-1) +#define UNKNOWN_GLOBAL_INDEX ((u8)-1) +#define UNKNOWN_NETWORK_INDEX ((u64)-1) +#define NETWORK_PLAYER_TIMEOUT 10 + +enum NetworkPlayerType { + NPT_LOCAL, + NPT_SERVER, + NPT_CLIENT, +}; + +struct NetworkPlayer { + bool connected; + enum NetworkPlayerType type; + u8 localIndex; + u8 globalIndex; + clock_t lastReceived; +}; + +extern struct NetworkPlayer gNetworkPlayers[]; +extern struct NetworkPlayer* gNetworkPlayerLocal; +extern struct NetworkPlayer* gNetworkPlayerServer; + +bool network_player_any_connected(void); +void network_player_update(void); +u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex); +u8 network_player_disconnected(u8 globalIndex); +void network_player_shutdown(void); + +#endif diff --git a/src/pc/network/packets/packet.c b/src/pc/network/packets/packet.c index 2e1247efb..d243b6c37 100644 --- a/src/pc/network/packets/packet.c +++ b/src/pc/network/packets/packet.c @@ -3,7 +3,15 @@ #include "pc/debuglog.h" void packet_receive(struct Packet* p) { - switch ((u8)p->buffer[0]) { + u8 packetType = (u8)p->buffer[0]; + + // refuse packets from unknown players other than join request + if (gNetworkType == NT_SERVER && p->localIndex == UNKNOWN_LOCAL_INDEX && packetType != PACKET_JOIN_REQUEST) { + network_send_kick(EKT_CLOSE_CONNECTION); + return; + } + + switch (packetType) { case PACKET_ACK: network_receive_ack(p); break; case PACKET_PLAYER: network_receive_player(p); break; case PACKET_OBJECT: network_receive_object(p); break; @@ -19,6 +27,10 @@ void packet_receive(struct Packet* p) { case PACKET_JOIN_REQUEST: network_receive_join_request(p); break; case PACKET_JOIN: network_receive_join(p); break; case PACKET_CHAT: network_receive_chat(p); break; + case PACKET_KICK: network_receive_kick(p); break; + case PACKET_KEEP_ALIVE: network_receive_keep_alive(p); break; + case PACKET_LEAVING: network_receive_leaving(p); break; + /// case PACKET_CUSTOM: network_receive_custom(p); break; default: LOG_ERROR("received unknown packet: %d", p->buffer[0]); } diff --git a/src/pc/network/packets/packet.h b/src/pc/network/packets/packet.h index a777ccaf6..0efecdb81 100644 --- a/src/pc/network/packets/packet.h +++ b/src/pc/network/packets/packet.h @@ -25,10 +25,15 @@ enum PacketType { PACKET_JOIN_REQUEST, PACKET_JOIN, PACKET_CHAT, + PACKET_KICK, + PACKET_KEEP_ALIVE, + PACKET_LEAVING, + /// PACKET_CUSTOM = 255, }; struct Packet { + u8 localIndex; u16 dataLength; u16 cursor; bool error; @@ -38,6 +43,11 @@ struct Packet { u8 buffer[PACKET_LENGTH]; }; +enum KickReasonType { + EKT_CLOSE_CONNECTION, + EKT_FULL_PARTY, +}; + // packet.c void packet_receive(struct Packet* packet); @@ -107,8 +117,8 @@ void network_receive_reservation(struct Packet* p); // packet_join.c void network_send_join_request(void); -void network_receive_join_request(UNUSED struct Packet* p); -void network_send_join(void); +void network_receive_join_request(struct Packet* p); +void network_send_join(struct Packet* joinRequestPacket); void network_receive_join(struct Packet* p); // packet_custom.c @@ -120,4 +130,16 @@ void network_receive_custom(struct Packet* p); void network_send_chat(char* message); void network_receive_chat(struct Packet* p); +// packet_kick.c +void network_send_kick(enum KickReasonType kickReason); +void network_receive_kick(struct Packet* p); + +// packet_keep_alive.c +void network_send_keep_alive(void); +void network_receive_keep_alive(struct Packet* p); + +// packet_leaving.c +void network_send_leaving(u8 globalIndex); +void network_receive_leaving(struct Packet* p); + #endif diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c index 54c3757b6..69d7866c5 100644 --- a/src/pc/network/packets/packet_join.c +++ b/src/pc/network/packets/packet_join.c @@ -10,6 +10,7 @@ #include "src/menu/custom_menu.h" #include "src/pc/fs/fs.h" #include "PR/os_eeprom.h" +#include "pc/debuglog.h" #define HASH_LENGTH 8 extern u8* gOverrideEeprom; @@ -22,15 +23,17 @@ void network_send_join_request(void) { struct Packet p; packet_init(&p, PACKET_JOIN_REQUEST, true); - network_send(&p); + network_send_to(0, &p); + LOG_INFO("sending join request"); } -void network_receive_join_request(UNUSED struct Packet* p) { +void network_receive_join_request(struct Packet* p) { assert(gNetworkType == NT_SERVER); - network_send_join(); + LOG_INFO("received join request"); + network_send_join(p); } -void network_send_join(void) { +void network_send_join(struct Packet* joinRequestPacket) { assert(gNetworkType == NT_SERVER); fs_file_t* fp = fs_open(SAVE_FILENAME); @@ -39,38 +42,60 @@ void network_send_join(void) { fs_close(fp); } + // do connection event + joinRequestPacket->localIndex = network_player_connected(NPT_CLIENT, joinRequestPacket->localIndex); + if (joinRequestPacket->localIndex == UNKNOWN_LOCAL_INDEX) { + network_send_kick(EKT_FULL_PARTY); + return; + } + char hash[HASH_LENGTH] = GIT_HASH; struct Packet p; packet_init(&p, PACKET_JOIN, true); packet_write(&p, &hash, sizeof(u8) * HASH_LENGTH); + packet_write(&p, &joinRequestPacket->localIndex, sizeof(u8)); packet_write(&p, &gCurrSaveFileNum, sizeof(s16)); packet_write(&p, &gServerSettings.playerInteractions, sizeof(u8)); packet_write(&p, &gServerSettings.playerKnockbackStrength, sizeof(u8)); packet_write(&p, &gServerSettings.stayInLevelAfterStar, sizeof(u8)); packet_write(&p, eeprom, sizeof(u8) * 512); - network_send(&p); + network_send_to(joinRequestPacket->localIndex , &p); + + LOG_INFO("sending join packet"); } void network_receive_join(struct Packet* p) { assert(gNetworkType == NT_CLIENT); + LOG_INFO("received join packet"); gOverrideEeprom = eeprom; char hash[HASH_LENGTH] = GIT_HASH; char remoteHash[HASH_LENGTH] = { 0 }; + u8 myGlobalIndex = UNKNOWN_GLOBAL_INDEX; - // find all reserved objects + if (network_player_any_connected()) { + LOG_ERROR("Received join packet, but already in-game!"); + return; + } + + // verify version packet_read(p, &remoteHash, sizeof(u8) * HASH_LENGTH); + if (memcmp(hash, remoteHash, HASH_LENGTH) != 0) { + custom_menu_version_mismatch(); + return; + } + + packet_read(p, &myGlobalIndex, sizeof(u8)); packet_read(p, &gCurrSaveFileNum, sizeof(s16)); packet_read(p, &gServerSettings.playerInteractions, sizeof(u8)); packet_read(p, &gServerSettings.playerKnockbackStrength, sizeof(u8)); packet_read(p, &gServerSettings.stayInLevelAfterStar, sizeof(u8)); packet_read(p, eeprom, sizeof(u8) * 512); + network_player_connected(NPT_SERVER, 0); + network_player_connected(NPT_LOCAL, myGlobalIndex); + save_file_load_all(TRUE); - if (memcmp(hash, remoteHash, HASH_LENGTH) != 0) { - custom_menu_version_mismatch(); - return; - } custom_menu_goto_game(gCurrSaveFileNum); } diff --git a/src/pc/network/packets/packet_keep_alive.c b/src/pc/network/packets/packet_keep_alive.c new file mode 100644 index 000000000..862edc202 --- /dev/null +++ b/src/pc/network/packets/packet_keep_alive.c @@ -0,0 +1,15 @@ +#include +#include "../network.h" +#include "pc/debuglog.h" + +void network_send_keep_alive(void) { + struct Packet p; + packet_init(&p, PACKET_KEEP_ALIVE, FALSE); + network_send(&p); + gLastNetworkSend = clock(); + LOG_INFO("sending keep alive"); +} + +void network_receive_keep_alive(struct Packet* p) { + LOG_INFO("received keep alive"); +} diff --git a/src/pc/network/packets/packet_kick.c b/src/pc/network/packets/packet_kick.c new file mode 100644 index 000000000..706aa1d38 --- /dev/null +++ b/src/pc/network/packets/packet_kick.c @@ -0,0 +1,31 @@ +#include +#include "../network.h" +#include "menu/custom_menu_system.h" +#include "pc/debuglog.h" + +void network_send_kick(enum KickReasonType kickReason) { + struct Packet p; + packet_init(&p, PACKET_KICK, FALSE); + packet_write(&p, &kickReason, sizeof(enum KickReasonType)); + network_send_to(0, &p); +} + +void network_receive_kick(struct Packet* p) { + if (gNetworkType != NT_CLIENT) { + LOG_ERROR("Kicking non-client... refuse!"); + return; + } + + if (network_player_any_connected() && gNetworkPlayers[p->localIndex].type != NPT_SERVER) { + LOG_ERROR("Kick came from non-server... refuse!"); + return; + } + + enum KickReasonType kickReason; + packet_read(p, &kickReason, sizeof(enum KickReasonType)); + switch (kickReason) { + case EKT_FULL_PARTY: custom_menu_error("The party is full."); break; + default: custom_menu_error("Host has closed the connection."); break; + } + network_shutdown(); +} diff --git a/src/pc/network/packets/packet_leaving.c b/src/pc/network/packets/packet_leaving.c new file mode 100644 index 000000000..1b2c7bcd3 --- /dev/null +++ b/src/pc/network/packets/packet_leaving.c @@ -0,0 +1,43 @@ +#include +#include "../network.h" +#include "menu/custom_menu_system.h" +#include "pc/debuglog.h" + +void network_send_leaving(u8 globalIndex) { + if (gNetworkPlayerLocal == NULL) { + LOG_ERROR("Local network player not initialized."); + return; + } + + if (gNetworkType != NT_SERVER) { + if (gNetworkPlayerServer == NULL) { + LOG_ERROR("Server network player not initialized."); + return; + } + + globalIndex = gNetworkPlayerLocal->globalIndex; + } + + struct Packet p; + packet_init(&p, PACKET_LEAVING, TRUE); + packet_write(&p, &globalIndex, sizeof(u8)); + if (gNetworkType == NT_SERVER) { + network_send(&p); + } else { + network_send_to(gNetworkPlayerServer->localIndex, &p); + } + LOG_INFO("Sending leaving event for %d", globalIndex); +} + +void network_receive_leaving(struct Packet* p) { + if (gNetworkType != NT_SERVER && network_player_any_connected() && gNetworkPlayers[p->localIndex].type != NPT_SERVER) { + LOG_ERROR("Leaving came from non-server... refuse!"); + return; + } + + u8 globalIndex = 0; + packet_read(p, &globalIndex, sizeof(u8)); + + LOG_INFO("Received leaving event for %d", globalIndex); + network_player_disconnected(globalIndex); +} diff --git a/src/pc/network/packets/packet_reliable.c b/src/pc/network/packets/packet_reliable.c index 285286a9b..2daf9d754 100644 --- a/src/pc/network/packets/packet_reliable.c +++ b/src/pc/network/packets/packet_reliable.c @@ -2,6 +2,8 @@ #include "../network.h" #include "pc/debuglog.h" +// two-player hack: the localIndex for resending packets can be 0... this means reply to last person received from. THIS WILL NOT WORK with more than two players + #define RELIABLE_RESEND_RATE 0.10f #define MAX_RESEND_ATTEMPTS 20 @@ -41,7 +43,7 @@ void network_send_ack(struct Packet* p) { struct Packet ack = { 0 }; packet_init(&ack, PACKET_ACK, false); packet_write(&ack, &seqId, sizeof(u16)); - network_send(&ack); + network_send_to(0, &ack); } void network_receive_ack(struct Packet* p) { @@ -94,7 +96,7 @@ void network_update_reliable(void) { float maxElapsed = (node->sendAttempts * node->sendAttempts * RELIABLE_RESEND_RATE) / ((float)MAX_RESEND_ATTEMPTS); if (elapsed > maxElapsed) { // resend - network_send(&node->p); + network_send_to(node->p.localIndex, &node->p); node->lastSend = clock(); node->sendAttempts++; if (node->sendAttempts >= MAX_RESEND_ATTEMPTS) { diff --git a/src/pc/network/socket/socket.c b/src/pc/network/socket/socket.c index d815010fc..a8d29b0a6 100644 --- a/src/pc/network/socket/socket.c +++ b/src/pc/network/socket/socket.c @@ -5,7 +5,7 @@ #include "menu/custom_menu.h" static SOCKET curSocket = INVALID_SOCKET; -struct sockaddr_in txAddr = { 0 }; +static struct sockaddr_in addr[MAX_PLAYERS] = { 0 }; static int socket_bind(SOCKET socket, unsigned int port) { struct sockaddr_in rxAddr; @@ -31,11 +31,19 @@ static int socket_send(SOCKET socket, struct sockaddr_in* addr, u8* buffer, u16 return rc; } -static int socket_receive(SOCKET socket, struct sockaddr_in* rxAddr, u8* buffer, u16 bufferLength, u16* receiveLength) { +static int socket_receive(SOCKET socket, struct sockaddr_in* rxAddr, u8* buffer, u16 bufferLength, u16* receiveLength, u8* localIndex) { *receiveLength = 0; int rxAddrSize = sizeof(struct sockaddr_in); int rc = recvfrom(socket, (char*)buffer, bufferLength, 0, (struct sockaddr*)rxAddr, &rxAddrSize); + + for (int i = 1; i < MAX_PLAYERS; i++) { + if (memcmp(rxAddr, &addr[i], sizeof(struct sockaddr_in)) == 0) { + *localIndex = i; + break; + } + } + if (rc == SOCKET_ERROR) { int error = SOCKET_LAST_ERROR; if (error != SOCKET_EWOULDBLOCK && error != SOCKET_ECONNRESET) { @@ -49,6 +57,7 @@ static int socket_receive(SOCKET socket, struct sockaddr_in* rxAddr, u8* buffer, } static bool ns_socket_initialize(enum NetworkType networkType) { + // sanity check port unsigned int port = (networkType == NT_CLIENT) ? configJoinPort : configHostPort; if (port == 0) { port = DEFAULT_PORT; } @@ -65,9 +74,9 @@ static bool ns_socket_initialize(enum NetworkType networkType) { LOG_INFO("bound to port %u", port); } else { // save the port to send to - txAddr.sin_family = AF_INET; - txAddr.sin_port = htons(port); - txAddr.sin_addr.s_addr = inet_addr(configJoinIp); + addr[0].sin_family = AF_INET; + addr[0].sin_port = htons(port); + addr[0].sin_addr.s_addr = inet_addr(configJoinIp); LOG_INFO("connecting to %s %u", configJoinIp, port); } @@ -87,20 +96,39 @@ static bool ns_socket_initialize(enum NetworkType networkType) { return true; } +static void ns_socket_save_id(u8 localId) { + assert(localId > 0); + assert(localId < MAX_PLAYERS); + addr[localId] = addr[0]; + LOG_INFO("saved addr for id %d", localId); +} + +static void ns_socket_clear_id(u8 localId) { + assert(localId > 0); + assert(localId < MAX_PLAYERS); + memset(&addr[localId], 0, sizeof(struct sockaddr_in)); + LOG_INFO("cleared addr for id %d", localId); +} + static void ns_socket_update(void) { if (gNetworkType == NT_NONE) { return; } do { // receive packet u8 data[PACKET_LENGTH]; u16 dataLength = 0; - int rc = socket_receive(curSocket, &txAddr, data, PACKET_LENGTH, &dataLength); + u8 localIndex = UNKNOWN_LOCAL_INDEX; + int rc = socket_receive(curSocket, &addr[0], data, PACKET_LENGTH, &dataLength, &localIndex); if (rc != NO_ERROR) { break; } - network_receive(data, dataLength); + network_receive(localIndex, data, dataLength); } while (true); } -static int ns_socket_send(u8* data, u16 dataLength) { - return socket_send(curSocket, &txAddr, data, dataLength); +static int ns_socket_send(u8 localIndex, u8* data, u16 dataLength) { + if (localIndex != 0) { + if (gNetworkType == NT_SERVER && gNetworkPlayers[localIndex].type != NPT_CLIENT) { return SOCKET_ERROR; } + if (gNetworkType == NT_CLIENT && gNetworkPlayers[localIndex].type != NPT_SERVER) { return SOCKET_ERROR; } + } + return socket_send(curSocket, &addr[localIndex], data, dataLength); } static void ns_socket_shutdown(void) { @@ -111,6 +139,8 @@ static void ns_socket_shutdown(void) { struct NetworkSystem gNetworkSystemSocket = { .initialize = ns_socket_initialize, + .save_id = ns_socket_save_id, + .clear_id = ns_socket_clear_id, .update = ns_socket_update, .send = ns_socket_send, .shutdown = ns_socket_shutdown, diff --git a/src/pc/network/socket/socket_windows.h b/src/pc/network/socket/socket_windows.h index 50dad0cfe..09f4539d6 100644 --- a/src/pc/network/socket/socket_windows.h +++ b/src/pc/network/socket/socket_windows.h @@ -7,5 +7,4 @@ #define SOCKET_LAST_ERROR WSAGetLastError() #define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK #define SOCKET_ECONNRESET WSAECONNRESET - #endif