Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Version 1.5.0 (TBD): Not yet released
### Major features

### Minor features, changes, and enhancements
[@is-this-c](https://github.com/is-this-c)
- Add `IoCursor` and `PacketBuilder`

### Bug fixes

Expand Down
7 changes: 4 additions & 3 deletions game_patch/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -268,15 +268,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
100 changes: 50 additions & 50 deletions game_patch/multi/alpine_packets.cpp

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions game_patch/multi/network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1007,8 +1007,8 @@ FunHook<MultiIoPacketHandler> process_chat_line_packet_hook{
// The stock relay loop team-filters recipients, so the teamless demo
// recorder never gets team chat; mirror the wire packet (the handler
// only receives the body) tagged with the sender's team.
const size_t body_len = 2 + strnlen(msg, rf::max_packet_size - 3) + 1;
std::byte packet_buf[rf::max_packet_size];
const size_t body_len = 2 + strnlen(msg, rf::MAX_PACKET_SIZE - 3) + 1;
std::byte packet_buf[rf::MAX_PACKET_SIZE];
if (sizeof(RF_GamePacketHeader) + body_len <= sizeof(packet_buf)) {
RF_GamePacketHeader header{};
header.type = chat_line;
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 @@ -308,3 +310,117 @@ bool try_to_auto_forward_port(int port);
void clear_rcon_profile_sessions();
void clear_rcon_state_for_addr(const rf::NetAddr& addr);
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