535 lines
16 KiB
C++
535 lines
16 KiB
C++
#include "NetQuake.h"
|
|
#include <iostream> // basic io
|
|
#include <vector> // dynamic arrays
|
|
#include <map> // keyval maps
|
|
#include <asio.hpp>
|
|
#include <asio/ts/buffer.hpp>
|
|
#include <asio/ts/internet.hpp>
|
|
#include <print>
|
|
#include <string>
|
|
#include <fstream>
|
|
#include <excpt.h>
|
|
|
|
NetQuake::SerializedGenericPacket NetQuake::Serialize(NetworkGenericPacket ngp)
|
|
{
|
|
SerializedGenericPacket sgp;
|
|
|
|
|
|
unsigned short typeAsShort;
|
|
|
|
typeAsShort = (ngp.type[0] << 8) + ngp.type[1];
|
|
sgp.type = (PacketType)typeAsShort;
|
|
sgp.length = (ngp.length[0] << 8) + ngp.length[1];
|
|
|
|
sgp.remainder = ngp.remainder;
|
|
|
|
return sgp;
|
|
}
|
|
|
|
NetQuake::GenericGamePacket NetQuake::toGenericGamePacket(SerializedGenericPacket sgp)
|
|
{
|
|
GenericGamePacket retval;
|
|
try {
|
|
|
|
int currentPacketLength = 0;
|
|
int currentPacketAt = 0;
|
|
bool inString = false;
|
|
for (int i = 0; i < sgp.remainder.size(); i++) {
|
|
if (currentPacketLength == 0 && !inString) {
|
|
if (sgp.remainder[i] == CLIENT_MESSAGE_NOOP) {
|
|
ClientMessage msg;
|
|
msg.type = CLIENT_MESSAGE_NOOP;
|
|
retval.messageBlock.messages.push_back(msg);
|
|
}
|
|
if (sgp.remainder[i] == CLIENT_MESSAGE_KEEPALIVE) {
|
|
ClientMessage msg;
|
|
msg.type = CLIENT_MESSAGE_NOOP;
|
|
retval.messageBlock.messages.push_back(msg);
|
|
}
|
|
if (sgp.remainder[i] == CLIENT_MESSAGE_MOVEMENT) {
|
|
ClientMessage msg;
|
|
msg.type = CLIENT_MESSAGE_MOVEMENT;
|
|
retval.messageBlock.messages.push_back(msg);
|
|
currentPacketLength = 15;
|
|
}
|
|
if (sgp.remainder[i] == CLIENT_MESSAGE_CONSOLE) {
|
|
ClientMessage msg;
|
|
msg.type = CLIENT_MESSAGE_CONSOLE;
|
|
retval.messageBlock.messages.push_back(msg);
|
|
|
|
}
|
|
}
|
|
else if (inString) {
|
|
if (sgp.remainder[i] == 0x00) inString = false;
|
|
else {
|
|
retval.messageBlock.messages[retval.messageBlock.messages.size() - 1].data += sgp.remainder[i];
|
|
}
|
|
}
|
|
else {
|
|
retval.messageBlock.messages[retval.messageBlock.messages.size() - 1].data += sgp.remainder[i];
|
|
currentPacketAt++;
|
|
if (currentPacketAt == currentPacketLength) {
|
|
currentPacketLength = 0;
|
|
currentPacketAt = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
retval.valid = true;
|
|
}
|
|
catch (std::exception& e) {
|
|
std::println("Something went wrong in serialized generic packet to generic game packet conversion: {}", e.what());
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
std::string NetQuake::Serialize(ServerInfoReply sirp)
|
|
{
|
|
std::string retval = "";
|
|
|
|
using namespace std::string_literals;
|
|
retval += CONTROL_SERVER_INFO_REPLY;
|
|
retval += sirp.address.ip + ":";
|
|
retval += std::to_string(sirp.address.port) + "\0"s;
|
|
retval += sirp.hostname + "\0"s;
|
|
retval += sirp.levelname + "\0"s;
|
|
retval += sirp.currentPlayers;
|
|
retval += sirp.maxPlayers;
|
|
retval += sirp.netVersion;
|
|
|
|
short retvalsize = retval.size() + 4;
|
|
unsigned char retvalsizechar[2];
|
|
|
|
retvalsizechar[0] = retvalsize & 0xff;
|
|
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
|
|
|
|
|
std::string fretval = "\x80";
|
|
fretval += "\0"s;
|
|
fretval += retvalsizechar[1];
|
|
fretval += retvalsizechar[0];
|
|
fretval += retval;
|
|
return fretval;
|
|
}
|
|
|
|
std::string NetQuake::Serialize(ConnectionRequestReply crr)
|
|
{
|
|
std::string retval = "";
|
|
using namespace std::string_literals;
|
|
|
|
|
|
if (crr.reject) {
|
|
retval = "\x82" + crr.reason + "\0"s;
|
|
}
|
|
else {
|
|
|
|
unsigned char portchar[2];
|
|
portchar[1] = crr.port & 0xff;
|
|
portchar[0] = (crr.port >> 8) & 0xff;
|
|
retval = "\x81"s;
|
|
retval += portchar[1];
|
|
retval += portchar[0];
|
|
retval += (unsigned char)crr.unknown[0];
|
|
retval += (unsigned char)crr.unknown[1];
|
|
}
|
|
|
|
|
|
short retvalsize = retval.size() + 4;
|
|
unsigned char retvalsizechar[2];
|
|
|
|
retvalsizechar[0] = retvalsize & 0xff;
|
|
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
|
|
|
std::string fretval = "\x80";
|
|
fretval += "\0"s;
|
|
fretval += retvalsizechar[1];
|
|
fretval += retvalsizechar[0];
|
|
fretval += retval;
|
|
return fretval;
|
|
}
|
|
|
|
std::string NetQuake::AcknowledgePacket(int packet)
|
|
{
|
|
std::string retval;
|
|
using namespace std::string_literals;
|
|
retval += reinterpret_cast<char*>(packet);
|
|
short retvalsize = retval.size() + 4;
|
|
unsigned char retvalsizechar[2];
|
|
|
|
retvalsizechar[0] = retvalsize & 0xff;
|
|
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
|
|
|
std::string fretval = "\0"s;
|
|
fretval += "\x10"s;
|
|
fretval += retvalsizechar[1];
|
|
fretval += retvalsizechar[0];
|
|
fretval += retval;
|
|
return fretval;
|
|
}
|
|
|
|
void NetQuake::NetQuake_ControlServer(Internal::InternalRequestConsole* internalRequestConsole, Config::Config* config, bool* serverRunning)
|
|
{
|
|
|
|
asio::io_context io_context;
|
|
asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), config->udpControl));
|
|
std::print("Started control server on {}\n", config->udpControl);
|
|
|
|
NetQuake::ServerInfoReply sir;
|
|
sir.address.ip = config->ip;
|
|
sir.address.port = config->udpControl;
|
|
sir.currentPlayers = 0;
|
|
sir.maxPlayers = config->maxPlayers;
|
|
|
|
sir.hostname = config->serverName;
|
|
sir.levelname = "start";
|
|
sir.netVersion = NetQuake::QGOLD;
|
|
|
|
|
|
while (true) {
|
|
asio::ip::udp::endpoint remote_endpoint;
|
|
std::array<char, 1024> recv_buf;
|
|
|
|
|
|
socket.receive_from(asio::buffer(recv_buf), remote_endpoint);
|
|
|
|
NetQuake::NetworkGenericPacket gpac;
|
|
|
|
gpac.type[0] = recv_buf[0];
|
|
gpac.type[1] = recv_buf[1];
|
|
gpac.length[0] = recv_buf[2];
|
|
gpac.length[1] = recv_buf[3];
|
|
for (int i = 4; i < recv_buf.size(); i++) {
|
|
gpac.remainder += recv_buf[i];
|
|
}
|
|
|
|
NetQuake::SerializedGenericPacket sgpac = NetQuake::Serialize(gpac);
|
|
|
|
std::cout << "new packet\n";
|
|
switch (sgpac.type) {
|
|
|
|
case NetQuake::NQ_CONTROL_PACKET:
|
|
{
|
|
NetQuake::GenericControlPacket gcpac(sgpac);
|
|
|
|
std::cout << "GenericControlPacket: " << gcpac.opcode << "\n";
|
|
|
|
// the 0x01 i had so much trouble getting through
|
|
if (gcpac.opcode == NetQuake::CONTROL_CONNECTION_REQUEST) {
|
|
std::cout << "Connection Request\n";
|
|
|
|
int endpointRequestId = internalRequestConsole->newEndpointRequest(remote_endpoint);
|
|
|
|
while (!internalRequestConsole->getEndpointRequest(endpointRequestId).ready) {
|
|
// waiting for endpoint request to go through
|
|
}
|
|
|
|
Internal::NewEndpointRequestReply nerr = internalRequestConsole->getEndpointRequest(endpointRequestId);
|
|
|
|
NetQuake::ConnectionRequestReply crr;
|
|
|
|
crr.reject = nerr.error;
|
|
crr.reason = nerr.reason;
|
|
crr.port = nerr.port;
|
|
|
|
crr.unknown[0] = config->unknownMagic[0];
|
|
crr.unknown[1] = config->unknownMagic[1];
|
|
|
|
asio::error_code e;
|
|
socket.send_to(asio::buffer(NetQuake::Serialize(crr)), remote_endpoint, 0, e);
|
|
/*NetQuake::ConnectionRequestReply crr;
|
|
|
|
crr.reject = false;
|
|
crr.port = 26000;
|
|
crr.unknown[0] = config->unknownMagic[0];
|
|
crr.unknown[1] = config->unknownMagic[1];
|
|
|
|
asio::error_code e;
|
|
socket.send_to(asio::buffer(NetQuake::Serialize(crr)), remote_endpoint, 0, e);
|
|
*/
|
|
}
|
|
if (gcpac.opcode == NetQuake::CONTROL_SERVER_INFO_REQUEST) {
|
|
NetQuake::ServerInfoRequest sirqpac(gcpac);
|
|
|
|
std::cout << "ServerInfoRequest: server info request for game \"" << sirqpac.gameName << "\", netversion " << sirqpac.netVersion << "\n";
|
|
|
|
std::string serializedTemplate = NetQuake::Serialize(sir);
|
|
|
|
std::ofstream templatestream("template.txt", std::ios::binary);
|
|
templatestream << serializedTemplate;
|
|
templatestream.close();
|
|
asio::error_code e;
|
|
socket.send_to(asio::buffer(serializedTemplate), remote_endpoint, 0, e);
|
|
std::cout << e.message() << "\n";
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
std::cout << "[WARNING] Unknown or unsupported packet came through.\n";
|
|
break;
|
|
}
|
|
// std::cout << gcpac.opcode << "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void NetQuake::NetQuake_EndpointServer(Config::Config* config, int port, asio::ip::udp::endpoint startRemote)
|
|
{
|
|
asio::io_context io_context;
|
|
asio::error_code e;
|
|
|
|
|
|
asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), port));
|
|
|
|
socket.connect(startRemote);
|
|
|
|
std::print("New endpoint server started on {}.\nClient Start Info: {} | {}\n", port, startRemote.address().to_string(), startRemote.port());
|
|
|
|
{ // startup block
|
|
ServerMessageBlock smb;
|
|
smb.type = NQ_MESSAGE_BLOCK_END;
|
|
smb.order = 100;
|
|
ServerMessages::Print serverBanner;
|
|
|
|
serverBanner.text = config->serverBanner;
|
|
smb.serverMessages.push_back(&serverBanner);
|
|
|
|
|
|
ServerMessages::ServerInfo serverInfo;
|
|
|
|
serverInfo.mapName = "start";
|
|
serverInfo.maxClients = config->maxPlayers;
|
|
serverInfo.multi = 1;
|
|
serverInfo.numModels = 0;
|
|
serverInfo.numSounds = 0;
|
|
serverInfo.serverVersion = 15;
|
|
|
|
smb.serverMessages.push_back(&serverInfo);
|
|
|
|
ServerMessages::SignOn signOn;
|
|
signOn.signon = 1; // prespawn(2=light,3=render)
|
|
smb.serverMessages.push_back(&signOn);
|
|
|
|
std::print("Tried sending server banner to client.\n");
|
|
|
|
socket.send_to(asio::buffer(smb.Serialize()), startRemote, 0, e);
|
|
std::ofstream smbattempt("smb.txt");
|
|
smbattempt << smb.Serialize();
|
|
smbattempt.close();
|
|
|
|
|
|
std::print("Server banner should be sent.\n");
|
|
}
|
|
|
|
while (true) {
|
|
asio::ip::udp::endpoint rec;
|
|
std::string recv_buf;
|
|
socket.receive_from(asio::buffer(recv_buf), rec);
|
|
std::cout << "packet on secondary line " << port << "\n";
|
|
std::cout << "received packet size: " << recv_buf.size() << "\n";
|
|
if(recv_buf.size() > 4) {
|
|
try {
|
|
NetQuake::NetworkGenericPacket ngp;
|
|
ngp.type[0] = recv_buf[0];
|
|
ngp.type[1] = recv_buf[1];
|
|
ngp.length[0] = recv_buf[2];
|
|
ngp.length[1] = recv_buf[3];
|
|
for (int i = 4; i < recv_buf.size(); i++) {
|
|
ngp.remainder += recv_buf[i];
|
|
}
|
|
SerializedGenericPacket sgpac = Serialize(ngp);
|
|
GenericGamePacket ggp = toGenericGamePacket(sgpac);
|
|
|
|
|
|
if (ggp.type == NQ_MESSAGE_BLOCK_CHUNK || ggp.type == NQ_MESSAGE_BLOCK_END) {
|
|
socket.send_to(asio::buffer(AcknowledgePacket(ggp.packetNumber)), rec);
|
|
std::println("Server acknowledged packet {}", ggp.packetNumber);
|
|
}
|
|
}
|
|
catch (...) {
|
|
std::println("Error on packet on {}", port);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
std::println("Weird packet received... :shrug:");
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
NetQuake::GenericControlPacket::GenericControlPacket(SerializedGenericPacket sgp)
|
|
{
|
|
|
|
if (sgp.type != NQ_CONTROL_PACKET) {
|
|
throw std::runtime_error("Wrong packet type given to generic control packet constructor.");
|
|
}
|
|
|
|
opcode = (Control_OpCode)sgp.remainder[0];
|
|
|
|
remainder = sgp.remainder;
|
|
remainder.erase(0, 1);
|
|
|
|
|
|
}
|
|
|
|
NetQuake::ServerInfoRequest::ServerInfoRequest(GenericControlPacket gcp)
|
|
{
|
|
if (gcp.opcode != CONTROL_SERVER_INFO_REQUEST) {
|
|
throw std::runtime_error("Wrong packet type given to server info request constructor.");
|
|
}
|
|
|
|
std::string toParse = gcp.remainder;
|
|
|
|
int i = 0;
|
|
for (; toParse[i] != '\0'; i++) {
|
|
gameName += toParse[i];
|
|
}
|
|
netVersion = (NetProtocolVersion) toParse[i+1];
|
|
|
|
}
|
|
|
|
NetQuake::ConnectionRequest::ConnectionRequest(GenericControlPacket gcp)
|
|
{
|
|
if (gcp.opcode != CONTROL_SERVER_INFO_REQUEST) {
|
|
throw std::runtime_error("Wrong packet type given to server info request constructor.");
|
|
}
|
|
|
|
std::string toParse = gcp.remainder;
|
|
|
|
int i = 0;
|
|
for (; toParse[i] != '\0'; i++) {
|
|
gameName += toParse[i];
|
|
}
|
|
netVersion = (NetProtocolVersion)toParse[i + 1];
|
|
|
|
}
|
|
|
|
NetQuake::Internal::InternalRequestConsole::InternalRequestConsole()
|
|
{
|
|
std::print("New Internal Request Console Created.\n");
|
|
}
|
|
|
|
int NetQuake::Internal::InternalRequestConsole::newEndpointRequest(asio::ip::udp::endpoint ep)
|
|
{
|
|
NewEndpointRequestReply nerr;
|
|
nerr.ready = false;
|
|
nerr.startRemote = ep;
|
|
newEndpointRequests.push_back(nerr);
|
|
return newEndpointRequests.size() - 1;
|
|
}
|
|
|
|
NetQuake::Internal::NewEndpointRequestReply NetQuake::Internal::InternalRequestConsole::getEndpointRequest(int id)
|
|
{
|
|
return newEndpointRequests[id];
|
|
}
|
|
|
|
void NetQuake::Internal::InternalRequestConsole::Update(Config::Config *config, std::vector<GameEndpoint>* gameEndpointsVector)
|
|
{
|
|
|
|
for (int i = lastEndpointRequestTreated; i < newEndpointRequests.size(); i++) {
|
|
if (!newEndpointRequests[i].ready) {
|
|
if(gameEndpointsVector->size() < config->maxPlayers){
|
|
GameEndpoint* endpoint = new GameEndpoint(config, 1000+i, newEndpointRequests[i].startRemote);
|
|
newEndpointRequests[i].port = 1000 + i;
|
|
}
|
|
else {
|
|
newEndpointRequests[i].error = true;
|
|
newEndpointRequests[i].reason = "too many players";
|
|
}
|
|
|
|
newEndpointRequests[i].ready = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
NetQuake::Internal::InternalRequestConsole::~InternalRequestConsole()
|
|
{
|
|
newEndpointRequests.clear();
|
|
std::print("Internal Request Console Destroyed.\n");
|
|
}
|
|
|
|
NetQuake::GameEndpoint::GameEndpoint()
|
|
{
|
|
throw; // constructor musn't be empty
|
|
}
|
|
|
|
NetQuake::GameEndpoint::GameEndpoint(Config::Config* config, int port, asio::ip::udp::endpoint startRemote)
|
|
{
|
|
std::thread internalInternalThread(NetQuake_EndpointServer, config, port, startRemote);
|
|
internalThread.swap(internalInternalThread);
|
|
}
|
|
|
|
NetQuake::GameEndpoint::~GameEndpoint()
|
|
{
|
|
internalThread.~thread();
|
|
}
|
|
|
|
std::string NetQuake::ServerMessages::Print::Serialize() {
|
|
using namespace std::string_literals;
|
|
std::string retval = text;
|
|
retval += "\0"s;
|
|
return retval;
|
|
}
|
|
|
|
std::string NetQuake::ServerMessages::ServerInfo::Serialize() {
|
|
std::string retval = "";
|
|
using namespace std::string_literals;
|
|
retval += serverVersion;
|
|
retval += maxClients;
|
|
retval += mapName + "\0"s;
|
|
for (int i = 0; i < precacheModels.size(); i++) {
|
|
retval += precacheModels[i] + "\0"s;
|
|
}
|
|
retval += numModels;
|
|
for (int i = 0; i < precacheSounds.size(); i++) {
|
|
retval += precacheSounds[i] + "\0"s;
|
|
}
|
|
|
|
retval += numSounds;
|
|
return retval;
|
|
}
|
|
|
|
|
|
std::string NetQuake::ServerMessages::SignOn::Serialize() {
|
|
std::string retval = "";
|
|
|
|
retval += signon;
|
|
|
|
return retval;
|
|
}
|
|
|
|
std::string NetQuake::ServerMessageBlock::Serialize()
|
|
{
|
|
std::string retval;
|
|
|
|
using namespace std::string_literals;
|
|
|
|
for (int i = 0; i < serverMessages.size(); i++) {
|
|
retval += serverMessages[i]->type;
|
|
retval += serverMessages[i]->Serialize();
|
|
}
|
|
|
|
|
|
|
|
short retvalsize = retval.size() + 4;
|
|
unsigned char retvalsizechar[2];
|
|
unsigned char typechar[2];
|
|
|
|
retvalsizechar[0] = retvalsize & 0xff;
|
|
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
|
typechar[1] = this->type & 0xff;
|
|
typechar[0] = (this->type >> 8) & 0xff;
|
|
|
|
std::string fretval = std::string({(char)typechar[0]});
|
|
fretval += typechar[1];
|
|
fretval += retvalsizechar[1];
|
|
fretval += (char)retvalsizechar[0];
|
|
fretval += retval;
|
|
return fretval;
|
|
}
|