diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj index 4a31efa78..0dcbb0eeb 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj +++ b/build-windows-visual-studio/sm64ex.vcxproj @@ -442,6 +442,7 @@ + @@ -526,6 +527,7 @@ + @@ -926,6 +928,7 @@ + @@ -986,6 +989,7 @@ + diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters index c88a8c7c9..d28c12a44 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj.filters +++ b/build-windows-visual-studio/sm64ex.vcxproj.filters @@ -4884,6 +4884,12 @@ Source Files\src\pc + + Source Files\src\pc + + + Source Files\src\pc\network + @@ -6043,5 +6049,11 @@ Source Files\src\pc\network + + Source Files\src\pc + + + Source Files\src\pc\network + \ No newline at end of file diff --git a/src/pc/chat_commands.c b/src/pc/chat_commands.c new file mode 100644 index 000000000..9e8032087 --- /dev/null +++ b/src/pc/chat_commands.c @@ -0,0 +1,189 @@ +#include "pc/network/network.h" +#include "pc/lua/smlua_hooks.h" +#include "pc/djui/djui_chat_message.h" +#include "chat_commands.h" +#include "pc/network/ban_list.h" +#include "pc/debuglog.h" + +enum ChatConfirmCommand { + CCC_NONE, + CCC_KICK, + CCC_BAN, + CCC_PERMBAN, +}; + +static enum ChatConfirmCommand sConfirming = CCC_NONE; +static u8 sConfirmPlayerIndex = 0; + +static struct NetworkPlayer* chat_get_network_player(char* name) { + // check for id + for (int i = 0; i < MAX_PLAYERS; i++) { + if (!gNetworkPlayers[i].connected) { continue; } + char id[16] = { 0 }; + snprintf(id, 16, "%d", i); + if (strcmp(id, name) == 0) { + return &gNetworkPlayers[i]; + } + } + + // check for name + for (int i = 0; i < MAX_PLAYERS; i++) { + if (!gNetworkPlayers[i].connected) { continue; } + if (strcmp(gNetworkPlayers[i].name, name) == 0) { + return &gNetworkPlayers[i]; + } + } + return NULL; +} + +static bool str_starts_with(const char* pre, char* str) { + return strncmp(pre, str, strlen(pre)) == 0; +} + +bool exec_chat_command(char* command) { + enum ChatConfirmCommand ccc = sConfirming; + sConfirming = CCC_NONE; + + if (ccc != CCC_NONE && strcmp("/confirm", command) == 0) { + if (gNetworkType == NT_SERVER && ccc == CCC_KICK) { + struct NetworkPlayer* np = &gNetworkPlayers[sConfirmPlayerIndex]; + if (!np->connected) { return true; } + char message[256] = { 0 }; + snprintf(message, 256, "\\#fff982\\Kicking '%s%s\\#fff982\\'!", network_get_player_text_color_string(np->localIndex), np->name); + djui_chat_message_create(message); + network_send_kick(np->localIndex, EKT_KICKED); + network_player_disconnected(np->localIndex); + return true; + } + if (gNetworkType == NT_SERVER && ccc == CCC_BAN) { + struct NetworkPlayer* np = &gNetworkPlayers[sConfirmPlayerIndex]; + if (!np->connected) { return true; } + char message[256] = { 0 }; + snprintf(message, 256, "\\#fff982\\Banning '%s%s\\#fff982\\'!", network_get_player_text_color_string(np->localIndex), np->name); + djui_chat_message_create(message); + network_send_kick(np->localIndex, EKT_BANNED); + ban_list_add(gNetworkSystem->get_id_str(np->localIndex), false); + network_player_disconnected(np->localIndex); + return true; + } + if (gNetworkType == NT_SERVER && ccc == CCC_PERMBAN) { + struct NetworkPlayer* np = &gNetworkPlayers[sConfirmPlayerIndex]; + if (!np->connected) { return true; } + char message[256] = { 0 }; + snprintf(message, 256, "\\#fff982\\Permanently banning '%s%s\\#fff982\\'!", network_get_player_text_color_string(np->localIndex), np->name); + djui_chat_message_create(message); + network_send_kick(np->localIndex, EKT_BANNED); + ban_list_add(gNetworkSystem->get_id_str(np->localIndex), true); + network_player_disconnected(np->localIndex); + return true; + } + } + + if (strcmp("/players", command) == 0) { + char message[600] = { 0 }; + char line[128] = { 0 }; + strncat(message, "\\#fff982\\Players:\n", 599); + for (int i = 0; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected) { continue; } + snprintf(line, 128, "\\#82f9ff\\%u \\#fff982\\- %s%s\n", np->globalIndex, network_get_player_text_color_string(np->localIndex), np->name); + strncat(message, line, 599); + } + djui_chat_message_create(message); + return true; + } + + if (str_starts_with("/kick ", command)) { + if (gNetworkType != NT_SERVER) { + djui_chat_message_create("Only the server can use this command."); + return true; + } + + struct NetworkPlayer* np = chat_get_network_player(&command[6]); + if (np == NULL) { + djui_chat_message_create("Could not find player."); + return true; + } + + if (np->localIndex == 0) { + djui_chat_message_create("Can not kick yourself."); + return true; + } + + char message[256] = { 0 }; + snprintf(message, 256, "\\#fff982\\Are you sure you want to kick '%s%s\\#fff982\\'?\nType '\\#a0ffa0\\/confirm\\#fff982\\' to kick.", network_get_player_text_color_string(np->localIndex), np->name); + djui_chat_message_create(message); + + sConfirming = CCC_KICK; + sConfirmPlayerIndex = np->localIndex; + + return true; + } + + if (str_starts_with("/ban ", command)) { + if (gNetworkType != NT_SERVER) { + djui_chat_message_create("Only the server can use this command."); + return true; + } + + struct NetworkPlayer* np = chat_get_network_player(&command[5]); + if (np == NULL) { + djui_chat_message_create("Could not find player."); + return true; + } + + if (np->localIndex == 0) { + djui_chat_message_create("Can not ban yourself."); + return true; + } + + char message[256] = { 0 }; + snprintf(message, 256, "\\#fff982\\Are you sure you want to ban '%s%s\\#fff982\\'?\nType '\\#a0ffa0\\/confirm\\#fff982\\' to ban.", network_get_player_text_color_string(np->localIndex), np->name); + djui_chat_message_create(message); + + sConfirming = CCC_BAN; + sConfirmPlayerIndex = np->localIndex; + + return true; + } + + if (str_starts_with("/permban ", command)) { + if (gNetworkType != NT_SERVER) { + djui_chat_message_create("Only the server can use this command."); + return true; + } + + struct NetworkPlayer* np = chat_get_network_player(&command[9]); + if (np == NULL) { + djui_chat_message_create("Could not find player."); + return true; + } + + if (np->localIndex == 0) { + djui_chat_message_create("Can not permanently ban yourself."); + return true; + } + + char message[256] = { 0 }; + snprintf(message, 256, "\\#fff982\\Are you sure you want to permanently ban '%s%s\\#fff982\\'?\nType '\\#a0ffa0\\/confirm\\#fff982\\' to permanently ban.", network_get_player_text_color_string(np->localIndex), np->name); + djui_chat_message_create(message); + + sConfirming = CCC_PERMBAN; + sConfirmPlayerIndex = np->localIndex; + + return true; + } + + return smlua_call_chat_command_hook(command); +} + +void display_chat_commands(void) { + djui_chat_message_create("/players - List all players and their IDs"); + if (gNetworkType == NT_SERVER) { + djui_chat_message_create("/kick [NAME|ID] - Kick this player from the current game"); + djui_chat_message_create("/ban [NAME|ID] - Ban this player from the current game"); + djui_chat_message_create("/permban [NAME|ID] - Ban this player from any game you host"); + } + if (sConfirming != CCC_NONE) { djui_chat_message_create("/confirm"); } + smlua_display_chat_commands(); +} \ No newline at end of file diff --git a/src/pc/chat_commands.h b/src/pc/chat_commands.h new file mode 100644 index 000000000..9614dbed8 --- /dev/null +++ b/src/pc/chat_commands.h @@ -0,0 +1,7 @@ +#ifndef CHAT_COMMANDS_H +#define CHAT_COMMANDS_H + +bool exec_chat_command(char* command); +void display_chat_commands(void); + +#endif \ No newline at end of file diff --git a/src/pc/configfile.c b/src/pc/configfile.c index d02e5947d..9334dab7b 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -14,6 +14,7 @@ #include "controller/controller_api.h" #include "fs/fs.h" #include "pc/mod_list.h" +#include "pc/network/ban_list.h" #define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0])) @@ -317,6 +318,12 @@ void configfile_load(const char *filename) { continue; } + // ban list + if (!strcmp(tokens[0], "ban:")) { + ban_list_add(tokens[1], true); + continue; + } + for (unsigned int i = 0; i < ARRAY_LEN(options); i++) { if (strcmp(tokens[0], options[i].name) == 0) { option = &options[i]; @@ -414,5 +421,13 @@ void configfile_save(const char *filename) { fprintf(file, "%s %s\n", "enable-mod:", entry->name); } + // save ban list + for (unsigned int i = 0; i < gBanCount; i++) { + if (gBanAddresses == NULL) { break; } + if (gBanAddresses[i] == NULL) { continue; } + if (!gBanPerm[i]) { continue; } + fprintf(file, "%s %s\n", "ban:", gBanAddresses[i]); + } + fclose(file); } diff --git a/src/pc/djui/djui_chat_box.c b/src/pc/djui/djui_chat_box.c index 9dee5b94a..9793a7eaf 100644 --- a/src/pc/djui/djui_chat_box.c +++ b/src/pc/djui/djui_chat_box.c @@ -2,6 +2,7 @@ #include #include "pc/network/network.h" #include "pc/lua/smlua_hooks.h" +#include "pc/chat_commands.h" #include "djui.h" struct DjuiChatBox* gDjuiChatBox = NULL; @@ -33,9 +34,9 @@ static void djui_chat_box_input_enter(struct DjuiInputbox* chatInput) { if (strlen(chatInput->buffer) != 0) { if (chatInput->buffer[0] == '/') { - if (!smlua_call_chat_command_hook(chatInput->buffer)) { + if (!exec_chat_command(chatInput->buffer)) { djui_chat_message_create("Unrecognized chat command."); - smlua_display_chat_commands(); + display_chat_commands(); } } else { djui_chat_message_create_from(gNetworkPlayerLocal->globalIndex, chatInput->buffer); diff --git a/src/pc/network/ban_list.c b/src/pc/network/ban_list.c new file mode 100644 index 000000000..c8f30c4ed --- /dev/null +++ b/src/pc/network/ban_list.c @@ -0,0 +1,45 @@ +#include +#include +#include "PR/ultratypes.h" +#include "ban_list.h" +#include "pc/debuglog.h" + +char** gBanAddresses = NULL; +bool* gBanPerm = NULL; +u16 gBanCount = 0; + +void ban_list_add(char* address, bool perm) { + u16 index = gBanCount++; + if (gBanAddresses == NULL) { + gBanAddresses = malloc(sizeof(char*) * gBanCount); + gBanPerm = malloc(sizeof(bool) * gBanCount); + } else { + gBanAddresses = realloc(gBanAddresses, sizeof(char*) * gBanCount); + gBanPerm = realloc(gBanPerm, sizeof(bool) * gBanCount); + } + if (gBanAddresses == NULL) { + LOG_ERROR("Failed to allocate gBanAddresses"); + return; + } + if (gBanPerm == NULL) { + LOG_ERROR("Failed to allocate gBanPerm"); + return; + } + gBanAddresses[index] = strdup(address); + gBanPerm[index] = perm; +} + +bool ban_list_contains(char* address) { + if (gBanAddresses == NULL || address == NULL) { + return false; + } + + for (int i = 0; i < gBanCount; i++) { + if (gBanAddresses[i] == NULL) { continue; } + if (strcmp(address, gBanAddresses[i]) == 0) { + return true; + } + } + + return false; +} diff --git a/src/pc/network/ban_list.h b/src/pc/network/ban_list.h new file mode 100644 index 000000000..22903e673 --- /dev/null +++ b/src/pc/network/ban_list.h @@ -0,0 +1,13 @@ +#ifndef BAN_LIST_H +#define BAN_LIST_H + +#include + +extern char** gBanAddresses; +extern bool* gBanPerm; +extern u16 gBanCount; + +void ban_list_add(char* address, bool perm); +bool ban_list_contains(char* address); + +#endif \ No newline at end of file diff --git a/src/pc/network/discord/discord.c b/src/pc/network/discord/discord.c index 5e35d6498..4f64c58fd 100644 --- a/src/pc/network/discord/discord.c +++ b/src/pc/network/discord/discord.c @@ -222,6 +222,7 @@ static void ns_discord_shutdown(void) { struct NetworkSystem gNetworkSystemDiscord = { .initialize = ns_discord_initialize, .get_id = ns_discord_get_id, + .get_id_str = ns_discord_get_id_str, .save_id = ns_discord_save_id, .clear_id = ns_discord_clear_id, .dup_addr = ns_discord_dup_addr, diff --git a/src/pc/network/discord/discord_network.c b/src/pc/network/discord/discord_network.c index 20c419c16..5f5f4bd3a 100644 --- a/src/pc/network/discord/discord_network.c +++ b/src/pc/network/discord/discord_network.c @@ -44,6 +44,13 @@ s64 ns_discord_get_id(u8 localId) { return gNetworkUserIds[localId]; } +char* ns_discord_get_id_str(u8 localId) { + if (localId == UNKNOWN_LOCAL_INDEX) { localId = 0; } + static char id_str[22] = { 0 }; + snprintf(id_str, 22, "%lld", gNetworkUserIds[localId]); + return id_str; +} + void ns_discord_save_id(u8 localId, s64 networkId) { assert(localId > 0); assert(localId < MAX_PLAYERS); diff --git a/src/pc/network/discord/discord_network.h b/src/pc/network/discord/discord_network.h index 43dbe21f3..a4acb13bc 100644 --- a/src/pc/network/discord/discord_network.h +++ b/src/pc/network/discord/discord_network.h @@ -9,6 +9,7 @@ int ns_discord_network_send(u8 localIndex, void* addr, 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); s64 ns_discord_get_id(u8 localId); +char* ns_discord_get_id_str(u8 localId); void ns_discord_save_id(u8 localId, s64 networkId); void ns_discord_clear_id(u8 localId); void discord_network_init(int64_t lobbyId); diff --git a/src/pc/network/network.h b/src/pc/network/network.h index 673363eaa..c7970a11b 100644 --- a/src/pc/network/network.h +++ b/src/pc/network/network.h @@ -43,6 +43,7 @@ enum NetworkSystemType { struct NetworkSystem { bool (*initialize)(enum NetworkType); s64 (*get_id)(u8 localIndex); + char* (*get_id_str)(u8 localIndex); void (*save_id)(u8 localIndex, s64 networkId); void (*clear_id)(u8 localIndex); void* (*dup_addr)(u8 localIndex); diff --git a/src/pc/network/packets/packet.c b/src/pc/network/packets/packet.c index 70b313219..20f2ed272 100644 --- a/src/pc/network/packets/packet.c +++ b/src/pc/network/packets/packet.c @@ -1,5 +1,6 @@ #include #include "../network.h" +#include "pc/network/ban_list.h" #include "pc/debuglog.h" void packet_process(struct Packet* p) { @@ -100,6 +101,15 @@ void packet_receive(struct Packet* p) { // send an ACK if requested network_send_ack(p); + // refuse packets from banned players + if (gNetworkType == NT_SERVER) { + if (ban_list_contains(gNetworkSystem->get_id_str(p->localIndex))) { + LOG_INFO("kicking banned player"); + network_send_kick(0, EKT_BANNED); + return; + } + } + // refuse packets from unknown servers if (gNetworkServerAddr != NULL && gNetworkType == NT_CLIENT) { bool fromServer = (p->localIndex == UNKNOWN_LOCAL_INDEX); @@ -114,7 +124,7 @@ void packet_receive(struct Packet* p) { if (gNetworkType == NT_SERVER && p->localIndex == UNKNOWN_LOCAL_INDEX && !network_allow_unknown_local_index(packetType)) { if (packetType != PACKET_PLAYER) { LOG_INFO("closing connection for packetType: %d", packetType); - network_send_kick(EKT_CLOSE_CONNECTION); + network_send_kick(0, EKT_CLOSE_CONNECTION); } LOG_INFO("refusing packet from unknown player, packetType: %d", packetType); return; diff --git a/src/pc/network/packets/packet.h b/src/pc/network/packets/packet.h index e6f2dff16..f78dcaf36 100644 --- a/src/pc/network/packets/packet.h +++ b/src/pc/network/packets/packet.h @@ -102,6 +102,8 @@ struct Packet { enum KickReasonType { EKT_CLOSE_CONNECTION, EKT_FULL_PARTY, + EKT_KICKED, + EKT_BANNED, }; struct LSTNetworkType { @@ -215,7 +217,7 @@ void network_send_chat(char* message, u8 globalIndex); void network_receive_chat(struct Packet* p); // packet_kick.c -void network_send_kick(enum KickReasonType kickReason); +void network_send_kick(u8 localIndex, enum KickReasonType kickReason); void network_receive_kick(struct Packet* p); // packet_keep_alive.c diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c index e8ead8878..e55fade76 100644 --- a/src/pc/network/packets/packet_join.c +++ b/src/pc/network/packets/packet_join.c @@ -73,7 +73,7 @@ void network_send_join(struct Packet* joinRequestPacket) { } } if (globalIndex == UNKNOWN_LOCAL_INDEX) { - network_send_kick(EKT_FULL_PARTY); + network_send_kick(0, EKT_FULL_PARTY); return; } } diff --git a/src/pc/network/packets/packet_kick.c b/src/pc/network/packets/packet_kick.c index badc47445..b231e6db6 100644 --- a/src/pc/network/packets/packet_kick.c +++ b/src/pc/network/packets/packet_kick.c @@ -3,12 +3,12 @@ #include "pc/debuglog.h" #include "pc/djui/djui.h" -void network_send_kick(enum KickReasonType kickReason) { +void network_send_kick(u8 localIndex, enum KickReasonType kickReason) { u8 kickReasonType = kickReason; struct Packet p = { 0 }; packet_init(&p, PACKET_KICK, false, PLMT_NONE); packet_write(&p, &kickReasonType, sizeof(u8)); - network_send_to(0, &p); + network_send_to(localIndex, &p); } void network_receive_kick(struct Packet* p) { @@ -28,6 +28,8 @@ void network_receive_kick(struct Packet* p) { switch (kickReason) { case EKT_FULL_PARTY: djui_panel_join_message_error("\\#ffa0a0\\Error:\\#c8c8c8\\ The party is full."); break; + case EKT_KICKED: djui_panel_join_message_error("\\#ffa0a0\\Error:\\#c8c8c8\\ The server kicked you."); break; + case EKT_BANNED: djui_panel_join_message_error("\\#ffa0a0\\Error:\\#c8c8c8\\ The server banned you."); break; default: djui_panel_join_message_error("\\#ffa0a0\\Error:\\#c8c8c8\\ Host has closed the connection."); break; } network_shutdown(false); diff --git a/src/pc/network/socket/socket.c b/src/pc/network/socket/socket.c index 55e07d622..84fea624a 100644 --- a/src/pc/network/socket/socket.c +++ b/src/pc/network/socket/socket.c @@ -105,6 +105,13 @@ static s64 ns_socket_get_id(UNUSED u8 localId) { return 0; } +static char* ns_socket_get_id_str(u8 localId) { + if (localId == UNKNOWN_LOCAL_INDEX) { localId = 0; } + static char id_str[INET_ADDRSTRLEN] = { 0 }; + snprintf(id_str, INET_ADDRSTRLEN, "%s", inet_ntoa(addr[localId].sin_addr)); + return id_str; +} + static void ns_socket_save_id(u8 localId, UNUSED s64 networkId) { assert(localId > 0); assert(localId < MAX_PLAYERS); @@ -168,6 +175,7 @@ static void ns_socket_shutdown(void) { struct NetworkSystem gNetworkSystemSocket = { .initialize = ns_socket_initialize, .get_id = ns_socket_get_id, + .get_id_str = ns_socket_get_id_str, .save_id = ns_socket_save_id, .clear_id = ns_socket_clear_id, .dup_addr = ns_socket_dup_addr,