summaryrefslogtreecommitdiff
path: root/src/wire/packets.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wire/packets.hpp')
-rw-r--r--src/wire/packets.hpp582
1 files changed, 582 insertions, 0 deletions
diff --git a/src/wire/packets.hpp b/src/wire/packets.hpp
new file mode 100644
index 0000000..a233344
--- /dev/null
+++ b/src/wire/packets.hpp
@@ -0,0 +1,582 @@
+#pragma once
+// packets.hpp - palatable socket buffer accessors
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include <vector>
+
+#include "../ints/little.hpp"
+
+#include "../compat/cast.hpp"
+
+#include "../proto2/fwd.hpp"
+
+#include "../net/socket.hpp"
+
+
+namespace tmwa
+{
+struct Buffer
+{
+ std::vector<Byte> bytes;
+};
+
+enum class RecvResult
+{
+ Incomplete,
+ Complete,
+ Error,
+};
+
+enum class SendResult
+{
+ Success,
+ Fail,
+};
+
+
+size_t packet_avail(Session *s);
+void packet_dump(io::WriteFile& out, Session *s);
+
+bool packet_fetch(Session *s, size_t offset, Byte *data, size_t sz);
+void packet_discard(Session *s, size_t sz);
+bool packet_send(Session *s, const Byte *data, size_t sz);
+
+inline
+bool packet_peek_id(Session *s, uint16_t *packet_id)
+{
+ Little16 id;
+ bool okay = packet_fetch(s, 0, reinterpret_cast<Byte *>(&id), 2);
+ if (okay)
+ {
+ if (!network_to_native(packet_id, id))
+ {
+ s->set_eof();
+ return false;
+ }
+ }
+ return okay;
+}
+
+inline
+void send_buffer(Session *s, const Buffer& buffer)
+{
+ bool ok = !buffer.bytes.empty() && packet_send(s, buffer.bytes.data(), buffer.bytes.size());
+ if (!ok)
+ s->set_eof();
+}
+
+template<uint16_t id>
+__attribute__((warn_unused_result))
+RecvResult net_recv_fpacket(Session *s, NetPacket_Fixed<id>& fixed)
+{
+ bool ok = packet_fetch(s, 0, reinterpret_cast<Byte *>(&fixed), sizeof(NetPacket_Fixed<id>));
+ if (ok)
+ {
+ packet_discard(s, sizeof(NetPacket_Fixed<id>));
+ return RecvResult::Complete;
+ }
+ return RecvResult::Incomplete;
+}
+
+template<uint16_t id>
+__attribute__((warn_unused_result))
+RecvResult net_recv_ppacket(Session *s, NetPacket_Payload<id>& payload)
+{
+ bool ok = packet_fetch(s, 0, reinterpret_cast<Byte *>(&payload), sizeof(NetPacket_Payload<id>));
+ if (ok)
+ {
+ packet_discard(s, sizeof(NetPacket_Payload<id>));
+ return RecvResult::Complete;
+ }
+ return RecvResult::Incomplete;
+}
+
+template<uint16_t id>
+__attribute__((warn_unused_result))
+RecvResult net_recv_vpacket(Session *s, NetPacket_Head<id>& head, std::vector<NetPacket_Repeat<id>>& repeat)
+{
+ bool ok = packet_fetch(s, 0, reinterpret_cast<Byte *>(&head), sizeof(NetPacket_Head<id>));
+ if (ok)
+ {
+ Packet_Head<id> nat;
+ if (!network_to_native(&nat, head))
+ return RecvResult::Error;
+ if (packet_avail(s) < nat.magic_packet_length)
+ return RecvResult::Incomplete;
+ if (nat.magic_packet_length < sizeof(NetPacket_Head<id>))
+ return RecvResult::Error;
+ size_t bytes_repeat = nat.magic_packet_length - sizeof(NetPacket_Head<id>);
+ if (bytes_repeat % sizeof(NetPacket_Repeat<id>))
+ return RecvResult::Error;
+ repeat.resize(bytes_repeat / sizeof(NetPacket_Repeat<id>));
+ if (packet_fetch(s, sizeof(NetPacket_Head<id>), reinterpret_cast<Byte *>(repeat.data()), bytes_repeat))
+ {
+ packet_discard(s, nat.magic_packet_length);
+ return RecvResult::Complete;
+ }
+ return RecvResult::Incomplete;
+ }
+ return RecvResult::Incomplete;
+}
+
+template<uint16_t id>
+__attribute__((warn_unused_result))
+RecvResult net_recv_opacket(Session *s, NetPacket_Head<id>& head, bool *has_opt, NetPacket_Option<id>& opt)
+{
+ bool ok = packet_fetch(s, 0, reinterpret_cast<Byte *>(&head), sizeof(NetPacket_Head<id>));
+ if (ok)
+ {
+ Packet_Head<id> nat;
+ if (!network_to_native(&nat, head))
+ return RecvResult::Error;
+ if (packet_avail(s) < nat.magic_packet_length)
+ return RecvResult::Incomplete;
+ if (nat.magic_packet_length < sizeof(NetPacket_Head<id>))
+ return RecvResult::Error;
+ size_t bytes_repeat = nat.magic_packet_length - sizeof(NetPacket_Head<id>);
+ if (bytes_repeat % sizeof(NetPacket_Option<id>))
+ return RecvResult::Error;
+ size_t has_opt_pls = bytes_repeat / sizeof(NetPacket_Option<id>);
+ if (has_opt_pls > 1)
+ return RecvResult::Error;
+ *has_opt = has_opt_pls;
+ if (!*has_opt || packet_fetch(s, sizeof(NetPacket_Head<id>), reinterpret_cast<Byte *>(&opt), sizeof(NetPacket_Option<id>)))
+ {
+ packet_discard(s, nat.magic_packet_length);
+ return RecvResult::Complete;
+ }
+ return RecvResult::Incomplete;
+ }
+ return RecvResult::Incomplete;
+}
+
+
+template<uint16_t id, uint16_t size>
+Buffer create_fpacket(const Packet_Fixed<id>& fixed)
+{
+ static_assert(id == Packet_Fixed<id>::PACKET_ID, "Packet_Fixed<id>::PACKET_ID");
+ static_assert(size == sizeof(NetPacket_Fixed<id>), "sizeof(NetPacket_Fixed<id>)");
+
+ Buffer buf;
+ buf.bytes.resize(sizeof(NetPacket_Fixed<id>));
+ auto& net_fixed = reinterpret_cast<NetPacket_Fixed<id>&>(
+ *(buf.bytes.begin() + 0));
+ if (!native_to_network(&net_fixed, fixed))
+ {
+ return Buffer();
+ }
+ return buf;
+}
+
+template<uint16_t id>
+Buffer create_ppacket(Packet_Payload<id>& payload)
+{
+ static_assert(id == Packet_Payload<id>::PACKET_ID, "Packet_Payload<id>::PACKET_ID");
+
+ if (id != 0x8000)
+ payload.magic_packet_length = sizeof(NetPacket_Payload<id>);
+
+ Buffer buf;
+ buf.bytes.resize(sizeof(NetPacket_Payload<id>));
+ auto& net_payload = reinterpret_cast<NetPacket_Payload<id>&>(
+ *(buf.bytes.begin() + 0));
+ if (!native_to_network(&net_payload, payload))
+ {
+ return Buffer();
+ }
+ return buf;
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+Buffer create_vpacket(Packet_Head<id>& head, const std::vector<Packet_Repeat<id>>& repeat)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "sizeof(NetPacket_Head<id>)");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "sizeof(NetPacket_Repeat<id>)");
+
+ // since these are already allocated, can't overflow address space
+ size_t total_size = sizeof(NetPacket_Head<id>) + repeat.size() * sizeof(NetPacket_Repeat<id>);
+ // truncates
+ head.magic_packet_length = total_size;
+ if (head.magic_packet_length != total_size)
+ {
+ return Buffer();
+ }
+
+ Buffer buf;
+ buf.bytes.resize(total_size);
+ auto& net_head = reinterpret_cast<NetPacket_Head<id>&>(
+ *(buf.bytes.begin() + 0));
+ if (!native_to_network(&net_head, head))
+ {
+ return Buffer();
+ }
+ for (size_t i = 0; i < repeat.size(); ++i)
+ {
+ auto& net_repeat_i = reinterpret_cast<NetPacket_Repeat<id>&>(
+ *(buf.bytes.begin()
+ + sizeof(NetPacket_Head<id>)
+ + i * sizeof(NetPacket_Repeat<id>)));
+ if (!native_to_network(&net_repeat_i, repeat[i]))
+ {
+ return Buffer();
+ }
+ }
+ return buf;
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t optsize>
+Buffer create_opacket(Packet_Head<id>& head, bool has_opt, const Packet_Option<id>& opt)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "sizeof(NetPacket_Head<id>)");
+ static_assert(id == Packet_Option<id>::PACKET_ID, "Packet_Option<id>::PACKET_ID");
+ static_assert(optsize == sizeof(NetPacket_Option<id>), "sizeof(NetPacket_Option<id>)");
+
+ // since these are already allocated, can't overflow address space
+ size_t total_size = sizeof(NetPacket_Head<id>) + has_opt * sizeof(NetPacket_Option<id>);
+ // truncates
+ head.magic_packet_length = total_size;
+ if (head.magic_packet_length != total_size)
+ {
+ return Buffer();
+ }
+
+ Buffer buf;
+ buf.bytes.resize(total_size);
+
+ auto& net_head = reinterpret_cast<NetPacket_Head<id>&>(
+ *(buf.bytes.begin() + 0));
+ if (!native_to_network(&net_head, head))
+ {
+ return Buffer();
+ }
+ if (has_opt)
+ {
+ auto& net_opt = reinterpret_cast<NetPacket_Option<id>&>(
+ *(buf.bytes.begin()
+ + sizeof(NetPacket_Head<id>)));
+ if (!native_to_network(&net_opt, opt))
+ {
+ return Buffer();
+ }
+ }
+
+ return buf;
+}
+
+template<uint16_t id, uint16_t size>
+void send_fpacket(Session *s, const Packet_Fixed<id>& fixed)
+{
+ Buffer pkt = create_fpacket<id, size>(fixed);
+ send_buffer(s, pkt);
+}
+
+template<uint16_t id>
+void send_ppacket(Session *s, Packet_Payload<id>& payload)
+{
+ Buffer pkt = create_ppacket<id>(payload);
+ send_buffer(s, pkt);
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+void send_vpacket(Session *s, Packet_Head<id>& head, const std::vector<Packet_Repeat<id>>& repeat)
+{
+ Buffer pkt = create_vpacket<id, headsize, repeatsize>(head, repeat);
+ send_buffer(s, pkt);
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t optsize>
+void send_opacket(Session *s, Packet_Head<id>& head, bool has_opt, const Packet_Option<id>& opt)
+{
+ Buffer pkt = create_opacket<id, headsize, optsize>(head, has_opt, opt);
+ send_buffer(s, pkt);
+}
+
+template<uint16_t id, uint16_t size>
+__attribute__((warn_unused_result))
+RecvResult recv_fpacket(Session *s, Packet_Fixed<id>& fixed)
+{
+ static_assert(id == Packet_Fixed<id>::PACKET_ID, "Packet_Fixed<id>::PACKET_ID");
+ static_assert(size == sizeof(NetPacket_Fixed<id>), "NetPacket_Fixed<id>");
+
+ NetPacket_Fixed<id> net_fixed;
+ RecvResult rv = net_recv_fpacket(s, net_fixed);
+ if (rv == RecvResult::Complete)
+ {
+ if (!network_to_native(&fixed, net_fixed))
+ return RecvResult::Error;
+ assert (fixed.magic_packet_id == Packet_Fixed<id>::PACKET_ID);
+ }
+ return rv;
+}
+
+template<uint16_t id>
+__attribute__((warn_unused_result))
+RecvResult recv_ppacket(Session *s, Packet_Payload<id>& payload)
+{
+ static_assert(id == Packet_Payload<id>::PACKET_ID, "Packet_Payload<id>::PACKET_ID");
+
+ NetPacket_Payload<id> net_payload;
+ RecvResult rv = net_recv_ppacket(s, net_payload);
+ if (rv == RecvResult::Complete)
+ {
+ if (!network_to_native(&payload, net_payload))
+ return RecvResult::Error;
+ assert (payload.magic_packet_id == Packet_Payload<id>::PACKET_ID);
+ if (id == 0x8000)
+ {
+ // 0x8000 is special
+ if (packet_avail(s) < payload.magic_packet_length)
+ return RecvResult::Incomplete;
+ payload.magic_packet_length = 4;
+ return RecvResult::Complete;
+ }
+ if (payload.magic_packet_length != sizeof(net_payload))
+ return RecvResult::Error;
+ }
+ return rv;
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+__attribute__((warn_unused_result))
+RecvResult recv_vpacket(Session *s, Packet_Head<id>& head, std::vector<Packet_Repeat<id>>& repeat)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "NetPacket_Head<id>");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "NetPacket_Repeat<id>");
+
+ NetPacket_Head<id> net_head;
+ std::vector<NetPacket_Repeat<id>> net_repeat;
+ RecvResult rv = net_recv_vpacket(s, net_head, net_repeat);
+ if (rv == RecvResult::Complete)
+ {
+ if (!network_to_native(&head, net_head))
+ return RecvResult::Error;
+ assert (head.magic_packet_id == Packet_Head<id>::PACKET_ID);
+
+ repeat.resize(net_repeat.size());
+ for (size_t i = 0; i < net_repeat.size(); ++i)
+ {
+ if (!network_to_native(&repeat[i], net_repeat[i]))
+ return RecvResult::Error;
+ }
+ }
+ return rv;
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t optsize>
+__attribute__((warn_unused_result))
+RecvResult recv_opacket(Session *s, Packet_Head<id>& head, bool *has_opt, Packet_Option<id>& opt)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "NetPacket_Head<id>");
+ static_assert(id == Packet_Option<id>::PACKET_ID, "Packet_Option<id>::PACKET_ID");
+ static_assert(optsize == sizeof(NetPacket_Option<id>), "NetPacket_Option<id>");
+
+ NetPacket_Head<id> net_head;
+ NetPacket_Option<id> net_opt;
+ RecvResult rv = net_recv_opacket(s, net_head, has_opt, net_opt);
+ if (rv == RecvResult::Complete)
+ {
+ if (!network_to_native(&head, net_head))
+ return RecvResult::Error;
+ assert (head.magic_packet_id == Packet_Head<id>::PACKET_ID);
+
+ if (*has_opt)
+ {
+ if (!network_to_native(&opt, net_opt))
+ return RecvResult::Error;
+ }
+ }
+ return rv;
+}
+
+
+// convenience for trailing strings
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+Buffer create_vpacket(Packet_Head<id>& head, const XString& repeat)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "NetPacket_Head<id>");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "NetPacket_Repeat<id>");
+ static_assert(repeatsize == 1, "repeatsize");
+
+ // since it's already allocated, it can't overflow address space
+ size_t total_length = sizeof(NetPacket_Head<id>) + (repeat.size() + 1) * sizeof(NetPacket_Repeat<id>);
+ head.magic_packet_length = total_length;
+ if (head.magic_packet_length != total_length)
+ {
+ return Buffer();
+ }
+
+ Buffer buf;
+ buf.bytes.resize(total_length);
+ auto& net_head = reinterpret_cast<NetPacket_Head<id>&>(
+ *(buf.bytes.begin() + 0));
+ std::vector<NetPacket_Repeat<id>> net_repeat(repeat.size() + 1);
+ if (!native_to_network(&net_head, head))
+ {
+ return Buffer();
+ }
+ for (size_t i = 0; i < repeat.size(); ++i)
+ {
+ auto& net_repeat_i = reinterpret_cast<NetPacket_Repeat<id>&>(
+ *(buf.bytes.begin()
+ + sizeof(NetPacket_Head<id>)
+ + i));
+ net_repeat_i.c = Byte{static_cast<uint8_t>(repeat[i])};
+ }
+ auto& net_repeat_repeat_size = reinterpret_cast<NetPacket_Repeat<id>&>(
+ *(buf.bytes.begin()
+ + sizeof(NetPacket_Head<id>)
+ + repeat.size()));
+ net_repeat_repeat_size.c = Byte{static_cast<uint8_t>('\0')};
+ return buf;
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+void send_vpacket(Session *s, Packet_Head<id>& head, const XString& repeat)
+{
+ Buffer pkt = create_vpacket<id, headsize, repeatsize>(head, repeat);
+ send_buffer(s, pkt);
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+__attribute__((warn_unused_result))
+RecvResult recv_vpacket(Session *s, Packet_Head<id>& head, AString& repeat)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "NetPacket_Head<id>");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "NetPacket_Repeat<id>");
+ static_assert(repeatsize == 1, "repeatsize");
+
+ NetPacket_Head<id> net_head;
+ std::vector<NetPacket_Repeat<id>> net_repeat;
+ RecvResult rv = net_recv_vpacket(s, net_head, net_repeat);
+ assert (head.magic_packet_id == Packet_Head<id>::PACKET_ID);
+ if (rv == RecvResult::Complete)
+ {
+ if (!network_to_native(&head, net_head))
+ return RecvResult::Error;
+ // reinterpret_cast is needed to correctly handle an empty vector
+ const char *begin = sign_cast<const char *>(net_repeat.data());
+ const char *end = begin + net_repeat.size();
+ end = std::find(begin, end, '\0');
+ repeat = XString(begin, end, nullptr);
+ }
+ return rv;
+}
+
+
+// if there is nothing in the head but the id and length, use the below
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+Buffer create_packet_repeatonly(const std::vector<Packet_Repeat<id>>& v)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "repeat headsize");
+ static_assert(headsize == 4, "repeat headsize");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "sizeof(NetPacket_Repeat<id>)");
+
+ Packet_Head<id> head;
+ return create_vpacket<id, 4, repeatsize>(head, v);
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+void send_packet_repeatonly(Session *s, const std::vector<Packet_Repeat<id>>& v)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "repeat headsize");
+ static_assert(headsize == 4, "repeat headsize");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "sizeof(NetPacket_Repeat<id>)");
+
+ Packet_Head<id> head;
+ send_vpacket<id, 4, repeatsize>(s, head, v);
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+__attribute__((warn_unused_result))
+RecvResult recv_packet_repeatonly(Session *s, std::vector<Packet_Repeat<id>>& v)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "repeat headsize");
+ static_assert(headsize == 4, "repeat headsize");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "sizeof(NetPacket_Repeat<id>)");
+
+ Packet_Head<id> head;
+ return recv_vpacket<id, 4, repeatsize>(s, head, v);
+}
+
+
+// and the combination of both of the above
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+Buffer create_packet_repeatonly(const XString& repeat)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "repeat headsize");
+ static_assert(headsize == 4, "repeat headsize");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "sizeof(NetPacket_Repeat<id>)");
+ static_assert(repeatsize == 1, "repeatsize");
+
+ Packet_Head<id> head;
+ return create_vpacket<id, 4, repeatsize>(head, repeat);
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+void send_packet_repeatonly(Session *s, const XString& repeat)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "repeat headsize");
+ static_assert(headsize == 4, "repeat headsize");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "sizeof(NetPacket_Repeat<id>)");
+ static_assert(repeatsize == 1, "repeatsize");
+
+ Packet_Head<id> head;
+ send_vpacket<id, 4, repeatsize>(s, head, repeat);
+}
+
+template<uint16_t id, uint16_t headsize, uint16_t repeatsize>
+__attribute__((warn_unused_result))
+RecvResult recv_packet_repeatonly(Session *s, AString& repeat)
+{
+ static_assert(id == Packet_Head<id>::PACKET_ID, "Packet_Head<id>::PACKET_ID");
+ static_assert(headsize == sizeof(NetPacket_Head<id>), "repeat headsize");
+ static_assert(headsize == 4, "repeat headsize");
+ static_assert(id == Packet_Repeat<id>::PACKET_ID, "Packet_Repeat<id>::PACKET_ID");
+ static_assert(repeatsize == sizeof(NetPacket_Repeat<id>), "sizeof(NetPacket_Repeat<id>)");
+ static_assert(repeatsize == 1, "repeatsize");
+
+ Packet_Head<id> head;
+ return recv_vpacket<id, 4, repeatsize>(s, head, repeat);
+}
+} // namespace tmwa