Add packet generation + handling automagically

This commit is contained in:
Joshua Ashton 2020-08-03 06:07:32 +01:00
parent e11f7ee3c5
commit e8244a3359
13 changed files with 413 additions and 294 deletions

View File

@ -2,15 +2,13 @@
#include "config/ServerProperties.h" #include "config/ServerProperties.h"
#include "PacketReader.h" #include "PacketReader.h"
#include <Windows.h>
namespace Feather namespace Feather
{ {
DedicatedServer::DedicatedServer(ServerProperties* properties) : DedicatedServer::DedicatedServer(ServerProperties* properties) :
m_properties(properties), m_properties(properties),
m_listener(properties->serverPort.GetValue(), this), m_listener(properties->serverPort.GetValue(), this),
m_status(), m_status()
m_protocol(*this)
{ {
m_status.descriptionText = properties->motd.GetValue(); m_status.descriptionText = properties->motd.GetValue();
m_status.maxPlayers = properties->maxPlayers.GetValue(); m_status.maxPlayers = properties->maxPlayers.GetValue();
@ -27,7 +25,7 @@ namespace Feather
while (offset != data.size()) while (offset != data.size())
{ {
auto reader = PacketReader(&data[offset]); auto reader = PacketReader(&data[offset]);
m_protocol.HandlePacket(client, reader); Protocol::ProcessPacket(*this, client, reader, client.GetContext().GetState());
offset += reader.Size(); offset += reader.Size();
} }
data.clear(); data.clear();
@ -56,4 +54,62 @@ namespace Feather
auto [clients, lock] = m_clients.borrow(); auto [clients, lock] = m_clients.borrow();
clients.remove_if([&](MinecraftClient& other) { return other.GetTCPClient().get() == client; }); clients.remove_if([&](MinecraftClient& other) { return other.GetTCPClient().get() == client; });
} }
using namespace Protocol;
void DedicatedServer::HandleLegacyPing(MinecraftClient& client)
{
Log_Info("Got legacy server list ping.");
}
template <>
void DedicatedServer::HandlePacket(MinecraftClient& client, const Handholding::ServerboundHandshake& handshake)
{
Log_Info("Client Intention Packet: version=%d, serverIp=%s, port=%u, intention=%d\n",
handshake.protocolVersion,
handshake.serverIP.c_str(),
handshake.port,
handshake.intention
);
client.GetContext().SetState(handshake.intention);
}
template <>
void DedicatedServer::HandlePacket(MinecraftClient& client, const Status::ServerboundRequest& request)
{
Log_Info("Client sent STATUS_PING_REQUEST");
Status::ClientboundResponse message =
{
.jsonResponse = m_status.GetServerStatusJSON()
};
client.SendMessage(message);
}
template <>
void DedicatedServer::HandlePacket(MinecraftClient& client, const Status::ServerboundPing& ping)
{
Log_Info("Client sent STATUS_PING: %llu", ping.timestamp);
Status::ClientboundPong message =
{
.timestamp = ping.timestamp
};
client.SendMessage(message);
}
template <>
void DedicatedServer::HandlePacket(MinecraftClient& client, const Login::ServerboundStart& start)
{
Login::ClientboundSuccess message =
{
.uuid = { 4658857991808325907ull, 7518717155607718277ull },
.username = start.username
};
client.SendMessage(message);
}
} }

View File

@ -2,7 +2,7 @@
#include "MinecraftClient.h" #include "MinecraftClient.h"
#include "ServerStatus.h" #include "ServerStatus.h"
#include "Protocol.h" #include "protocol/Protocol.h"
#include "network/IListenerInterface.h" #include "network/IListenerInterface.h"
@ -19,13 +19,15 @@ namespace Feather
void OnClientConnect(Network::TCPClientHandle&& client) override; void OnClientConnect(Network::TCPClientHandle&& client) override;
void OnClientDisconnect(const Network::TCPClient* client) override; void OnClientDisconnect(const Network::TCPClient* client) override;
ServerStatus& GetStatus() { return m_status; } void HandleLegacyPing(MinecraftClient& client);
template <typename T>
void HandlePacket(MinecraftClient& client, const T& message);
private: private:
ServerProperties* m_properties; ServerProperties* m_properties;
Network::TCPListener m_listener; Network::TCPListener m_listener;
ServerStatus m_status; ServerStatus m_status;
Protocol m_protocol;
LockableList<MinecraftClient> m_clients; LockableList<MinecraftClient> m_clients;
}; };

View File

@ -2,7 +2,7 @@
#include "network/TCPListener.h" #include "network/TCPListener.h"
#include "network/TCPClient.h" #include "network/TCPClient.h"
#include "Protocol.h" #include "protocol/Protocol.h"
#include "NetworkMessage.h" #include "NetworkMessage.h"
namespace Feather namespace Feather
@ -14,12 +14,12 @@ namespace Feather
~MinecraftClient(); ~MinecraftClient();
inline Network::TCPClientHandle& GetTCPClient() { return m_client; } inline Network::TCPClientHandle& GetTCPClient() { return m_client; }
inline ProtocolContext& GetContext() { return m_context; } inline Protocol::ProtocolContext& GetContext() { return m_context; }
void SendMessage(const NetworkMessage& message); void SendMessage(const NetworkMessage& message);
private: private:
Network::TCPClientHandle m_client; Network::TCPClientHandle m_client;
ProtocolContext m_context; Protocol::ProtocolContext m_context;
}; };
} }

View File

@ -5,6 +5,7 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include <string>
#include <cassert> #include <cassert>
#define VARINT_MAX_SIZE 5 #define VARINT_MAX_SIZE 5
@ -16,9 +17,7 @@ namespace Feather
public: public:
NetworkMessage(int32_t expectedSize) NetworkMessage(int32_t expectedSize)
{ {
size_t initialSize = VARINT_MAX_SIZE + expectedSize; m_data.reserve(expectedSize);
m_data.reserve(initialSize);
m_data.resize(VARINT_MAX_SIZE); m_data.resize(VARINT_MAX_SIZE);
} }
@ -37,7 +36,7 @@ namespace Feather
{ {
if constexpr (asEndian == Endian::Big && sizeof(T) > 1) if constexpr (asEndian == Endian::Big && sizeof(T) > 1)
{ {
T valueReversed = ReverseBytes<T>(value); T valueReversed = ReverseBytes(value);
WriteData(&valueReversed, sizeof(T)); WriteData(&valueReversed, sizeof(T));
} }
else else
@ -67,6 +66,11 @@ namespace Feather
WriteData(string, stringSize); WriteData(string, stringSize);
} }
inline void WriteString(const std::string& string)
{
WriteString(string.c_str(), string.length());
}
inline void Finalize() inline void Finalize()
{ {
PrependVarIntSize(); PrependVarIntSize();

View File

@ -27,7 +27,7 @@ namespace Feather
{ {
T value = *reinterpret_cast<const T *const>(&m_data[m_offset]); T value = *reinterpret_cast<const T *const>(&m_data[m_offset]);
return (endianMode == Endian::Big) ? ReverseBytes<T>(value) : value; return (endianMode == Endian::Big) ? ReverseBytes(value) : value;
} }
// Read the next value // Read the next value
@ -51,11 +51,9 @@ namespace Feather
int32_t length = ReadVarInt(&m_lengthSize); int32_t length = ReadVarInt(&m_lengthSize);
// HACK: handle Legacy Server List Ping // HACK: handle Legacy Server List Ping
if (length == 0xFE) { m_legacy = length == 0xFE && PeekByte() == 0x01;
if (PeekByte() == 0x01) { if (m_legacy)
return 1; return 1;
}
}
return length; return length;
} }
@ -100,11 +98,15 @@ namespace Feather
uint32_t Length() const { return m_length; } uint32_t Length() const { return m_length; }
uint32_t Size() const { return m_length + m_lengthSize; } uint32_t Size() const { return m_length + m_lengthSize; }
bool IsLegacyPing() const { return m_legacy; }
private: private:
const uint8_t *const m_data; const uint8_t *const m_data;
uint32_t m_offset; uint32_t m_offset;
const uint32_t m_length; const uint32_t m_length;
uint32_t m_lengthSize; uint32_t m_lengthSize;
bool m_legacy;
}; };
// Use fast Read and Peek for uint8_t // Use fast Read and Peek for uint8_t

View File

@ -1,39 +0,0 @@
#pragma once
namespace Feather
{
enum class ServerboundHandholdingPacketId : int32_t
{
Handshake = 0,
};
enum class ServerboundStatusPacketId : int32_t
{
Request = 0,
Ping = 1
};
enum class ClientBoundStatusPacketId : int32_t
{
Response = 0,
Pong = 1
};
enum class ServerboundLoginPacketId : int32_t
{
LoginStart = 0,
EncryptionResponse = 1,
LoginPluginResponse = 2
};
enum class ClientboundLoginPacketId : int32_t
{
Disconnect = 0,
EncryptionRequest = 1,
LoginSuccess = 2,
SetCompression = 3,
LoginPluginRequest = 4
};
constexpr uint8_t LegacyServerListPing = 0xFE;
}

View File

@ -1,125 +0,0 @@
#include "Protocol.h"
#include "PacketReader.h"
#include "PacketTypes.h"
#include "MinecraftClient.h"
#include "DedicatedServer.h"
#include <string>
namespace Feather
{
void Protocol::HandlePacket(MinecraftClient& client, PacketReader& packet)
{
auto& context = client.GetContext();
switch (context.GetState())
{
case ProtocolState::Handholding:
{
auto id = packet.ReadVarInt<ServerboundHandholdingPacketId>();
uint8_t legacyId = packet.Peek<uint8_t>();
if (id != ServerboundHandholdingPacketId::Handshake && legacyId != LegacyServerListPing)
{
printf("[Protocol] Client sent packet with non-zero ID 0x%x while state was HANDSHAKING\n", id);
break;
}
if (id != ServerboundHandholdingPacketId::Handshake)
{
// Legacy server ping.
// TODO: Do server list ping here.
break;
}
HandshakeMessage handshake(packet);
printf("[Protocol] Client Intention Packet: version=%d, serverIp=%s, port=%u, intention=%d\n",
handshake.protocolVersion,
handshake.serverIP.c_str(),
handshake.port,
handshake.intention
);
context.SetState(handshake.intention);
break;
}
case ProtocolState::Status:
{
auto id = packet.ReadVarInt<ServerboundStatusPacketId>();
switch (id)
{
case ServerboundStatusPacketId::Request:
{
printf("[Protocol] Client sent STATUS_PING_REQUEST\n");
std::string status = m_server.GetStatus().GetServerStatusJSON();
NetworkMessage msg(VARINT_MAX_SIZE + status.length());
// Packet ID
msg.WriteVarInt(ClientBoundStatusPacketId::Response);
// JSON Contents
msg.WriteString(status.c_str(), static_cast<int32_t>(status.length()));
msg.Finalize();
client.SendMessage(msg);
break;
}
case ServerboundStatusPacketId::Ping:
{
PingMessage ping(packet);
printf("[Protocol] Client sent STATUS_PING: %lld\n", ping.timestamp);
NetworkMessage msg(VARINT_MAX_SIZE + sizeof(int64_t));
msg.WriteVarInt(ClientBoundStatusPacketId::Pong);
msg.Write<uint64_t>(ping.timestamp);
msg.Finalize();
client.SendMessage(msg);
break;
}
}
break;
}
case ProtocolState::Login:
{
auto id = packet.ReadVarInt<ServerboundLoginPacketId>();
switch (id)
{
case ServerboundLoginPacketId::LoginStart:
{
LoginStartMessage loginStart(packet);
// 1.15.2...
//std::string uuid = "ecb99913-96a8-40a7-8529-a2ca6ad95768";
//uuid.resize(36);
//NetworkMessage msg(VARINT_MAX_SIZE * 3 + uuid.length() + loginStart.username.length());
NetworkMessage msg(VARINT_MAX_SIZE * 3 + 2 * sizeof(uint64_t) + loginStart.username.length());
msg.WriteVarInt(ClientboundLoginPacketId::LoginSuccess);
// UUID 1.16.1
msg.Write<uint64_t>(4658857991808325907LL);
msg.Write<uint64_t>(7518717155607718277LL);
// UUID 1.15.2
//msg.WriteString(uuid.c_str(), uuid.length());
msg.WriteString(loginStart.username.c_str(), loginStart.username.length());
msg.Finalize();
client.SendMessage(msg);
break;
}
}
break;
}
}
}
}

22
src/Types.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "util/ByteUtil.h"
#include <cstdint>
#include <array>
namespace Feather
{
struct MinecraftUUID
{
std::array<uint64_t, 2> data;
};
inline MinecraftUUID ReverseBytes(MinecraftUUID uuid)
{
return MinecraftUUID
{
.data = { ReverseBytes(uuid.data[0]), ReverseBytes(uuid.data[1]) }
};
}
}

View File

@ -2,7 +2,6 @@ feather_src = [
'Main.cpp', 'Main.cpp',
'DedicatedServer.cpp', 'DedicatedServer.cpp',
'MinecraftClient.cpp', 'MinecraftClient.cpp',
'Protocol.cpp',
'logging/Logger.cpp', 'logging/Logger.cpp',

View File

@ -8,10 +8,13 @@
namespace Feather namespace Feather
{ {
class DedicatedServer;
class PacketReader; class PacketReader;
class MinecraftClient; class MinecraftClient;
class DedicatedServer;
}
namespace Feather::Protocol
{
class ProtocolContext class ProtocolContext
{ {
public: public:
@ -28,13 +31,4 @@ namespace Feather
private: private:
ProtocolState m_state = ProtocolState::Handholding; ProtocolState m_state = ProtocolState::Handholding;
}; };
class Protocol
{
DedicatedServer& m_server;
public:
Protocol(DedicatedServer& server) : m_server(server) {}
void HandlePacket(MinecraftClient& client, PacketReader& packet);
};
} }

View File

@ -2,22 +2,75 @@ import hjson
import sys import sys
text = '' text = ''
varint_max_size = 5
indent_count = 0
def indent():
global indent_count
indent_count += 1
def unindent():
global indent_count
indent_count -= 1
def add_text(fmt, *args): def add_text(fmt, *args):
global text global text
global indent_count
for i in range(indent_count):
text += ' '
text += (fmt + '\n').format(*args) text += (fmt + '\n').format(*args)
def resolve_type(aliases, type): def newline():
if type in aliases: global text
return aliases[type] text += '\n'
def extract_array_type(type):
if '[' in type and ']' in type:
return type[:type.rfind('[')]
return type return type
def extract_array_count(type):
if '[' in type and ']' in type:
return int(type[type.find('[')+1:type.rfind(']')])
return 1
def resolve_type(aliases, type):
type_no_array = type.rstrip('_')
if type_no_array in aliases:
return aliases[type_no_array]
return type
def get_type_size(type):
global varint_max_size
count = extract_array_count(type)
type = extract_array_type(type)
if type == 'varint':
return varint_max_size
elif type == 'int64' or type == 'uint64' or type == 'double':
return 8
elif type == 'int32' or type == 'uint32' or type == 'float':
return 4
elif type == 'int16' or type == 'uint16':
return 2
elif type == 'int8' or type == 'uint8':
return 1
elif type == 'string':
return count
elif type == 'uuid':
return 16
else:
print(type)
assert False
def print_states(states): def print_states(states):
add_text(' enum class ProtocolState : int32_t') add_text('enum class ProtocolState : int32_t')
add_text(' {{') add_text('{{')
indent()
for state, value in states.items(): for state, value in states.items():
add_text(' {} = {},', state, value) add_text('{} = {},', state, value)
add_text(' }};') unindent()
add_text('}};')
def get_rw_func(primitiveType, aliasedType, read): def get_rw_func(primitiveType, aliasedType, read):
prefix = 'Read' if read else 'Write' prefix = 'Read' if read else 'Write'
@ -29,30 +82,113 @@ def get_rw_func(primitiveType, aliasedType, read):
elif aliasedType == 'string': elif aliasedType == 'string':
return '{}String'.format(prefix) return '{}String'.format(prefix)
else: else:
return 'Read<{}>'.format(aliasedType) return '{}<{}>'.format(prefix, primitiveType)
def print_messages(list, aliases, primitives, serverbound): def print_messages(list, aliases, primitives):
global text global text
for message_name, message in list.items(): for state, direction_list in list.items():
add_text(' struct {}Message', message_name) add_text('namespace {}', state.capitalize())
add_text(' {{') add_text('{{')
add_text(' static constexpr int32_t PacketId = {};', message['id']) indent()
add_text(' static constexpr bool ServerBound = {};', 'true' if serverbound else 'false') for direction, messages in direction_list.items():
add_text(' static constexpr ProtocolState PacketState = ProtocolState::{};', message['state']) serverbound = direction == 'serverbound'
add_text('') for message_name, message in messages.items():
if serverbound: global varint_max_size
add_text(' {}Message(PacketReader& reader)', message_name) size = varint_max_size # Packet Length
add_text(' {{') size += varint_max_size # Packet Id
for name, type in message['vars'].items(): for name, type in message['vars'].items():
add_text(' {} = reader.{}();', name, get_rw_func(resolve_type(primitives, type), resolve_type(aliases, type), True)) size += get_type_size(resolve_type(aliases, type))
add_text(' }}')
add_text('')
for name, type in message['vars'].items(): struct_name = '{}{}'.format(direction.capitalize(), message_name)
add_text(' {} {};', resolve_type(primitives, type), name) add_text('struct {}', struct_name)
add_text('{{')
indent()
add_text('static constexpr int32_t PacketId = {};', message['id'])
add_text('static constexpr bool Serverbound = {};', 'true' if serverbound else 'false')
add_text('static constexpr ProtocolState PacketState = ProtocolState::{};', state.capitalize())
add_text('static constexpr size_t MaxSize = {};', size)
newline()
if serverbound:
add_text('{}(PacketReader& reader)', struct_name)
add_text('{{')
indent()
for name, type in message['vars'].items():
add_text('{} = reader.{}();', name, get_rw_func(resolve_type(primitives, extract_array_type(type)), resolve_type(aliases, extract_array_type(type)), True))
unindent()
add_text('}}')
newline()
else:
add_text('operator NetworkMessage() const')
add_text('{{')
indent()
add_text('NetworkMessage msg(MaxSize);')
add_text('msg.WriteVarInt(PacketId);')
for name, type in message['vars'].items():
add_text('msg.{}({});', get_rw_func(resolve_type(primitives, extract_array_type(type)), resolve_type(aliases, extract_array_type(type)), False), name)
add_text('msg.Finalize();')
add_text('return msg;')
unindent()
add_text('}}')
for name, type in message['vars'].items():
resolved_type = resolve_type(primitives, extract_array_type(type))
if not serverbound and resolved_type == 'std::string':
add_text('const {}& {};', resolved_type, name)
else:
add_text('{} {};', resolved_type, name)
unindent()
add_text('}};')
newline()
unindent()
add_text('}}')
newline()
def print_handler(list):
add_text('template <typename HandlerType, typename ClientType>')
add_text('void ProcessPacket(HandlerType& handler, ClientType& client, PacketReader& packet, ProtocolState state)')
add_text('{{')
indent()
add_text('if (packet.IsLegacyPing())')
add_text('{{')
indent()
add_text('handler.HandleLegacyPing(client);')
add_text('return;')
unindent()
add_text('}}')
newline()
add_text('const int32_t packetId = packet.ReadVarInt();')
add_text('switch (state)')
add_text('{{')
indent()
for state, direction_list in list.items():
add_text('case ProtocolState::{}:', state.capitalize())
add_text('{{')
indent()
add_text('switch (packetId)')
add_text('{{')
indent()
for direction, messages in direction_list.items():
serverbound = direction == 'serverbound'
if not serverbound:
continue
for message_name, message in messages.items():
name = '{}::Serverbound{}'.format(state.capitalize(), message_name)
add_text('case {}::PacketId:', name)
indent()
add_text('handler.HandlePacket<{}>(client, {}(packet));', name, name)
add_text('break;')
unindent()
unindent()
add_text('}}')
add_text('break;')
unindent()
add_text('}}')
unindent()
add_text('}}')
unindent()
add_text('}}')
add_text(' }};')
add_text('')
def print_protocol(): def print_protocol():
with open(sys.argv[1]) as message_file: with open(sys.argv[1]) as message_file:
@ -60,29 +196,37 @@ def print_protocol():
print_states(message_scheme['states']) print_states(message_scheme['states'])
add_text('') newline()
print_messages( print_messages(
message_scheme['messages']['serverbound'], message_scheme['messages'],
message_scheme['types']['aliases'], message_scheme['types']['aliases'],
message_scheme['types']['primitives'], message_scheme['types']['primitives'])
True)
newline()
print_handler(message_scheme['messages'])
def main(): def main():
global text
if len(sys.argv) != 3: if len(sys.argv) != 3:
print('Usage: generate_protocol.py <input: protocol.hjson> <output: ProtocolDefinitions.h>') print('Usage: generate_protocol.py <input: protocol.hjson> <output: ProtocolDefinitions.h>')
return return
add_text('#pragma once') add_text('#pragma once')
add_text('') newline()
add_text('#include <cstdint>') add_text('#include <cstdint>')
add_text('#include "Types.h"')
add_text('#include "PacketReader.h"') add_text('#include "PacketReader.h"')
add_text('') newline()
add_text('namespace Feather') add_text('namespace Feather::Protocol')
add_text('{{') add_text('{{')
indent()
print_protocol() print_protocol()
unindent()
add_text('}}') add_text('}}')
add_text('') newline()
with open(sys.argv[2], 'w') as out_file: with open(sys.argv[2], 'w') as out_file:
out_file.write(text) out_file.write(text)

View File

@ -10,6 +10,16 @@
{ {
varint : int32_t varint : int32_t
string : std::string string : std::string
uuid : MinecraftUUID
uint64 : uint64_t
int64 : int64_t
uint32 : uint32_t
int32 : int32_t
uint16 : uint16_t
int16 : int16_t
uint8 : uint8_t
int8 : int8_t
} }
} }
@ -23,45 +33,103 @@
messages : messages :
{ {
serverbound : handholding :
{ {
Handshake : serverbound :
{ {
id : 0 Handshake :
state : Handholding
vars :
{ {
protocolVersion : varint id : 0
serverIP : string vars :
port : uint16_t {
intention : ProtocolState protocolVersion : varint
} serverIP : string[255]
} port : uint16
intention : ProtocolState
Ping : }
{
id : 1
state : Login
vars :
{
timestamp : uint64_t
}
}
LoginStart :
{
id : 0
state : Login
vars :
{
username : string
} }
} }
} }
clientbound : status :
{ {
serverbound :
{
Request :
{
id : 0
vars :
{
}
}
Ping :
{
id : 1
vars :
{
timestamp : uint64
}
}
}
clientbound :
{
Response :
{
id : 0
vars :
{
jsonResponse : string[32767]
}
}
Pong :
{
id : 1
vars :
{
timestamp : uint64
}
}
}
}
login :
{
clientbound :
{
Disconnect :
{
id : 0
vars :
{
username : string[32767]
}
}
Success :
{
id : 2
vars :
{
uuid : uuid
username : string[16]
}
}
}
serverbound :
{
Start :
{
id : 0
vars :
{
username : string[16]
}
}
}
} }
} }
} }

View File

@ -1,38 +1,30 @@
#pragma once #pragma once
#ifdef _MSC_VER #include <cstdlib>
#include <stdlib.h> #include <cstdint>
#endif
enum class Endian namespace Feather
{ {
Little, enum class Endian
Big {
}; Little,
Big
};
// Reverses the byte order of a primitive value of any typical size // TODO: Make this constexpr
template <typename T> inline uint8_t ReverseBytes(uint8_t n) { return n; }
inline constexpr T ReverseBytes(T n) #ifdef __GNUC__
{ inline uint16_t ReverseBytes(uint16_t n) { return __builtin_bswap16(n); }
// 1 byte, cannot reverse inline uint32_t ReverseBytes(uint32_t n) { return __builtin_bswap32(n); }
if constexpr (sizeof(T) == 1) return n; inline uint32_t ReverseBytes(uint64_t n) { return __builtin_bswap64(n); }
#else
inline uint16_t ReverseBytes(uint16_t n) { return _byteswap_ushort(n); }
inline uint32_t ReverseBytes(uint32_t n) { return _byteswap_ulong (n); }
inline uint64_t ReverseBytes(uint64_t n) { return _byteswap_uint64(n); }
#endif
#ifdef __GNUC__ inline int8_t ReverseBytes(int8_t n) { return n; }
// GCC intrinsic byte swaps inline int16_t ReverseBytes(int16_t n) { return int16_t(ReverseBytes(uint16_t(n))); }
// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fbswap16 inline int32_t ReverseBytes(int32_t n) { return int32_t(ReverseBytes(uint32_t(n))); }
inline int64_t ReverseBytes(int64_t n) { return int64_t(ReverseBytes(uint64_t(n))); }
if constexpr (sizeof(T) == 2) return __builtin_bswap16(n); // 2 bytes
else if constexpr (sizeof(T) == 4) return __builtin_bswap32(n); // 4 bytes
else if constexpr (sizeof(T) == 8) return __builtin_bswap64(n); // 8 bytes
#elif defined(_MSC_VER)
// MSVC intrinsic byteswaps
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/byteswap-uint64-byteswap-ulong-byteswap-ushort
if constexpr (sizeof(T) == 2) return _byteswap_ushort(n); // 2 bytes
else if constexpr (sizeof(T) == 4) return _byteswap_ulong(n); // 4 bytes
else if constexpr (sizeof(T) == 8) return _byteswap_uint64(n); // 8 bytes
#endif
//else static_assert(false, "Attempted to call ReverseBytes() on type with unusual size.");
} }