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,