diff options
| author | hovertank3d <[email protected]> | 2025-11-14 20:05:59 +0100 |
|---|---|---|
| committer | hovertank3d <[email protected]> | 2025-11-15 14:03:07 +0100 |
| commit | fa318825ef0404816728f733484caa1bc23afa53 (patch) | |
| tree | 154d51e23f907102cbb8367b8cdc4d3bb4153dc1 | |
| parent | 6c1a6c62bb13d09a0beb51ebb5cd5e4a69d9fe5a (diff) | |
| download | ogurec-fa318825ef0404816728f733484caa1bc23afa53.tar.gz ogurec-fa318825ef0404816728f733484caa1bc23afa53.zip | |
george droid
| -rw-r--r-- | include/file/metadata.hpp | 54 | ||||
| -rw-r--r-- | include/file/world.hpp | 65 | ||||
| -rw-r--r-- | include/net/client.hpp | 2 | ||||
| -rw-r--r-- | include/net/conn.hpp | 90 | ||||
| -rw-r--r-- | include/net/packet.hpp | 27 | ||||
| -rw-r--r-- | include/net/server.hpp | 2 | ||||
| -rw-r--r-- | include/net/types.hpp | 189 | ||||
| -rw-r--r-- | include/util/bitset.hpp | 45 | ||||
| -rw-r--r-- | include/util/io.hpp | 202 | ||||
| -rw-r--r-- | include/version.hpp | 3 | ||||
| -rw-r--r-- | include/world/world.hpp | 12 | ||||
| -rw-r--r-- | src/kill_proxy.cpp | 37 |
12 files changed, 424 insertions, 304 deletions
diff --git a/include/file/metadata.hpp b/include/file/metadata.hpp new file mode 100644 index 0000000..8b9ec4f --- /dev/null +++ b/include/file/metadata.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "util/bitset.hpp" +#include "util/io.hpp" +#include <cstdint> +#include <cstdio> +#include <stdexcept> + +namespace file { + +enum class filetype : uint8_t { + Map = 1, + World, + Player, + TOTAL, +}; + +struct metadata { + static constexpr auto magic = 27981915666277746uL; + static constexpr auto magic_mask = 0xFFFFFFFFFFFFFFuL; + static constexpr auto size = 20; + + enum class flags { + Favorite, + }; + + filetype ftype; + uint32_t revision; + bitset<64> flags; + + template <class T> + ssize_t read(io::serialized_io<T>& io) + { + uint64_t filemagic; + auto bytes = io.read(filemagic); + if ((filemagic & magic_mask) != magic) { + throw std::runtime_error("not a relogic file"); + } + + uint8_t type = ((filemagic >> 56) & 0xFF); + if (type == 0 || type >= int(filetype::TOTAL)) { + throw std::runtime_error("unknown file type"); + } + + ftype = filetype(type); + + bytes += io.read(revision); + bytes += io.read(flags); + + return bytes; + } +}; + +}
\ No newline at end of file diff --git a/include/file/world.hpp b/include/file/world.hpp new file mode 100644 index 0000000..300a3e0 --- /dev/null +++ b/include/file/world.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include <array> +#include <cstdint> +#include <cstdio> +#include <fcntl.h> +#include <filesystem> +#include <fstream> +#include <iostream> +#include <stdexcept> +#include <string> +#include <vector> + +#include "file/metadata.hpp" +#include "util/bitset.hpp" +#include "util/io.hpp" +#include "version.hpp" + +namespace file { + +class world { + int32_t version; + // metadata meta; + + std::vector<uint32_t> positions; + bitset<0> importance; + + int32_t seed; + std::array<uint8_t, 16> guid; + int32_t worldID; + int32_t world_left; + int32_t world_right; + int32_t world_top; + int32_t world_botton; + int32_t height; + int32_t width; + +public: + void load_header(std::ifstream& f) + { + } + + world(std::string filename) + { + if (!std::filesystem::exists(filename)) { + throw std::runtime_error("file not found"); + } + auto rw = io::serialized_io(io::file_io { filename }); + + rw.read(version); + if (version <= 88 || version > terraria_version) { + throw std::runtime_error("invalid world file version"); + } + std::cout << version << std::endl; + metadata meta; + + rw.read(meta); + + if (meta.ftype != filetype::World) { + throw std::runtime_error("expected world file"); + } + } +}; + +}
\ No newline at end of file diff --git a/include/net/client.hpp b/include/net/client.hpp index 23d250b..87b0ea1 100644 --- a/include/net/client.hpp +++ b/include/net/client.hpp @@ -23,7 +23,7 @@ namespace net { class client { - struct sockaddr_in server_address { }; + struct sockaddr_in server_address {}; public: client(std::string hostname, int port) diff --git a/include/net/conn.hpp b/include/net/conn.hpp index 95992ed..6f57293 100644 --- a/include/net/conn.hpp +++ b/include/net/conn.hpp @@ -9,15 +9,17 @@ #include <netinet/in.h> #include "net/packet.hpp" +#include "util/io.hpp" namespace net { class conn { - int sock_fd; + io::serialized_io<io::file_io> rw; sockaddr_in conn_addr; socklen_t conn_addr_size; - using handler_list = std::vector<std::function<bool(std::span<uint8_t>)>>; + using handler = std::function<bool(std::span<uint8_t>)>; + using handler_list = std::vector<handler>; std::array<handler_list, 256> handlers; std::map<uint16_t, handler_list> netmodule_handlers; @@ -27,72 +29,20 @@ private: uint16_t packet_size; uint8_t id; - recv(packet_size); - recv(id); + rw.read(packet_size); + rw.read(id); return { id, packet_size - 3 }; } public: conn(int sock_fd, sockaddr_in conn_addr, socklen_t conn_addr_size) - : sock_fd(sock_fd) + : rw(io::file_io(sock_fd)) , conn_addr(conn_addr) , conn_addr_size(conn_addr_size) { } - ~conn() - { - close(sock_fd); - } - - ssize_t send(std::span<uint8_t> data) - { - auto bytes = ::send(sock_fd, data.data(), data.size_bytes(), 0); - if (bytes < 0) { - throw std::runtime_error(strerror(errno)); - } - return bytes; - } - - template <std::integral T> - ssize_t send(T t) - { - t = to_little(t); - auto bytes = ::send(sock_fd, &t, sizeof(T), 0); - if (bytes < 0) { - throw std::runtime_error(strerror(errno)); - } - return bytes; - } - - ssize_t recv(std::span<uint8_t> data) - { - if (data.size_bytes() == 0) { - return 0; - } - - auto bytes = ::recv(sock_fd, data.data(), data.size_bytes(), 0); - if (bytes <= 0) { - throw std::runtime_error(strerror(errno)); - } - return bytes; - } - - template <std::integral T> - ssize_t recv(T& t) - { - auto bytes = ::recv(sock_fd, &t, sizeof(T), 0); - if (bytes <= 0) { - throw std::runtime_error(strerror(errno)); - } - t = to_little(t); - if (bytes != sizeof(T)) { - throw std::runtime_error(strerror(errno)); - } - return bytes; - } - template <packet::packet T> void expect_packet(T& t) { @@ -101,11 +51,17 @@ public: if (len > 0) { std::vector<uint8_t> payload(len); - recv(payload); + rw.read(payload); decode_packet(payload, t); } } + template <class H> + void reg_handler(int id, H h) + { + handlers[id].push_back(h); + } + template <packet::packet P, typename H> void reg_handler(H h) { @@ -133,13 +89,21 @@ public: template <packet::packet T> void send_packet(T p) { - auto payload = encode_packet(p); + send_packet(T::packet_id, encode_packet(p)); + } - send(int16_t(payload.size() + 3)); - send(T::packet_id); + void send_packet(uint8_t id, std::span<uint8_t> payload) + { + std::vector<uint8_t> packet_data; + auto buffer = io::serialized_io(io::buffered_io(packet_data)); + + buffer.write(int16_t(payload.size() + 3)); + buffer.write(id); if (payload.size() > 0) { - send(payload); + buffer.write(payload); } + + rw.write(packet_data); } bool handle_netmodule(std::span<uint8_t> payload) @@ -170,7 +134,7 @@ public: } std::vector<uint8_t> payload(len); - recv(payload); + rw.read(payload); // got netmodule if (id == 82) { diff --git a/include/net/packet.hpp b/include/net/packet.hpp index 117fc15..950601e 100644 --- a/include/net/packet.hpp +++ b/include/net/packet.hpp @@ -17,6 +17,7 @@ #include <zlib.h> #include "types.hpp" +#include "util/io.hpp" namespace net { @@ -27,28 +28,22 @@ using packet_header = std::tuple<uint8_t, uint16_t>; template <packet T> void decode_packet(std::span<uint8_t> payload, T& t) { - std::size_t offset = 0; + std::size_t len = 0; - boost::pfr::for_each_field(t, [&payload, &offset](auto& field, int i) { - if (offset >= payload.size()) { - throw std::runtime_error("Received invalid packet"); - } - offset += decode_field(payload.subspan(offset), field); - }); + auto rd = io::serialized_io(io::buffered_io { payload }); + len = rd.read(t); - if (offset < payload.size()) { - throw std::runtime_error(std::format("{}: {}", offset, payload.size())); + if (len != payload.size()) { + throw std::runtime_error(std::format("{}: {}", len, payload.size())); } } template <packet T> -std::vector<uint8_t> encode_raw_packet(T& t) +std::vector<uint8_t> encode_packet_impl(T& t) { std::vector<uint8_t> payload; - - boost::pfr::for_each_field(t, [&payload](auto& field) { - encode_field(payload, field); - }); + auto wt = io::serialized_io(io::buffered_io { payload }); + wt.write(t); return payload; } @@ -56,7 +51,7 @@ std::vector<uint8_t> encode_raw_packet(T& t) template <packet T> std::vector<uint8_t> encode_packet(T& t) { - return encode_raw_packet(t); + return encode_packet_impl(t); } template <compressed_packet T> @@ -69,7 +64,7 @@ std::vector<uint8_t> encode_packet(T& t) int ret, flush; z_stream strm; - auto raw_payload = encode_raw_packet(t); + auto raw_payload = encode_packet_impl(t); strm.zalloc = Z_NULL; strm.zfree = Z_NULL; diff --git a/include/net/server.hpp b/include/net/server.hpp index 30791fc..9b5e7c5 100644 --- a/include/net/server.hpp +++ b/include/net/server.hpp @@ -23,7 +23,7 @@ namespace net { class server { - struct sockaddr_in server_address { }; + struct sockaddr_in server_address {}; int sock_fd; public: diff --git a/include/net/types.hpp b/include/net/types.hpp index a4a8c3b..13c96e0 100644 --- a/include/net/types.hpp +++ b/include/net/types.hpp @@ -1,13 +1,11 @@ #pragma once -#include <string> -#include <vector> #include <array> -#include <span> #include <cstring> +#include <string> +#include <unistd.h> #include "util/bitset.hpp" -#include "util/endian.hpp" #include "version.hpp" namespace net { @@ -76,183 +74,6 @@ struct death_reason { std::string custom; }; -void encode_field(std::vector<uint8_t>& data, const std::string& f) -{ - auto bytes = reinterpret_cast<uint8_t const*>(f.data()); - data.insert(data.end(), uint8_t(f.size())); - data.insert(data.end(), bytes, bytes + f.size()); -} - -template <std::integral T> -void encode_field(std::vector<uint8_t>& data, T f) -{ - auto fixed = to_little(f); - auto* p = reinterpret_cast<uint8_t const*>(&fixed); - data.insert(data.end(), p, p + sizeof(T)); -} - -template <std::floating_point T> -void encode_field(std::vector<uint8_t>& data, T f) -{ - auto x = reinterpret_cast<uint8_t*>(&f); - data.insert(data.end(), x, x + sizeof(T)); -} - -void encode_field(std::vector<uint8_t>& data, nstring& s) -{ - data.insert(data.end(), uint8_t(s.mode)); - encode_field(data, s.s); -} - -void encode_field(std::vector<uint8_t>& data, const rgb& f) -{ - encode_field(data, f.r); - encode_field(data, f.g); - encode_field(data, f.b); -} - -template <typename T, std::size_t sz> -void encode_field(std::vector<uint8_t>& data, std::array<T, sz>& f) -{ - for (auto& e : f) { - encode_field(data, e); - } -} - -template <typename T> -void encode_field(std::vector<uint8_t>& data, std::vector<T>& f) -{ - for (auto& e : f) { - encode_field(data, e); - } -} - -template <typename T> -void encode_field(std::vector<uint8_t>& data, std::span<T>& f) -{ - for (auto& e : f) { - encode_field(data, e); - } -} - -template <unsigned int bits> -void encode_field(std::vector<uint8_t>& data, bitset<bits>& f) -{ - for (auto& e : f) { - encode_field(data, e); - } -} - -void encode_field(std::vector<uint8_t>& data, death_reason& f) -{ - encode_field(data, f.reasons); - if (f.reasons[death_reason::reason::Player]) { - encode_field(data, f.player_index); - } - if (f.reasons[death_reason::reason::NPC]) { - encode_field(data, f.npc_index); - } - if (f.reasons[death_reason::reason::Projectile]) { - encode_field(data, f.projectile_index); - } - if (f.reasons[death_reason::reason::Other]) { - encode_field(data, f.other_index); - } - if (f.reasons[death_reason::reason::ProjectileType]) { - encode_field(data, f.projectile_type); - } - if (f.reasons[death_reason::reason::Item]) { - encode_field(data, f.item_type); - } - if (f.reasons[death_reason::reason::ItemPrefix]) { - encode_field(data, f.item_prefix); - } - if (f.reasons[death_reason::reason::Custom]) { - encode_field(data, f.custom); - } -} - -ssize_t decode_field(std::span<uint8_t> data, std::string& f) -{ - assert(data.size() > 0); - uint8_t size = uint8_t(data[0]); // FIXME: varint - - assert(data.size() >= std::size_t(size + 1)); - - f.resize(size); - f.assign(reinterpret_cast<const char*>(data.data() + 1 /* FIXME: varint*/), size); - return size + 1; -} - -template <std::integral T> -ssize_t decode_field(std::span<uint8_t> data, T& f) -{ - assert(data.size() >= sizeof(T)); - std::memcpy(&f, data.data(), sizeof(T)); - f = to_little(f); - return sizeof(T); -} - -ssize_t decode_field(std::span<uint8_t> data, std::vector<uint8_t>& f) -{ - f.insert(f.begin(), data.begin(), data.end()); - return f.size(); -} - -template <typename T, std::size_t sz> -ssize_t decode_field(std::span<uint8_t> data, std::array<T, sz>& f) -{ - assert(data.size() >= sizeof(T) * sz); - - ssize_t offset = 0; - for (auto& e : f) { - offset += decode_field(data.subspan(offset), e); - } - - return offset; -} - -ssize_t decode_field(std::span<uint8_t> data, death_reason& f) -{ - int offset = 0; - offset += decode_field(data, f.reasons); - if (f.reasons[0]) { - offset += decode_field(data.subspan(offset), f.player_index); - } - if (f.reasons[1]) { - offset += decode_field(data.subspan(offset), f.npc_index); - } - if (f.reasons[2]) { - offset += decode_field(data.subspan(offset), f.projectile_index); - } - if (f.reasons[3]) { - offset += decode_field(data.subspan(offset), f.other_index); - } - if (f.reasons[4]) { - offset += decode_field(data.subspan(offset), f.projectile_type); - } - if (f.reasons[5]) { - offset += decode_field(data.subspan(offset), f.item_type); - } - if (f.reasons[6]) { - offset += decode_field(data.subspan(offset), f.item_prefix); - } - if (f.reasons[7]) { - offset += decode_field(data.subspan(offset), f.custom); - } - - return offset; -} -ssize_t decode_field(std::span<uint8_t> data, rgb& f) -{ - assert(data.size() >= 3); - - f.r = uint8_t(data[0]); - f.g = uint8_t(data[1]); - f.b = uint8_t(data[2]); - return 3; -} - struct conn_request { static constexpr uint8_t packet_id = 1; @@ -480,12 +301,6 @@ struct damage_player { int8_t cooldown_counter; }; -template <uint8_t id> -struct raw { - static constexpr uint8_t packet_id = id; - std::vector<uint8_t> data; -}; - inline nstring operator""_ns(const char* str, std::size_t) { return nstring(str); diff --git a/include/util/bitset.hpp b/include/util/bitset.hpp index de5bd49..cd1dbd0 100644 --- a/include/util/bitset.hpp +++ b/include/util/bitset.hpp @@ -3,6 +3,7 @@ #include <array> #include <cassert> #include <cstdint> +#include <vector> constexpr int bits_ceil(int bits) { @@ -50,4 +51,48 @@ public: assert(i < flags); return bit_ref(*this, i / 8, (1 << (i % 8))); } +}; + +// FIXME: ?? +template <> +struct bitset<0> : std::vector<std::uint8_t> { + using container_type = std::vector<std::uint8_t>; + +private: + struct bit_ref { + bitset<0>& a; + int referenced_byte; + uint8_t referenced_bit_mask; + + bool operator=(bool b) + { + if (b) + a.at(referenced_byte) |= referenced_bit_mask; + else + a.at(referenced_byte) &= ~referenced_bit_mask; + return b; + } + + constexpr operator bool() const + { + return (a.at(referenced_byte) & referenced_bit_mask) > 0; + } + }; + +public: + template <class T> + constexpr bool operator[](T idx) const + { + unsigned int i = (unsigned int)(idx); + assert(i < container_type::size()); + return (container_type::at(i / 8) & (1 << (i % 8))) > 0; + } + + template <class T> + bit_ref operator[](T idx) + { + unsigned int i = (unsigned int)(idx); + assert(i < container_type::size()); + return bit_ref(*this, i / 8, (1 << (i % 8))); + } };
\ No newline at end of file diff --git a/include/util/io.hpp b/include/util/io.hpp new file mode 100644 index 0000000..8cfaf4c --- /dev/null +++ b/include/util/io.hpp @@ -0,0 +1,202 @@ +#pragma once + +#include "util/endian.hpp" +#include <boost/pfr/core.hpp> +#include <cassert> +#include <concepts> +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <fcntl.h> +#include <stdexcept> +#include <string> +#include <unistd.h> + +namespace io { + +template <class T, class K> +concept custom_write = requires(T t) { + { t.write(K()) } -> std::same_as<ssize_t>; +}; +template <class T, class K> +concept custom_read = requires(T& t, K& k) { + { t.read(k) } -> std::same_as<ssize_t>; +}; + +template <class T> +concept container = requires(T& t) { + { t.data() }; + { t.size() }; + { t.begin() }; + { t.end() }; +}; + +class file_io { + int fd; + +public: + file_io(int fd) + : fd(fd) + { + } + + file_io(std::string filename) + : fd(open(filename.c_str(), O_RDWR)) + { + if (fd < 0) + throw std::runtime_error(strerror(errno)); + } + + //~file_io() + //{ + // ::close(fd); + //} + + ssize_t read_data(char* buf, size_t nbytes) + { + auto bytes = ::read(fd, buf, nbytes); + assert(bytes >= 0); + return bytes; + } + + ssize_t write_data(const char* buf, size_t nbytes) + { + auto bytes = ::write(fd, buf, nbytes); + assert(bytes >= 0); + return bytes; + } +}; + +template <container T> +class buffered_io { + size_t cursor = 0; + T& buffer; + +public: + buffered_io(T& buffer) + : buffer(buffer) + { + } + + ssize_t read_data(char* buf, size_t nbytes) + { + auto bytes = nbytes > (buffer.size() - cursor) ? (buffer.size() - cursor) : nbytes; + + memcpy(buf, buffer.data() + cursor, bytes); + cursor += bytes; + return bytes; + } + + ssize_t write_data(const char* buf, size_t nbytes) + { + buffer.insert(buffer.end(), buf, buf + nbytes); + return nbytes; + } +}; + +template <class I> +class serialized_io { + I io; + +public: + serialized_io(I io) + : io(io) + { + } + + template <std::integral T> + ssize_t read(T& f) + { + auto bytes = io.read_data(reinterpret_cast<char*>(&f), sizeof(T)); + f = to_little(f); + return bytes; + } + + template <std::floating_point T> + ssize_t read(T& f) + { + auto bytes = io.read_data(reinterpret_cast<char*>(&f), sizeof(T)); + f = to_little(f); + return bytes; + } + + ssize_t read(std::string& f) + { + // FIXME: varint + uint8_t size; + auto bytes = read(size); + + f.resize(size); + bytes += io.read_data(f.data(), size); + return bytes; + } + + template <custom_read<serialized_io<I>> T> + ssize_t read(T& f) + { + return f.read(*this); + } + + template <container T> + ssize_t read(T& f) + { + return io.read_data(reinterpret_cast<char*>(f.data()), f.size() * sizeof(typename T::value_type)); + } + + template <class T> + ssize_t read(T& f) + { + ssize_t bytes = 0; + boost::pfr::for_each_field(f, [this, &bytes](auto& field) { + bytes += read(field); + }); + return bytes; + } + + template <std::integral T> + ssize_t write(const T f) + { + auto x = to_little(f); + return io.write_data(reinterpret_cast<char*>(&x), sizeof(T)); + } + + template <std::floating_point T> + ssize_t write(const T f) + { + // FIXME: endianness + return io.write_data(reinterpret_cast<char*>(&f), sizeof(T)); + } + + ssize_t write(const std::string f) + { + // FIXME: varint + auto bytes = write(f.size()); + bytes += io.write_data(f.data(), f.size()); + return bytes; + } + + template <container T> + ssize_t write(const T f) + { + return io.write_data(reinterpret_cast<const char*>(f.data()), f.size() * sizeof(typename T::value_type)); + } + + template <custom_write<serialized_io<I>> T> + ssize_t write(T& f) + { + return f.write(*this); + } + + template <class T> + ssize_t write(const T f) + { + ssize_t bytes = 0; + boost::pfr::for_each_field(f, [this, &bytes](auto field) { + bytes += write(field); + }); + return bytes; + } +}; + +}
\ No newline at end of file diff --git a/include/version.hpp b/include/version.hpp index b5f4856..b5ec59f 100644 --- a/include/version.hpp +++ b/include/version.hpp @@ -1,3 +1,4 @@ #pragma once -constexpr auto max_buffs = 44;
\ No newline at end of file +constexpr auto max_buffs = 44; +constexpr auto terraria_version = 279;
\ No newline at end of file diff --git a/include/world/world.hpp b/include/world/world.hpp deleted file mode 100644 index 1b2481b..0000000 --- a/include/world/world.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include <cstdint> - -namespace world { - -class world { - int32_t version; - -}; - -}
\ No newline at end of file diff --git a/src/kill_proxy.cpp b/src/kill_proxy.cpp index e72848f..4e0ecd1 100644 --- a/src/kill_proxy.cpp +++ b/src/kill_proxy.cpp @@ -1,3 +1,5 @@ +#include <cstdint> +#include <span> #include <thread> #include "net/client.hpp" @@ -5,14 +7,14 @@ #include "net/server.hpp" #include "net/types.hpp" -template <typename T, T... ints> -void forward_packets(net::conn& dst, net::conn& src, std::integer_sequence<T, ints...> int_seq) +void forward_packets(net::conn& dst, net::conn& src) { - (src.reg_handler<net::packet::raw<ints+1>>([&dst](auto& a) { - dst.send_packet(a); - return true; - }), - ...); + for (int i = 1; i < 255; i++) { + src.reg_handler(i, [&dst, i](std::span<uint8_t> data) { + dst.send_packet(i, data); + return true; + }); + } } int main(int argc, char* argv[]) @@ -32,22 +34,8 @@ int main(int argc, char* argv[]) return false; }); - proxy_conn.reg_handler<net::packet::damage_player>([&slot, &server_conn](auto& dmg) { - if (dmg.client_id != slot) { - dmg.damage *= 100; - dmg.reason.reasons.at(0) = 0; - - dmg.reason.reasons[net::packet::death_reason::reason::Custom] = true; - dmg.reason.custom = "killed by server insecurity"; - - server_conn.send_packet(dmg); - return true; - } - return false; - }); - - forward_packets(server_conn, proxy_conn, std::make_integer_sequence<uint8_t, 255> {}); - forward_packets(proxy_conn, server_conn, std::make_integer_sequence<uint8_t, 255> {}); + forward_packets(server_conn, proxy_conn); + forward_packets(proxy_conn, server_conn); auto handle_loop = [](net::conn& c) { for (;;) { @@ -58,5 +46,8 @@ int main(int argc, char* argv[]) std::thread server_thread(handle_loop, std::ref(server_conn)); std::thread proxy_thread(handle_loop, std::ref(proxy_conn)); + while (1) { + } + return 0; }
\ No newline at end of file |
