Skip to content
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Version 1.4.0 (Lupin): Not yet released
- Make clock clutter objects correctly display the current local real world time

[@is-this-c](https://github.com/is-this-c)
- Add `IoCursor` and `PacketBuilder`
- Disable `Refresh Selected` in the server browser, only if `Refresh Selected` was pressed
- Never disable `Add Server` in the server browser
- Add `NOT IN ROUND`, `IDLE`, and `SPECTATOR` to the spectate UI
Expand Down
7 changes: 4 additions & 3 deletions game_patch/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,16 @@ set(SRCS
multi/bots/bot_waypoint_route.cpp
multi/bots/bot_waypoint_route.h
multi/bots/bot_weapon_profiles.h
os/autocomplete.cpp
os/console.cpp
os/console.h
os/commands.cpp
os/autocomplete.cpp
os/frametime.cpp
os/timer.cpp
os/win32_console.cpp
os/io_cursor.h
os/os.cpp
os/os.h
os/timer.cpp
os/win32_console.cpp
object/object.h
object/mover.h
object/object_private.h
Expand Down
54 changes: 27 additions & 27 deletions game_patch/multi/alpine_packets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ void af_send_packet(rf::Player* player, const void* data, int len, bool is_relia
}
return;
}
if (len <= 0 || len > rf::max_packet_size) {
xlog::error("af_send_packet: Packet size {} exceeds max {}", len, rf::max_packet_size);
if (len <= 0 || len > rf::MAX_PACKET_SIZE) {
xlog::error("af_send_packet: Packet size {} exceeds max {}", len, rf::MAX_PACKET_SIZE);
return;
}

Expand Down Expand Up @@ -152,7 +152,7 @@ void af_send_ping_location_req_packet(rf::Vector3* pos)
return;
}

std::byte packet_buf[rf::max_packet_size];
std::byte packet_buf[rf::MAX_PACKET_SIZE];
af_ping_location_req_packet ping_location_req_packet{};
ping_location_req_packet.header.type = static_cast<uint8_t>(af_packet_type::af_ping_location_req);
ping_location_req_packet.header.size = sizeof(ping_location_req_packet) - sizeof(ping_location_req_packet.header);
Expand Down Expand Up @@ -233,7 +233,7 @@ void af_send_ping_location_packet(rf::Vector3* pos, uint8_t player_id, rf::Playe
return;
}

std::byte packet_buf[rf::max_packet_size];
std::byte packet_buf[rf::MAX_PACKET_SIZE];
af_ping_location_packet ping_location_packet{};
ping_location_packet.header.type = static_cast<uint8_t>(af_packet_type::af_ping_location);
ping_location_packet.header.size = sizeof(ping_location_packet) - sizeof(ping_location_packet.header);
Expand Down Expand Up @@ -313,7 +313,7 @@ void af_send_damage_notify_packet(uint8_t player_id, float damage, bool died, rf
return;
}

std::byte packet_buf[rf::max_packet_size];
std::byte packet_buf[rf::MAX_PACKET_SIZE];
af_damage_notify_packet damage_notify_packet{};
damage_notify_packet.header.type = static_cast<uint8_t>(af_packet_type::af_damage_notify);
damage_notify_packet.header.size = sizeof(damage_notify_packet) - sizeof(damage_notify_packet.header);
Expand Down Expand Up @@ -433,7 +433,7 @@ void af_send_obj_update_packet(rf::Player* player)
size_t object_data_size = obj_updates.size() * sizeof(af_obj_update);
size_t total_packet_size = sizeof(RF_GamePacketHeader) + object_data_size;

if (total_packet_size > rf::max_packet_size) {
if (total_packet_size > rf::MAX_PACKET_SIZE) {
xlog::error("af_send_obj_update_packet: Packet too large! Size: {}", total_packet_size);
return;
}
Expand Down Expand Up @@ -591,7 +591,7 @@ void af_send_client_req_packet(const af_client_req_packet& packet)
return;
}

std::byte buf[rf::max_packet_size];
std::byte buf[rf::MAX_PACKET_SIZE];
size_t offset = 0;

// Write header
Expand Down Expand Up @@ -690,7 +690,7 @@ void af_send_server_req_packet(const af_server_req_packet& packet, rf::Player* p
return;
}

std::byte buf[rf::max_packet_size];
std::byte buf[rf::MAX_PACKET_SIZE];
size_t offset = 0;

std::memcpy(buf + offset, &packet.header, sizeof(packet.header));
Expand Down Expand Up @@ -838,7 +838,7 @@ void af_send_just_spawned_loadout(rf::Player* to_player, std::vector<WeaponLoado
if (!rf::is_server || !to_player)
return;

std::byte buf[rf::max_packet_size];
std::byte buf[rf::MAX_PACKET_SIZE];
if (sizeof(buf) < sizeof(RF_GamePacketHeader) + 1)
return;

Expand All @@ -863,7 +863,7 @@ void af_send_just_spawned_loadout(rf::Player* to_player, std::vector<WeaponLoado
hdr->size = static_cast<uint16_t>(1 + payload_written);

const size_t total_len = sizeof(RF_GamePacketHeader) + hdr->size;
if (total_len > rf::max_packet_size)
if (total_len > rf::MAX_PACKET_SIZE)
return;

af_send_packet(to_player, buf, static_cast<int>(total_len), true);
Expand Down Expand Up @@ -962,10 +962,10 @@ void af_send_koth_hill_state_packet(rf::Player* player, const HillInfo& h, const
pkt.red_score = static_cast<uint16_t>(std::clamp(g_koth_info.red_team_score, 0, 0xFFFF));
pkt.blue_score = static_cast<uint16_t>(std::clamp(g_koth_info.blue_team_score, 0, 0xFFFF));

std::byte buf[rf::max_packet_size];
std::byte buf[rf::MAX_PACKET_SIZE];
const size_t wire_sz = sizeof(pkt);
if (wire_sz > rf::max_packet_size) {
xlog::error("af_koth_state: packet too large ({}>{})", wire_sz, rf::max_packet_size);
if (wire_sz > rf::MAX_PACKET_SIZE) {
xlog::error("af_koth_state: packet too large ({}>{})", wire_sz, rf::MAX_PACKET_SIZE);
return;
}
std::memcpy(buf, &pkt, wire_sz);
Expand Down Expand Up @@ -1056,12 +1056,12 @@ void af_send_koth_hill_captured_packet(rf::Player* player, uint8_t hill_uid, Hil
const size_t payload_size = (sizeof(af_koth_hill_captured_packet) - sizeof(RF_GamePacketHeader)) + id_count;
const size_t wire_size = sizeof(RF_GamePacketHeader) + payload_size;

if (wire_size > rf::max_packet_size) {
xlog::error("af_koth_hill_captured: packet too large ({} > {})", wire_size, rf::max_packet_size);
if (wire_size > rf::MAX_PACKET_SIZE) {
xlog::error("af_koth_hill_captured: packet too large ({} > {})", wire_size, rf::MAX_PACKET_SIZE);
return;
}

std::byte packet_buf[rf::max_packet_size];
std::byte packet_buf[rf::MAX_PACKET_SIZE];

af_koth_hill_captured_packet af_koth_hill_captured_packet{};
af_koth_hill_captured_packet.header.type = static_cast<uint8_t>(af_packet_type::af_koth_hill_captured);
Expand Down Expand Up @@ -1653,7 +1653,7 @@ void af_send_server_cfg(rf::Player* player) {
}

const auto send_msg = [player] (const std::string_view msg) {
constexpr size_t max_chunk_len = rf::max_packet_size - sizeof(af_server_msg_packet);
constexpr size_t max_chunk_len = rf::MAX_PACKET_SIZE - sizeof(af_server_msg_packet);
const size_t len = std::clamp(msg.size(), 0uz, max_chunk_len);

af_server_msg_packet server_msg_packet;
Expand Down Expand Up @@ -1682,7 +1682,7 @@ void af_send_server_cfg(rf::Player* player) {
// We cannot send multiple server configs at once.
send_queues_rel_clear_packets(player->net_data->reliable_socket);

constexpr int chunk_size = rf::max_packet_size - sizeof(af_server_msg_packet);
constexpr int chunk_size = rf::MAX_PACKET_SIZE - sizeof(af_server_msg_packet);
for (const auto chunk : g_alpine_server_config.printed_cfg
| std::views::chunk(chunk_size)) {
send_msg(std::string_view{chunk.begin(), chunk.end()});
Expand All @@ -1705,13 +1705,13 @@ void af_send_server_cfg(rf::Player* player) {

union af_server_msg_packet_buf {
af_server_msg_packet packet;
std::array<uint8_t, rf::max_packet_size> buf;
std::array<uint8_t, rf::MAX_PACKET_SIZE> buf;
};

af_server_msg_packet_buf build_automated_chat_msg_packet(
const std::string_view msg
) {
constexpr size_t max_len = rf::max_packet_size - sizeof(af_server_msg_packet);
constexpr size_t max_len = rf::MAX_PACKET_SIZE - sizeof(af_server_msg_packet);
const size_t len = std::clamp(msg.size(), 0uz, max_len);

af_server_msg_packet_buf buf{};
Expand All @@ -1730,7 +1730,7 @@ af_server_msg_packet_buf build_automated_chat_msg_packet(
af_server_msg_packet_buf build_server_console_msg_packet(
const std::string_view msg
) {
constexpr size_t max_len = rf::max_packet_size - sizeof(af_server_msg_packet);
constexpr size_t max_len = rf::MAX_PACKET_SIZE - sizeof(af_server_msg_packet);
const size_t len = std::clamp(msg.size(), 0uz, max_len);

af_server_msg_packet_buf buf{};
Expand Down Expand Up @@ -1874,7 +1874,7 @@ void af_send_bot_control_simple(rf::Player* player, af_bot_control_type subtype)
return;
}

std::byte buf[rf::max_packet_size];
std::byte buf[rf::MAX_PACKET_SIZE];
size_t offset = 0;

RF_GamePacketHeader header{};
Expand Down Expand Up @@ -1947,7 +1947,7 @@ void af_send_bot_control_update_personality(rf::Player* player, const ServerBotC
return;
}

std::byte buf[rf::max_packet_size];
std::byte buf[rf::MAX_PACKET_SIZE];
const size_t len = write_profile_update_payload(
buf, sizeof(buf),
af_bot_control_type::update_personality,
Expand All @@ -1966,7 +1966,7 @@ void af_send_bot_control_update_skill(rf::Player* player, const ServerBotConfig&
return;
}

std::byte buf[rf::max_packet_size];
std::byte buf[rf::MAX_PACKET_SIZE];
const size_t len = write_profile_update_payload(
buf, sizeof(buf),
af_bot_control_type::update_skill,
Expand All @@ -1988,7 +1988,7 @@ void af_send_bot_control_update_identity(rf::Player* player, const std::string&
const uint8_t name_len = static_cast<uint8_t>(
std::min<size_t>(name.size(), kMaxPresetNameLen));

std::byte buf[rf::max_packet_size];
std::byte buf[rf::MAX_PACKET_SIZE];
size_t offset = 0;

RF_GamePacketHeader header{};
Expand Down Expand Up @@ -2400,7 +2400,7 @@ void af_send_player_info_response(const rf::NetAddr& addr)

// Compute segment boundaries (each segment fits within max_packet_size)
constexpr int header_size = static_cast<int>(sizeof(af_player_info_packet));
constexpr int max_payload = static_cast<int>(rf::max_packet_size) - header_size;
constexpr int max_payload = static_cast<int>(rf::MAX_PACKET_SIZE) - header_size;

int seg_boundaries[max_players + 2]; // preamble + entries + sentinel
int total_segments = 0;
Expand Down Expand Up @@ -2436,7 +2436,7 @@ void af_send_player_info_response(const rf::NetAddr& addr)
}

// Build and send each segment
std::byte packet_buf[rf::max_packet_size];
std::byte packet_buf[rf::MAX_PACKET_SIZE];
for (int seg = 0; seg < total_segments; ++seg) {
int first_entry = seg_boundaries[seg];
int end_entry = seg_boundaries[seg + 1];
Expand Down
116 changes: 116 additions & 0 deletions game_patch/multi/network.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

#include <string>
#include <vector>
#include <common/rfproto.h>
#include <common/version/version.h>
#include "server_internal.h"
#include "../rf/multi.h"
#include "../os/io_cursor.h"

constexpr uint32_t ALPINE_FACTION_SIGNATURE = 0x4E4C5246;
constexpr uint32_t DASH_FACTION_SIGNATURE = 0xDA58FAC7;
Expand Down Expand Up @@ -277,3 +279,117 @@ void send_queues_rel_clear_packets(int socket_id);
void send_queues_rel_add_packet(int socket_id, const uint8_t* data, size_t len);
void clear_rcon_profile_sessions();
void multi_disconnect_from_server();

template <io_cursor_detail::WriteSeek Storage, auto MAX_LEN = 512>
class PacketBuilder {
public:
explicit PacketBuilder(Storage storage)
noexcept(std::is_nothrow_move_constructible_v<Storage>)
: storage{std::move(storage)}
, cursor{this->storage} {
if constexpr (std::derived_from<Storage, io_cursor_detail::VectorGrowStorage>) {
this->storage.reserve(rf::MAX_PACKET_SIZE);
}
this->cursor.write(DUMMY_HEADER);
}

template <typename... Args>
requires std::constructible_from<Storage, Args...>
explicit PacketBuilder(Args&&... args)
noexcept(std::is_nothrow_constructible_v<Storage, Args...>)
: storage{std::forward<Args>(args)...}
, cursor{this->storage} {
if constexpr (std::derived_from<Storage, io_cursor_detail::VectorGrowStorage>) {
this->storage.reserve(rf::MAX_PACKET_SIZE);
}
this->cursor.write(DUMMY_HEADER);
}

template <io_cursor_detail::PodLike T>
bool write(this PacketBuilder& self, const T& value) noexcept {
const bool res = self.cursor.write(value);
if (!res || self.size() > MAX_LEN) {
self.cursor.poison();
return false;
} else {
return true;
}
}

bool write_zstring(
this PacketBuilder& self,
const std::string_view text
) noexcept {
const bool res = self.cursor.write_zstring(text);
if (!res || self.size() > MAX_LEN) {
self.cursor.poison();
return false;
} else {
return true;
}
}

template <typename E>
requires std::is_enum_v<E>
&& (sizeof(std::underlying_type_t<E>) == 1)
bool finalize(this PacketBuilder& self, const E type) noexcept {
if (self.cursor.is_poisoned()
|| self.payload_size() > std::numeric_limits<uint16_t>::max()) {
return false;
}
const RF_GamePacketHeader header{
.type = std::to_underlying(type),
.size = static_cast<uint16_t>(self.payload_size())
};
try {
const size_t num_bytes =
self.storage.write(0, std::as_bytes(std::span{&header, 1}));
if (num_bytes != sizeof(RF_GamePacketHeader) || self.size() > MAX_LEN) {
goto POISON;
}
} catch (...) {
goto POISON;
}
return true;
POISON:
self.cursor.poison();
return false;
}

[[nodiscard]]
size_t capacity(this const PacketBuilder& self) noexcept {
return self.cursor.size();
}

[[nodiscard]]
size_t size(this const PacketBuilder& self) noexcept {
return self.cursor.position();
}

[[nodiscard]]
size_t payload_size(this const PacketBuilder& self) noexcept {
return self.cursor.position() - sizeof(RF_GamePacketHeader);
}

[[nodiscard]]
const char* data(this const PacketBuilder& self) noexcept {
return self.storage.data();
}

[[nodiscard]]
bool is_poisoned(this const PacketBuilder& self) noexcept {
return self.cursor.is_poisoned();
}

private:
static constexpr RF_GamePacketHeader DUMMY_HEADER{};

Storage storage;
IoCursor<Storage> cursor;
};

template <size_t N>
PacketBuilder(char (&)[N]) -> PacketBuilder<io_cursor_detail::FixedSpanStorage>;
PacketBuilder(std::span<char>) -> PacketBuilder<io_cursor_detail::FixedSpanStorage>;
PacketBuilder(void*, size_t) -> PacketBuilder<io_cursor_detail::FixedSpanStorage>;
PacketBuilder(std::vector<char>&) -> PacketBuilder<io_cursor_detail::VectorGrowStorage>;
Loading
Loading