From d86429acab024377519fa595c85c027614218b24 Mon Sep 17 00:00:00 2001 From: is-this-c <87069698+is-this-c@users.noreply.github.com> Date: Sun, 23 Aug 2026 02:27:23 +1200 Subject: [PATCH 1/7] Join between levels II --- common/include/common/rfproto.h | 2 +- game_patch/multi/network.cpp | 32 ++++++++++++++++++++++++++++++++ game_patch/rf/multi.h | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/common/include/common/rfproto.h b/common/include/common/rfproto.h index d75d14229..853ec84b9 100644 --- a/common/include/common/rfproto.h +++ b/common/include/common/rfproto.h @@ -338,7 +338,7 @@ enum RF_LeftReason RF_LR_KICKED = 0x02, RF_LR_DATA_INCOMPATIBLE = 0x04, // Note: received when PF wants to download map (?) RF_LR_BETWEEN_LEVELS = 0x05, - RF_LR_EMPTY = 0x06, // TODO: check if it's polish translation bug + RF_LR_TIMED_OUT_JOINING = 0x06, RF_LR_NO_LEVEL_FILE = 0x07, // Note: other reasons shows the same message as RF_LR_UNKNOWN }; diff --git a/game_patch/multi/network.cpp b/game_patch/multi/network.cpp index 710c098c5..ba9455f2b 100644 --- a/game_patch/multi/network.cpp +++ b/game_patch/multi/network.cpp @@ -2970,6 +2970,37 @@ CodeInjection send_state_info_injection{ }, }; +CodeInjection process_state_info_req_kick_between_levels_patch{ + 0x00481C12, + [] (auto& regs) { + rf::Player* const player = regs.ebx; + if (!is_player_minimum_af_client_version(player, 1, 4, 0)) { + return; + } + + rf::send_state_info(player); + + // `send_state_info` sets `NETPLAYER_STATE_IN_GAME`, but we set + // `NETPLAYER_STATE_WAITING` like `mp_change_level`. + constexpr int NETPLAYER_STATE_WAITING = 0x0; + player->net_data->state = NETPLAYER_STATE_WAITING; + + const RF_GamePacketHeader enter_limbo{ + .type = RF_GPT_ENTER_LIMBO, + .size = 0 + }; + rf::multi_io_send_reliable( + player, + &enter_limbo, + enter_limbo.size + sizeof(RF_GamePacketHeader), + FALSE + ); + + regs.esp += 0x8; + regs.eip = 0x00481C4B; + } +}; + FunHook send_players_packet_hook{ 0x00481C70, [](rf::Player *player) { @@ -3528,6 +3559,7 @@ void network_init() // print join_req denial reasons check_access_for_new_player_hook.install(); + process_state_info_req_kick_between_levels_patch.install(); // Move our limbo check to `check_join_request_restrict_status`. AsmWriter{0x0047AE64}.nop(6); diff --git a/game_patch/rf/multi.h b/game_patch/rf/multi.h index e7c4c7d93..8591c154f 100644 --- a/game_patch/rf/multi.h +++ b/game_patch/rf/multi.h @@ -263,7 +263,7 @@ namespace rf static auto& multi_get_game_type = addr_as_ref(0x00470770); static auto& multi_io_send = addr_as_ref(0x00479370); static auto& multi_io_send_reliable = - addr_as_ref(0x00479480); + addr_as_ref(0x00479480); static auto& multi_io_send_reliable_to_all = addr_as_ref(0x004795A0); static auto& multi_io_send_buffered_reliable_packets = addr_as_ref(0x004796C0); From 8bb63c75efe7c0cb4d4ace438b23373bd4102506 Mon Sep 17 00:00:00 2001 From: is-this-c <87069698+is-this-c@users.noreply.github.com> Date: Sun, 23 Aug 2026 20:48:04 +1200 Subject: [PATCH 2/7] Update network.cpp --- game_patch/multi/network.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/game_patch/multi/network.cpp b/game_patch/multi/network.cpp index ba9455f2b..d801c16fa 100644 --- a/game_patch/multi/network.cpp +++ b/game_patch/multi/network.cpp @@ -2970,7 +2970,7 @@ CodeInjection send_state_info_injection{ }, }; -CodeInjection process_state_info_req_kick_between_levels_patch{ +CodeInjection process_state_info_req_between_levels_patch{ 0x00481C12, [] (auto& regs) { rf::Player* const player = regs.ebx; @@ -2980,19 +2980,16 @@ CodeInjection process_state_info_req_kick_between_levels_patch{ rf::send_state_info(player); - // `send_state_info` sets `NETPLAYER_STATE_IN_GAME`, but we set - // `NETPLAYER_STATE_WAITING` like `mp_change_level`. + // Set `NETPLAYER_STATE_WAITING` like `mp_change_level`. constexpr int NETPLAYER_STATE_WAITING = 0x0; player->net_data->state = NETPLAYER_STATE_WAITING; - const RF_GamePacketHeader enter_limbo{ - .type = RF_GPT_ENTER_LIMBO, - .size = 0 - }; + const RF_GamePacketHeader enter_limbo_packet = + {.type = RF_GPT_ENTER_LIMBO, .size = 0}; rf::multi_io_send_reliable( player, - &enter_limbo, - enter_limbo.size + sizeof(RF_GamePacketHeader), + &enter_limbo_packet, + enter_limbo_packet.size + sizeof(RF_GamePacketHeader), FALSE ); @@ -3559,7 +3556,7 @@ void network_init() // print join_req denial reasons check_access_for_new_player_hook.install(); - process_state_info_req_kick_between_levels_patch.install(); + process_state_info_req_between_levels_patch.install(); // Move our limbo check to `check_join_request_restrict_status`. AsmWriter{0x0047AE64}.nop(6); From d233063a6f23807cd8c25c7605151e136ba595a7 Mon Sep 17 00:00:00 2001 From: is-this-c <87069698+is-this-c@users.noreply.github.com> Date: Sun, 23 Aug 2026 20:48:52 +1200 Subject: [PATCH 3/7] Update network.cpp --- game_patch/multi/network.cpp | 38 +++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/game_patch/multi/network.cpp b/game_patch/multi/network.cpp index d801c16fa..403b068f7 100644 --- a/game_patch/multi/network.cpp +++ b/game_patch/multi/network.cpp @@ -2974,27 +2974,25 @@ CodeInjection process_state_info_req_between_levels_patch{ 0x00481C12, [] (auto& regs) { rf::Player* const player = regs.ebx; - if (!is_player_minimum_af_client_version(player, 1, 4, 0)) { - return; - } - - rf::send_state_info(player); - - // Set `NETPLAYER_STATE_WAITING` like `mp_change_level`. - constexpr int NETPLAYER_STATE_WAITING = 0x0; - player->net_data->state = NETPLAYER_STATE_WAITING; - - const RF_GamePacketHeader enter_limbo_packet = - {.type = RF_GPT_ENTER_LIMBO, .size = 0}; - rf::multi_io_send_reliable( - player, - &enter_limbo_packet, - enter_limbo_packet.size + sizeof(RF_GamePacketHeader), - FALSE - ); + if (is_player_minimum_af_client_version(player, 1, 4, 0)) { + rf::send_state_info(player); + + // Set `NETPLAYER_STATE_WAITING` like `mp_change_level`. + constexpr int NETPLAYER_STATE_WAITING = 0x0; + player->net_data->state = NETPLAYER_STATE_WAITING; + + const RF_GamePacketHeader enter_limbo_packet = + {.type = RF_GPT_ENTER_LIMBO, .size = 0}; + rf::multi_io_send_reliable( + player, + &enter_limbo_packet, + enter_limbo_packet.size + sizeof(RF_GamePacketHeader), + FALSE + ); - regs.esp += 0x8; - regs.eip = 0x00481C4B; + regs.esp += 0x8; + regs.eip = 0x00481C4B; + } } }; From e1b7a4720d89cb2c29839c8db6fe7c9201ed021b Mon Sep 17 00:00:00 2001 From: is-this-c <87069698+is-this-c@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:57:05 +1200 Subject: [PATCH 4/7] Update CHANGELOG.md --- docs/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ac80fde9c..f104e69e7 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,12 @@ ⚙️⛏ Alpine Faction Changelog ⛏⚙️ =================================== +Version 1.5.0 (TBD): Not yet released +-------------------------------- +### Minor features, changes, and enhancements +[@is-this-c](https://github.com/is-this-c) +- Allow a player to join right before limbo + Version 1.4.0 (Lupin): Not yet released -------------------------------- ### Major features From 0e10e39dcf38935b4e75d2a6ed8a4d8d0c54599f Mon Sep 17 00:00:00 2001 From: is-this-c <87069698+is-this-c@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:01:00 +1200 Subject: [PATCH 5/7] Update CHANGELOG.md --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f104e69e7..ab3051a80 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,7 +5,7 @@ Version 1.5.0 (TBD): Not yet released -------------------------------- ### Minor features, changes, and enhancements [@is-this-c](https://github.com/is-this-c) -- Allow a player to join right before limbo +- Do not kick a player, if they joined right before limbo Version 1.4.0 (Lupin): Not yet released -------------------------------- From e2c2f8524e329aeefaff4c67a4e99925bd4120ba Mon Sep 17 00:00:00 2001 From: is-this-c <87069698+is-this-c@users.noreply.github.com> Date: Fri, 28 Aug 2026 03:43:54 +1200 Subject: [PATCH 6/7] Fix --- game_patch/multi/demo/demo_record.cpp | 4 ++-- game_patch/multi/network.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/game_patch/multi/demo/demo_record.cpp b/game_patch/multi/demo/demo_record.cpp index 0633018b0..5de70f956 100644 --- a/game_patch/multi/demo/demo_record.cpp +++ b/game_patch/multi/demo/demo_record.cpp @@ -334,12 +334,12 @@ namespace FunHook multi_io_send_reliable_hook{ 0x00479480, - [](rf::Player* player, const void* data, int len, int not_limbo) { + [](rf::Player* player, const void* data, int len, int require_in_game) { if (player && player->is_observer()) { capture_packet(data, len, DEMO_PKT_RELIABLE); return; } - multi_io_send_reliable_hook.call_target(player, data, len, not_limbo); + multi_io_send_reliable_hook.call_target(player, data, len, require_in_game); }, }; diff --git a/game_patch/multi/network.cpp b/game_patch/multi/network.cpp index 403b068f7..81e1e0dda 100644 --- a/game_patch/multi/network.cpp +++ b/game_patch/multi/network.cpp @@ -2974,7 +2974,7 @@ CodeInjection process_state_info_req_between_levels_patch{ 0x00481C12, [] (auto& regs) { rf::Player* const player = regs.ebx; - if (is_player_minimum_af_client_version(player, 1, 4, 0)) { + if (regs.al && is_player_minimum_af_client_version(player, 1, 4, 0)) { rf::send_state_info(player); // Set `NETPLAYER_STATE_WAITING` like `mp_change_level`. @@ -2992,8 +2992,11 @@ CodeInjection process_state_info_req_between_levels_patch{ regs.esp += 0x8; regs.eip = 0x00481C4B; + } else { + regs.eip = regs.al ? 0x00481C31 : 0x00481C53; } - } + }, + false }; FunHook send_players_packet_hook{ From bacb7bf56d54f1ed5482b86149eda4d1a39bedb0 Mon Sep 17 00:00:00 2001 From: is-this-c <87069698+is-this-c@users.noreply.github.com> Date: Fri, 28 Aug 2026 06:09:32 +1200 Subject: [PATCH 7/7] Fix --- game_patch/multi/network.cpp | 45 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/game_patch/multi/network.cpp b/game_patch/multi/network.cpp index aeee44d00..807ec4f4a 100644 --- a/game_patch/multi/network.cpp +++ b/game_patch/multi/network.cpp @@ -3196,28 +3196,33 @@ CodeInjection send_state_info_injection{ CodeInjection process_state_info_req_between_levels_patch{ 0x00481C12, [] (auto& regs) { - rf::Player* const player = regs.ebx; - if (regs.al && is_player_minimum_af_client_version(player, 1, 4, 0)) { - rf::send_state_info(player); - - // Set `NETPLAYER_STATE_WAITING` like `mp_change_level`. - constexpr int NETPLAYER_STATE_WAITING = 0x0; - player->net_data->state = NETPLAYER_STATE_WAITING; - - const RF_GamePacketHeader enter_limbo_packet = - {.type = RF_GPT_ENTER_LIMBO, .size = 0}; - rf::multi_io_send_reliable( - player, - &enter_limbo_packet, - enter_limbo_packet.size + sizeof(RF_GamePacketHeader), - FALSE - ); - - regs.esp += 0x8; - regs.eip = 0x00481C4B; + if (regs.al) { + rf::Player* const player = regs.ebx; + if (is_player_minimum_af_client_version(player, 1, 4, 0)) { + rf::send_state_info(player); + + // Set `NETPLAYER_STATE_WAITING` like `mp_change_level`. + constexpr int NETPLAYER_STATE_WAITING = 0x0; + player->net_data->state = NETPLAYER_STATE_WAITING; + + const RF_GamePacketHeader enter_limbo_packet = + {.type = RF_GPT_ENTER_LIMBO, .size = 0}; + rf::multi_io_send_reliable( + player, + &enter_limbo_packet, + enter_limbo_packet.size + sizeof(RF_GamePacketHeader), + FALSE + ); + + regs.eip = 0x00481C4B; + } else { + regs.eip = 0x00481C31; + } } else { - regs.eip = regs.al ? 0x00481C31 : 0x00481C53; + regs.eip = 0x00481C53; } + + regs.esp += 0x8; }, false };