mirror of
https://github.com/edo9300/edopro
synced 2026-08-23 22:26:05 -04:00
444 lines
13 KiB
C++
444 lines
13 KiB
C++
#include "epro_thread.h"
|
|
#include "config.h"
|
|
#if EDOPRO_POSIX
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
#include "netserver.h"
|
|
#include "generic_duel.h"
|
|
#include "common.h"
|
|
|
|
namespace ygo {
|
|
bool operator==(const ClientVersion& ver1, const ClientVersion& ver2) {
|
|
return ver1.client.major == ver2.client.major && ver1.client.minor == ver2.client.minor && ver1.core.major == ver2.core.major && ver1.core.minor == ver2.core.minor;
|
|
}
|
|
bool operator!=(const ClientVersion& ver1, const ClientVersion& ver2) {
|
|
return !(ver1 == ver2);
|
|
}
|
|
|
|
bool operator==(const DeckSizes::Sizes& limits, const size_t count) {
|
|
if(count < limits.min)
|
|
return false;
|
|
return count <= limits.max;
|
|
}
|
|
|
|
bool operator!=(const DeckSizes::Sizes& limits, const size_t count) {
|
|
return !(limits == count);
|
|
}
|
|
|
|
|
|
std::unordered_map<bufferevent*, DuelPlayer> NetServer::users;
|
|
uint16_t NetServer::server_port = 0;
|
|
event_base* NetServer::net_evbase = nullptr;
|
|
event* NetServer::broadcast_ev = nullptr;
|
|
evconnlistener* NetServer::listener = nullptr;
|
|
DuelMode* NetServer::duel_mode = nullptr;
|
|
uint8_t NetServer::net_server_read[0x20000];
|
|
uint8_t NetServer::net_server_write[0x20000];
|
|
uint16_t NetServer::last_sent = 0;
|
|
|
|
static evconnlistener* createIpv6Listener(uint16_t port, struct event_base* net_evbase,
|
|
evconnlistener_cb cb) {
|
|
#ifdef LEV_OPT_BIND_IPV4_AND_IPV6
|
|
sockaddr_in6 sin;
|
|
memset(&sin, 0, sizeof(sin));
|
|
sin.sin6_family = AF_INET6;
|
|
evutil_inet_pton(AF_INET6, "::", &sin.sin6_addr);
|
|
sin.sin6_port = htons(port);
|
|
return evconnlistener_new_bind(net_evbase, cb, nullptr,
|
|
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE | LEV_OPT_BIND_IPV4_AND_IPV6, -1, (sockaddr*)&sin, sizeof(sin));
|
|
#else
|
|
sockaddr_in6 sin;
|
|
memset(&sin, 0, sizeof(sin));
|
|
sin.sin6_family = AF_INET6;
|
|
evutil_inet_pton(AF_INET6, "::", &sin.sin6_addr);
|
|
sin.sin6_port = htons(port);
|
|
int on = 1;
|
|
int off = 0;
|
|
evutil_socket_t fd = socket(AF_INET6, SOCK_STREAM, 0);
|
|
if(fd == EVUTIL_INVALID_SOCKET)
|
|
return nullptr;
|
|
if(evutil_make_socket_nonblocking(fd) != 0
|
|
|| setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&on, (ev_socklen_t)sizeof(on)) != 0
|
|
|| evutil_make_listen_socket_reuseable(fd) != 0
|
|
#ifdef IPV6_V6ONLY
|
|
|| setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&off, (ev_socklen_t)sizeof(off)) != 0
|
|
|| bind(fd, (sockaddr*)&sin, sizeof(sin)) != 0
|
|
#endif
|
|
) {
|
|
evutil_closesocket(fd);
|
|
return nullptr;
|
|
}
|
|
auto listener = evconnlistener_new(net_evbase, cb, nullptr, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, fd);
|
|
if(listener == nullptr)
|
|
evutil_closesocket(fd);
|
|
return listener;
|
|
#endif
|
|
}
|
|
|
|
static evconnlistener* createIpv4Listener(uint16_t port, struct event_base* net_evbase,
|
|
evconnlistener_cb cb) {
|
|
sockaddr_in sin;
|
|
memset(&sin, 0, sizeof(sin));
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
sin.sin_port = htons(port);
|
|
return evconnlistener_new_bind(net_evbase, cb, nullptr,
|
|
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (sockaddr*)&sin, sizeof(sin));
|
|
}
|
|
|
|
bool NetServer::StartServer(uint16_t port) {
|
|
if(net_evbase)
|
|
return false;
|
|
net_evbase = event_base_new();
|
|
if(!net_evbase)
|
|
return false;
|
|
if(!(listener = createIpv6Listener(port, net_evbase, ServerAccept)))
|
|
listener = createIpv4Listener(port, net_evbase, ServerAccept);
|
|
server_port = port;
|
|
if(!listener) {
|
|
event_base_free(net_evbase);
|
|
net_evbase = 0;
|
|
return false;
|
|
}
|
|
evconnlistener_set_error_cb(listener, ServerAcceptError);
|
|
epro::thread(ServerThread).detach();
|
|
return true;
|
|
}
|
|
#ifndef IPV6_JOIN_GROUP
|
|
#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP
|
|
#endif
|
|
bool NetServer::StartBroadcast() {
|
|
if(!net_evbase)
|
|
return false;
|
|
auto CreateIPV6MulticastSocketListener = []() -> evutil_socket_t {
|
|
#ifdef IPV6_V6ONLY
|
|
evutil_socket_t udp = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
if(udp == EVUTIL_INVALID_SOCKET)
|
|
return EVUTIL_INVALID_SOCKET;
|
|
int off = 0;
|
|
if(setsockopt(udp, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&off, (ev_socklen_t)sizeof(off)) != 0) {
|
|
evutil_closesocket(udp);
|
|
return EVUTIL_INVALID_SOCKET;
|
|
}
|
|
int on = 1;
|
|
if(setsockopt(udp, SOL_SOCKET, SO_BROADCAST, (const char*)&on, (ev_socklen_t)sizeof(on)) != 0) {
|
|
evutil_closesocket(udp);
|
|
return EVUTIL_INVALID_SOCKET;
|
|
}
|
|
//If this fails we can live with it
|
|
setsockopt(udp, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, (ev_socklen_t)sizeof(on));
|
|
|
|
sockaddr_in6 addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin6_family = AF_INET6;
|
|
addr.sin6_port = htons(7920);
|
|
struct ipv6_mreq group;
|
|
group.ipv6mr_interface = 0;
|
|
evutil_inet_pton(AF_INET6, "FF02::1", &group.ipv6mr_multiaddr);
|
|
|
|
if(setsockopt(udp, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char*)&group, (ev_socklen_t)sizeof(group)) != 0) {
|
|
evutil_closesocket(udp);
|
|
return EVUTIL_INVALID_SOCKET;
|
|
}
|
|
|
|
if(bind(udp, (sockaddr*)&addr, sizeof(addr)) == -1) {
|
|
evutil_closesocket(udp);
|
|
return EVUTIL_INVALID_SOCKET;
|
|
}
|
|
return udp;
|
|
#else
|
|
return EVUTIL_INVALID_SOCKET;
|
|
#endif
|
|
};
|
|
auto CreateIPV4BroadcastSocketListener = []() -> evutil_socket_t {
|
|
evutil_socket_t udp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if(udp == EVUTIL_INVALID_SOCKET)
|
|
return EVUTIL_INVALID_SOCKET;
|
|
int on = 1;
|
|
if(setsockopt(udp, SOL_SOCKET, SO_BROADCAST, (const char*)&on, (ev_socklen_t)sizeof(on)) != 0) {
|
|
evutil_closesocket(udp);
|
|
return EVUTIL_INVALID_SOCKET;
|
|
}
|
|
//If this fails we can live with it
|
|
setsockopt(udp, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, (ev_socklen_t)sizeof(on));
|
|
sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(7920);
|
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
if(bind(udp, (sockaddr*)&addr, sizeof(addr)) == -1) {
|
|
evutil_closesocket(udp);
|
|
return EVUTIL_INVALID_SOCKET;
|
|
}
|
|
return udp;
|
|
};
|
|
auto udp = CreateIPV6MulticastSocketListener();
|
|
if(udp == EVUTIL_INVALID_SOCKET)
|
|
udp = CreateIPV4BroadcastSocketListener();
|
|
if(udp == EVUTIL_INVALID_SOCKET)
|
|
return false;
|
|
broadcast_ev = event_new(net_evbase, udp, EV_READ | EV_PERSIST, BroadcastEvent, nullptr);
|
|
event_add(broadcast_ev, nullptr);
|
|
return true;
|
|
}
|
|
void NetServer::StopServer() {
|
|
if(!net_evbase)
|
|
return;
|
|
if(duel_mode)
|
|
duel_mode->EndDuel();
|
|
timeval time{ 0,1 };
|
|
event_base_loopexit(net_evbase, &time);
|
|
}
|
|
void NetServer::StopBroadcast() {
|
|
if(!net_evbase || !broadcast_ev)
|
|
return;
|
|
event_del(broadcast_ev);
|
|
evutil_socket_t fd;
|
|
event_get_assignment(broadcast_ev, 0, &fd, 0, 0, 0);
|
|
evutil_closesocket(fd);
|
|
event_free(broadcast_ev);
|
|
broadcast_ev = 0;
|
|
}
|
|
void NetServer::StopListen() {
|
|
evconnlistener_disable(listener);
|
|
StopBroadcast();
|
|
}
|
|
void NetServer::BroadcastEvent(evutil_socket_t fd, [[maybe_unused]] short events, [[maybe_unused]] void* arg) {
|
|
sockaddr_storage bc_addr;
|
|
ev_socklen_t sz = sizeof(bc_addr);
|
|
char buf[256];
|
|
int ret = recvfrom(fd, buf, 256, 0, (sockaddr*)&bc_addr, &sz);
|
|
if(ret == -1)
|
|
return;
|
|
HostRequest* pHR = (HostRequest*)buf;
|
|
if(pHR->identifier == NETWORK_CLIENT_ID) {
|
|
if(bc_addr.ss_family == AF_INET)
|
|
reinterpret_cast<sockaddr_in&>(bc_addr).sin_port = htons(7921);
|
|
else
|
|
reinterpret_cast<sockaddr_in6&>(bc_addr).sin6_port = htons(7921);
|
|
HostPacket hp{};
|
|
hp.identifier = NETWORK_SERVER_ID;
|
|
hp.port = server_port;
|
|
hp.version = PRO_VERSION;
|
|
hp.host = duel_mode->host_info;
|
|
BufferIO::EncodeUTF16(duel_mode->name, hp.name, 20);
|
|
sendto(fd, (const char*)&hp, sizeof(HostPacket), 0, (sockaddr*)&bc_addr, bc_addr.ss_family == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
|
|
}
|
|
}
|
|
void NetServer::ServerAccept([[maybe_unused]] evconnlistener* bev_listener, evutil_socket_t fd, [[maybe_unused]] sockaddr* address,
|
|
[[maybe_unused]] int socklen, [[maybe_unused]] void* ctx) {
|
|
bufferevent* bev = bufferevent_socket_new(net_evbase, fd, BEV_OPT_CLOSE_ON_FREE);
|
|
DuelPlayer dp;
|
|
dp.name[0] = 0;
|
|
dp.type = 0xff;
|
|
dp.bev = bev;
|
|
users[bev] = dp;
|
|
bufferevent_setcb(bev, ServerEchoRead, nullptr, ServerEchoEvent, nullptr);
|
|
bufferevent_enable(bev, EV_READ);
|
|
}
|
|
void NetServer::ServerAcceptError([[maybe_unused]] evconnlistener* bev_listener, [[maybe_unused]] void* ctx) {
|
|
event_base_loopexit(net_evbase, 0);
|
|
}
|
|
void NetServer::ServerEchoRead(bufferevent *bev, [[maybe_unused]] void *ctx) {
|
|
evbuffer* input = bufferevent_get_input(bev);
|
|
size_t len = evbuffer_get_length(input);
|
|
uint16_t packet_len = 0;
|
|
while(true) {
|
|
if(len < 2)
|
|
return;
|
|
evbuffer_copyout(input, &packet_len, 2);
|
|
if(len < (size_t)packet_len + 2)
|
|
return;
|
|
evbuffer_remove(input, net_server_read, packet_len + 2);
|
|
if(packet_len)
|
|
HandleCTOSPacket(&users[bev], &net_server_read[2], packet_len);
|
|
len -= packet_len + 2;
|
|
}
|
|
}
|
|
void NetServer::ServerEchoEvent(bufferevent* bev, short events, [[maybe_unused]] void* ctx) {
|
|
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
|
DuelPlayer* dp = &users[bev];
|
|
DuelMode* dm = dp->game;
|
|
if(dm)
|
|
dm->LeaveGame(dp);
|
|
else DisconnectPlayer(dp);
|
|
}
|
|
}
|
|
int NetServer::ServerThread() {
|
|
Utils::SetThreadName("GameServer");
|
|
event_base_dispatch(net_evbase);
|
|
for(auto bit = users.begin(); bit != users.end(); ++bit) {
|
|
bufferevent_disable(bit->first, EV_READ);
|
|
bufferevent_free(bit->first);
|
|
}
|
|
users.clear();
|
|
evconnlistener_free(listener);
|
|
listener = 0;
|
|
if(broadcast_ev) {
|
|
evutil_socket_t fd;
|
|
event_get_assignment(broadcast_ev, 0, &fd, 0, 0, 0);
|
|
evutil_closesocket(fd);
|
|
event_free(broadcast_ev);
|
|
broadcast_ev = nullptr;
|
|
}
|
|
if(duel_mode) {
|
|
event_free(duel_mode->etimer);
|
|
delete duel_mode;
|
|
}
|
|
duel_mode = nullptr;
|
|
event_base_free(net_evbase);
|
|
net_evbase = 0;
|
|
return 0;
|
|
}
|
|
void NetServer::DisconnectPlayer(DuelPlayer* dp) {
|
|
auto bit = users.find(dp->bev);
|
|
if(bit != users.end()) {
|
|
bufferevent_flush(dp->bev, EV_WRITE, BEV_FLUSH);
|
|
bufferevent_disable(dp->bev, EV_READ);
|
|
bufferevent_free(dp->bev);
|
|
users.erase(bit);
|
|
}
|
|
}
|
|
void NetServer::HandleCTOSPacket(DuelPlayer* dp, uint8_t* data, uint32_t len) {
|
|
static constexpr ClientVersion serverversion{ EXPAND_VERSION(CLIENT_VERSION) };
|
|
auto* pdata = data;
|
|
uint8_t pktType = BufferIO::Read<uint8_t>(pdata);
|
|
if((pktType != CTOS_SURRENDER) && (pktType != CTOS_CHAT) && (dp->state == 0xff || (dp->state && dp->state != pktType)))
|
|
return;
|
|
switch(pktType) {
|
|
case CTOS_RESPONSE: {
|
|
if(!dp->game || !duel_mode->pduel)
|
|
return;
|
|
duel_mode->GetResponse(dp, pdata, len - 1);
|
|
break;
|
|
}
|
|
case CTOS_TIME_CONFIRM: {
|
|
if(!dp->game || !duel_mode->pduel)
|
|
return;
|
|
duel_mode->TimeConfirm(dp);
|
|
break;
|
|
}
|
|
case CTOS_CHAT: {
|
|
if(!dp->game)
|
|
return;
|
|
duel_mode->Chat(dp, pdata, len - 1);
|
|
break;
|
|
}
|
|
case CTOS_UPDATE_DECK: {
|
|
if(!dp->game)
|
|
return;
|
|
duel_mode->UpdateDeck(dp, pdata, len - 1);
|
|
break;
|
|
}
|
|
case CTOS_HAND_RESULT: {
|
|
if(!dp->game)
|
|
return;
|
|
auto pkt = BufferIO::getStruct<CTOS_HandResult>(pdata, len - 1);
|
|
dp->game->HandResult(dp, pkt.res);
|
|
break;
|
|
}
|
|
case CTOS_TP_RESULT: {
|
|
if(!dp->game)
|
|
return;
|
|
auto pkt = BufferIO::getStruct<CTOS_TPResult>(pdata, len - 1);
|
|
dp->game->TPResult(dp, pkt.res);
|
|
break;
|
|
}
|
|
case CTOS_PLAYER_INFO: {
|
|
auto pkt = BufferIO::getStruct<CTOS_PlayerInfo>(pdata, len - 1);
|
|
BufferIO::CopyStr(pkt.name, dp->name, 20);
|
|
break;
|
|
}
|
|
case CTOS_CREATE_GAME: {
|
|
if(dp->game || duel_mode)
|
|
return;
|
|
auto pkt = BufferIO::getStruct<CTOS_CreateGame>(pdata, len - 1);
|
|
if((pkt.info.handshake != SERVER_HANDSHAKE || pkt.info.version != serverversion)) {
|
|
VersionError vererr{ serverversion };
|
|
NetServer::SendPacketToPlayer(dp, STOC_ERROR_MSG, vererr);
|
|
return;
|
|
}
|
|
pkt.info.team1 = std::max(1, std::min(pkt.info.team1, 3));
|
|
pkt.info.team2 = std::max(1, std::min(pkt.info.team2, 3));
|
|
duel_mode = new GenericDuel(pkt.info.team1, pkt.info.team2, !!(pkt.info.duel_flag_low & DUEL_RELAY), pkt.info.best_of);
|
|
duel_mode->etimer = event_new(net_evbase, 0, EV_TIMEOUT | EV_PERSIST, GenericDuel::GenericTimer, duel_mode);
|
|
timeval timeout = { 1, 0 };
|
|
event_add(duel_mode->etimer, &timeout);
|
|
uint32_t hash = 1;
|
|
for(auto lfit = gdeckManager->_lfList.begin(); lfit != gdeckManager->_lfList.end(); ++lfit) {
|
|
if(pkt.info.lflist == lfit->hash) {
|
|
hash = pkt.info.lflist;
|
|
break;
|
|
}
|
|
}
|
|
if(hash == 1)
|
|
pkt.info.lflist = gdeckManager->_lfList[0].hash;
|
|
duel_mode->host_info = pkt.info;
|
|
BufferIO::DecodeUTF16(pkt.name, duel_mode->name, 20);
|
|
BufferIO::DecodeUTF16(pkt.pass, duel_mode->pass, 20);
|
|
duel_mode->JoinGame(dp, 0, true);
|
|
StartBroadcast();
|
|
break;
|
|
}
|
|
case CTOS_JOIN_GAME: {
|
|
if(!duel_mode || ((len - 1) < sizeof(CTOS_JoinGame)))
|
|
break;
|
|
auto pkt = BufferIO::getStruct<CTOS_JoinGame>(pdata, len - 1);
|
|
duel_mode->JoinGame(dp, &pkt, false);
|
|
break;
|
|
}
|
|
case CTOS_LEAVE_GAME: {
|
|
if(!duel_mode)
|
|
break;
|
|
duel_mode->LeaveGame(dp);
|
|
break;
|
|
}
|
|
case CTOS_SURRENDER: {
|
|
if(!duel_mode)
|
|
break;
|
|
duel_mode->Surrender(dp);
|
|
break;
|
|
}
|
|
case CTOS_HS_TODUELIST: {
|
|
if(!duel_mode || duel_mode->pduel)
|
|
break;
|
|
duel_mode->ToDuelist(dp);
|
|
break;
|
|
}
|
|
case CTOS_HS_TOOBSERVER: {
|
|
if(!duel_mode || duel_mode->pduel)
|
|
break;
|
|
duel_mode->ToObserver(dp);
|
|
break;
|
|
}
|
|
case CTOS_HS_READY:
|
|
case CTOS_HS_NOTREADY: {
|
|
if(!duel_mode || duel_mode->pduel)
|
|
break;
|
|
duel_mode->PlayerReady(dp, (CTOS_HS_NOTREADY - pktType) != 0);
|
|
break;
|
|
}
|
|
case CTOS_HS_KICK: {
|
|
if(!duel_mode || duel_mode->pduel)
|
|
break;
|
|
auto pkt = BufferIO::getStruct<CTOS_Kick>(pdata, len - 1);
|
|
duel_mode->PlayerKick(dp, pkt.pos);
|
|
break;
|
|
}
|
|
case CTOS_HS_START: {
|
|
if(!duel_mode || duel_mode->pduel)
|
|
break;
|
|
duel_mode->StartDuel(dp);
|
|
break;
|
|
}
|
|
case CTOS_REMATCH_RESPONSE: {
|
|
if(!dp->game || !duel_mode || !duel_mode->seeking_rematch)
|
|
break;
|
|
auto pkt = BufferIO::getStruct<CTOS_RematchResponse>(pdata, len - 1);
|
|
duel_mode->RematchResult(dp, pkt.rematch);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|