From fbdd4bb0566feafc849cce2931248efa680f8b14 Mon Sep 17 00:00:00 2001 From: bluelightzero Date: Sun, 26 Apr 2026 02:13:44 +0100 Subject: [PATCH 1/3] Init BadNet --- src/pc/network/badnet.c | 158 +++++++++++++++++++++++++++++++++++++++ src/pc/network/badnet.h | 13 ++++ src/pc/network/network.c | 10 ++- 3 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/pc/network/badnet.c create mode 100644 src/pc/network/badnet.h diff --git a/src/pc/network/badnet.c b/src/pc/network/badnet.c new file mode 100644 index 000000000..d9abdb10a --- /dev/null +++ b/src/pc/network/badnet.c @@ -0,0 +1,158 @@ +#include "badnet.h" + +#include "socket/socket.h" +#include "pc/utils/misc.h" + +/* + + Bad Net is a tool for simulating a bad network connection. + + It wraps gNetworkSystemSocket and manipulates out-going packets + to recreate the same types of issues caused by an internet connection; + all from the comfort of your direct connection over localhost. + +*/ + +struct DelayedPacket { + u8 localIndex; + u8* data; + u16 dataLength; + f64 time; + struct DelayedPacket* next; +}; + +// Requires reconnection +// Set to `true` to wrap gNetworkSystemSocket +bool gBadNetEnabled = true; + +// Time in seconds for round trip +// 0.1 -> Good +// 0.2 -> Common +// 0.3 -> Across the sea +// 1.0 -> Too slow! +f64 gBadNetLatency = 0.0; + +//Add an additional random delay to packets (Can cause out of order packets) +// 0.0 -> No Jitter +// 0.5 -> Extra 0 to 500ms extra delay. +f64 gBadNetLatencyJitter = 0.0; + +// Randomly drop packets +// 0.0 -> No Packets Lost +// 1.0 -> All Packets Lost +f64 gBadNetPacketLoss = 0.0; + +// Chance of packet being sent again. (Warning: This stacks! Use small values) +// 0.0 -> No duplication +// 0.1 -> Every packet has a 10% chance of +// 1.0 -> :Skull: +f64 gBadNetPacketDuplicate = 0.0; + +struct NetworkSystem *gNetworkSystemBadNetWraps = &gNetworkSystemSocket; + +struct DelayedPacket* delayedPackets = NULL; + +static bool ns_badnet_initialize(enum NetworkType networkType, bool reconnecting) { + return gNetworkSystemBadNetWraps->initialize(networkType, reconnecting); +} + +static s64 ns_badnet_get_id(u8 localId) { + return gNetworkSystemBadNetWraps->get_id(localId); +} + +static char* ns_badnet_get_id_str(u8 localId) { + return gNetworkSystemBadNetWraps->get_id_str(localId); +} + +static void ns_badnet_save_id(u8 localId, s64 networkId) { + gNetworkSystemBadNetWraps->save_id(localId, networkId); +} + +static void ns_badnet_clear_id(u8 localId) { + gNetworkSystemBadNetWraps->clear_id(localId); +} + +static void* ns_badnet_dup_addr(u8 localIndex) { + return gNetworkSystemBadNetWraps->dup_addr(localIndex); +} + +static bool ns_badnet_match_addr(void* addr1, void* addr2) { + return gNetworkSystemBadNetWraps->match_addr(addr1, addr2); +} + +static void ns_badnet_update(void) { + struct DelayedPacket* prev = NULL; + struct DelayedPacket* p = delayedPackets; + while(p) { + struct DelayedPacket *next = p->next; + if (clock_elapsed_f64() > p->time) { + gNetworkSystemBadNetWraps->send( + p->localIndex, + gNetworkSystemBadNetWraps->dup_addr(p->localIndex), + p->data, + p->dataLength + ); + free(p->data); + free(p); + if (prev) { + prev->next = next; + } else { + delayedPackets = next; + } + } else { + prev = p; + } + p = next; + } + + gNetworkSystemBadNetWraps->update(); +} + +static f64 random_f64() { + return (float)rand()/(float)RAND_MAX; +} + +static int ns_badnet_send(u8 localIndex, UNUSED void* address, u8* data, u16 dataLength) { + if (random_f64() > gBadNetPacketLoss) { + do { + struct DelayedPacket* p = malloc(sizeof(struct DelayedPacket)); + p->localIndex = localIndex; + p->data = calloc(dataLength, sizeof(u8)); + memcpy(p->data, data, dataLength); + p->dataLength = dataLength; + p->time = clock_elapsed_f64() + (gBadNetLatency + gBadNetLatencyJitter * random_f64()) * 0.5; + p->next = delayedPackets; + delayedPackets = p; + } while (random_f64() < gBadNetPacketDuplicate); + } + return dataLength; +} + +static void ns_badnet_get_lobby_id(char* destination, u32 destLength) { + gNetworkSystemBadNetWraps->get_lobby_id(destination, destLength); +} + +static void ns_badnet_get_lobby_secret(char* destination, u32 destLength) { + gNetworkSystemBadNetWraps->get_lobby_secret(destination, destLength); +} + +static void ns_badnet_shutdown(bool reconnecting) { + gNetworkSystemBadNetWraps->shutdown(reconnecting); +} + +struct NetworkSystem gNetworkSystemBadNet = { + .initialize = ns_badnet_initialize, + .get_id = ns_badnet_get_id, + .get_id_str = ns_badnet_get_id_str, + .save_id = ns_badnet_save_id, + .clear_id = ns_badnet_clear_id, + .dup_addr = ns_badnet_dup_addr, + .match_addr = ns_badnet_match_addr, + .update = ns_badnet_update, + .send = ns_badnet_send, + .get_lobby_id = ns_badnet_get_lobby_id, + .get_lobby_secret = ns_badnet_get_lobby_secret, + .shutdown = ns_badnet_shutdown, + .requireServerBroadcast = true, + .name = "BadNet", +}; diff --git a/src/pc/network/badnet.h b/src/pc/network/badnet.h new file mode 100644 index 000000000..e0d6db38d --- /dev/null +++ b/src/pc/network/badnet.h @@ -0,0 +1,13 @@ +#ifndef BADNET_H +#define BADNET_H + +#include + +extern bool gBadNetEnabled; +extern f64 gBadNetLatency; +extern f64 gBadNetLatencyJitter; +extern f64 gBadNetPacketLoss; +extern f64 gBadNetPacketDuplicate; +extern struct NetworkSystem gNetworkSystemBadNet; + +#endif diff --git a/src/pc/network/network.c b/src/pc/network/network.c index 6bd1d4882..e691f7566 100644 --- a/src/pc/network/network.c +++ b/src/pc/network/network.c @@ -2,6 +2,8 @@ #include "coopnet/coopnet.h" #include #include "network.h" + +#include "badnet.h" #include "object_fields.h" #include "game/level_update.h" #include "object_constants.h" @@ -99,7 +101,13 @@ void network_set_system(enum NetworkSystemType nsType) { network_forget_all_reliable(); switch (nsType) { - case NS_SOCKET: gNetworkSystem = &gNetworkSystemSocket; break; + case NS_SOCKET: + if (gBadNetEnabled) { + gNetworkSystem = &gNetworkSystemBadNet; + } else { + gNetworkSystem = &gNetworkSystemSocket; + } + break; #ifdef COOPNET case NS_COOPNET: gNetworkSystem = &gNetworkSystemCoopNet; break; #endif From f0c0f283bf911e86324ea233441063e435ef7235 Mon Sep 17 00:00:00 2001 From: bluelightzero Date: Fri, 1 May 2026 22:29:29 +0100 Subject: [PATCH 2/3] Default BadNet to disabled --- src/pc/network/badnet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/network/badnet.c b/src/pc/network/badnet.c index d9abdb10a..b53d5c91b 100644 --- a/src/pc/network/badnet.c +++ b/src/pc/network/badnet.c @@ -23,7 +23,7 @@ struct DelayedPacket { // Requires reconnection // Set to `true` to wrap gNetworkSystemSocket -bool gBadNetEnabled = true; +bool gBadNetEnabled = false; // Time in seconds for round trip // 0.1 -> Good From 04e5916c4b6f21b8bc7e64b7b8ed0b616ac4db17 Mon Sep 17 00:00:00 2001 From: bluelightzero Date: Fri, 1 May 2026 22:31:24 +0100 Subject: [PATCH 3/3] Allow Developer mode to be used along with BadNet --- src/pc/network/network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/network/network.c b/src/pc/network/network.c index e691f7566..16902ef8f 100644 --- a/src/pc/network/network.c +++ b/src/pc/network/network.c @@ -652,7 +652,7 @@ static inline void color_set(Color color, u8 r, u8 g, u8 b) { } bool network_allow_mod_dev_mode(void) { - return (configModDevMode && gNetworkSystem == &gNetworkSystemSocket && gNetworkType == NT_SERVER); + return (configModDevMode && gNetworkSystem != &gNetworkSystemCoopNet && gNetworkType == NT_SERVER); } void network_mod_dev_mode_reload(void) {