mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-17 03:20:06 +00:00
dongresource
d5fe1cc513
In certain circumstances, like when running a private server through Hamachi, the shard IP will be set to an address the local machine can't reach itself from, preventing only the local player from getting past character selection. This workaround detects local connections and sends a loopback address for the shard instead of the configured one. This makes those use cases feasible.
476 lines
14 KiB
C++
476 lines
14 KiB
C++
#include "CNProtocol.hpp"
|
|
#include "CNStructs.hpp"
|
|
|
|
#include <assert.h>
|
|
|
|
// ========================================================[[ CNSocketEncryption ]]========================================================
|
|
|
|
// literally C/P from the client and converted to C++ (does some byte swapping /shrug)
|
|
int CNSocketEncryption::Encrypt_byte_change_A(int ERSize, uint8_t* data, int size) {
|
|
int num = 0;
|
|
int num2 = 0;
|
|
int num3 = 0;
|
|
|
|
while (num + ERSize <= size) {
|
|
int num4 = num + num3;
|
|
int num5 = num + (ERSize - 1 - num3);
|
|
|
|
uint8_t b = data[num4];
|
|
data[num4] = data[num5];
|
|
data[num5] = b;
|
|
num += ERSize;
|
|
num3++;
|
|
if (num3 > ERSize / 2) {
|
|
num3 = 0;
|
|
}
|
|
}
|
|
|
|
num2 = ERSize - (num + ERSize - size);
|
|
return num + num2;
|
|
}
|
|
|
|
int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
|
|
// xor every 8 bytes with 8 byte key
|
|
for (int i = 0; i < size; i++) {
|
|
buffer[i] ^= key[i % keyLength];
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
|
uint64_t num = (uint64_t)(iv1 + 1);
|
|
uint64_t num2 = (uint64_t)(iv2 + 1);
|
|
uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
|
|
return dEKey * (uTime * num * num2);
|
|
}
|
|
|
|
int CNSocketEncryption::encryptData(uint8_t* buffer, uint8_t* key, int size) {
|
|
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // C/P from client
|
|
int size2 = xorData(buffer, key, size);
|
|
return Encrypt_byte_change_A(eRSize, buffer, size2);
|
|
}
|
|
|
|
int CNSocketEncryption::decryptData(uint8_t* buffer, uint8_t* key, int size) {
|
|
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // size % of 18????
|
|
int size2 = Encrypt_byte_change_A(eRSize, buffer, size);
|
|
return xorData(buffer, key, size2);
|
|
}
|
|
|
|
// ========================================================[[ CNPacketData ]]========================================================
|
|
|
|
CNPacketData::CNPacketData(void* b, uint32_t t, int l): buf(b), size(l), type(t) {}
|
|
|
|
// ========================================================[[ CNSocket ]]========================================================
|
|
|
|
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
|
|
EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
|
|
}
|
|
|
|
bool CNSocket::sendData(uint8_t* data, int size) {
|
|
int sentBytes = 0;
|
|
int maxTries = 10;
|
|
|
|
while (sentBytes < size) {
|
|
int sent = send(sock, (buffer_t*)(data + sentBytes), size - sentBytes, 0);
|
|
if (SOCKETERROR(sent)) {
|
|
if (OF_ERRNO == OF_EWOULD && maxTries > 0) {
|
|
maxTries--;
|
|
continue; // try again
|
|
}
|
|
printSocketError("send");
|
|
return false; // error occured while sending bytes
|
|
}
|
|
sentBytes += sent;
|
|
}
|
|
|
|
return true; // it worked!
|
|
}
|
|
|
|
void CNSocket::setEKey(uint64_t k) {
|
|
EKey = k;
|
|
}
|
|
|
|
void CNSocket::setFEKey(uint64_t k) {
|
|
FEKey = k;
|
|
}
|
|
|
|
uint64_t CNSocket::getEKey() {
|
|
return EKey;
|
|
}
|
|
|
|
uint64_t CNSocket::getFEKey() {
|
|
return FEKey;
|
|
}
|
|
|
|
bool CNSocket::isAlive() {
|
|
return alive;
|
|
}
|
|
|
|
void CNSocket::kill() {
|
|
alive = false;
|
|
#ifdef _WIN32
|
|
shutdown(sock, SD_BOTH);
|
|
closesocket(sock);
|
|
#else
|
|
shutdown(sock, SHUT_RDWR);
|
|
close(sock);
|
|
#endif
|
|
}
|
|
|
|
void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
|
|
if (!alive)
|
|
return;
|
|
|
|
uint8_t fullpkt[CN_PACKET_BUFFER_SIZE]; // length, type, body
|
|
uint8_t* body = fullpkt + 4; // packet without length (type, body)
|
|
size_t bodysize = size + 4;
|
|
|
|
// set packet length
|
|
memcpy(fullpkt, (void*)&bodysize, 4);
|
|
|
|
// copy packet type to the front of the buffer & then the actual buffer
|
|
memcpy(body, (void*)&type, 4);
|
|
memcpy(body+4, buf, size);
|
|
|
|
// encrypt the packet
|
|
switch (activeKey) {
|
|
case SOCKETKEY_E:
|
|
CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&EKey), bodysize);
|
|
break;
|
|
case SOCKETKEY_FE:
|
|
CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&FEKey), bodysize);
|
|
break;
|
|
default:
|
|
DEBUGLOG(
|
|
std::cout << "[WARN]: UNSET KEYTYPE FOR SOCKET!! ABORTING SEND" << std::endl;
|
|
)
|
|
return;
|
|
}
|
|
|
|
// send packet data!
|
|
if (alive && !sendData(fullpkt, bodysize+4))
|
|
kill();
|
|
}
|
|
|
|
void CNSocket::setActiveKey(ACTIVEKEY key) {
|
|
activeKey = key;
|
|
}
|
|
|
|
void CNSocket::step() {
|
|
// read step
|
|
|
|
// XXX NOTE: we must not recv() twice without a poll() inbetween
|
|
if (readSize <= 0) {
|
|
// we aren't reading a packet yet, try to start looking for one
|
|
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
|
if (recved == 0) {
|
|
// the socket was closed normally
|
|
kill();
|
|
} else if (!SOCKETERROR(recved)) {
|
|
// we got our packet size!!!!
|
|
readSize = *((int32_t*)readBuffer);
|
|
// sanity check
|
|
if (readSize > CN_PACKET_BUFFER_SIZE) {
|
|
kill();
|
|
return;
|
|
}
|
|
|
|
// we'll just leave bufferIndex at 0 since we already have the packet size, it's safe to overwrite those bytes
|
|
activelyReading = true;
|
|
} else if (OF_ERRNO != OF_EWOULD) {
|
|
// serious socket issue, disconnect connection
|
|
printSocketError("recv");
|
|
kill();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (readSize > 0 && readBufferIndex < readSize) {
|
|
// read until the end of the packet! (or at least try too)
|
|
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
|
if (recved == 0) {
|
|
// the socket was closed normally
|
|
kill();
|
|
} else if (!SOCKETERROR(recved))
|
|
readBufferIndex += recved;
|
|
else if (OF_ERRNO != OF_EWOULD) {
|
|
// serious socket issue, disconnect connection
|
|
printSocketError("recv");
|
|
kill();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (activelyReading && readBufferIndex >= readSize) {
|
|
// decrypt readBuffer and copy to CNPacketData
|
|
CNSocketEncryption::decryptData((uint8_t*)&readBuffer, (uint8_t*)(&EKey), readSize);
|
|
|
|
void* tmpBuf = readBuffer+sizeof(uint32_t);
|
|
CNPacketData tmp(tmpBuf, *((uint32_t*)readBuffer), readSize-sizeof(int32_t));
|
|
|
|
// call packet handler!!
|
|
pHandler(this, &tmp);
|
|
|
|
// reset vars :)
|
|
readSize = 0;
|
|
readBufferIndex = 0;
|
|
activelyReading = false;
|
|
}
|
|
}
|
|
|
|
void printSocketError(const char *call) {
|
|
#ifdef _WIN32
|
|
std::cerr << call << ": ";
|
|
|
|
LPSTR lpMsgBuf = nullptr; // string buffer
|
|
DWORD errCode = WSAGetLastError(); // error code
|
|
|
|
if (errCode == 0) {
|
|
std::cerr << "no error code" << std::endl;
|
|
return;
|
|
}
|
|
|
|
size_t bufSize = FormatMessageA( // actually get the error message
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
errCode, // in
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR)&lpMsgBuf, // out
|
|
0, NULL);
|
|
|
|
// convert buffer to string and output message to terminal
|
|
std::string msg(lpMsgBuf, bufSize);
|
|
std::cerr << msg; // newline included
|
|
|
|
LocalFree(lpMsgBuf); // free the buffer
|
|
#else
|
|
perror(call);
|
|
#endif
|
|
}
|
|
|
|
bool setSockNonblocking(SOCKET listener, SOCKET newSock) {
|
|
#ifdef _WIN32
|
|
unsigned long mode = 1;
|
|
if (ioctlsocket(newSock, FIONBIO, &mode) != 0) {
|
|
#else
|
|
if (fcntl(newSock, F_SETFL, (fcntl(newSock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
|
#endif
|
|
printSocketError("fcntl");
|
|
std::cerr << "[WARN] OpenFusion: fcntl failed on new connection" << std::endl;
|
|
#ifdef _WIN32
|
|
shutdown(newSock, SD_BOTH);
|
|
closesocket(newSock);
|
|
#else
|
|
shutdown(newSock, SHUT_RDWR);
|
|
close(newSock);
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ========================================================[[ CNServer ]]========================================================
|
|
|
|
void CNServer::init() {
|
|
// create socket file descriptor
|
|
sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (SOCKETINVALID(sock)) {
|
|
printSocketError("socket");
|
|
std::cerr << "[FATAL] OpenFusion: socket failed" << std::endl;
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// attach socket to the port
|
|
int opt = 1;
|
|
#ifdef _WIN32
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
|
|
#else
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
|
|
#endif
|
|
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
|
|
printSocketError("setsockopt");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.s_addr = INADDR_ANY;
|
|
address.sin_port = htons(port);
|
|
|
|
addressSize = sizeof(address);
|
|
|
|
// Bind to the port
|
|
if (SOCKETERROR(bind(sock, (struct sockaddr *)&address, addressSize))) {
|
|
std::cerr << "[FATAL] OpenFusion: bind failed" << std::endl;
|
|
printSocketError("bind");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (SOCKETERROR(listen(sock, SOMAXCONN))) {
|
|
std::cerr << "[FATAL] OpenFusion: listen failed" << std::endl;
|
|
printSocketError("listen");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// set server listener to non-blocking
|
|
#ifdef _WIN32
|
|
unsigned long mode = 1;
|
|
if (ioctlsocket(sock, FIONBIO, &mode) != 0) {
|
|
#else
|
|
if (fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
|
#endif
|
|
printSocketError("fcntl");
|
|
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// poll() configuration
|
|
fds.reserve(STARTFDSCOUNT);
|
|
fds.push_back({sock, POLLIN});
|
|
}
|
|
|
|
CNServer::CNServer() {};
|
|
CNServer::CNServer(uint16_t p): port(p) {}
|
|
|
|
void CNServer::addPollFD(SOCKET s) {
|
|
fds.push_back({s, POLLIN});
|
|
}
|
|
|
|
void CNServer::removePollFD(int i) {
|
|
auto it = fds.begin();
|
|
while (it != fds.end() && it->fd != fds[i].fd)
|
|
it++;
|
|
assert(it != fds.end());
|
|
|
|
fds.erase(it);
|
|
}
|
|
|
|
void CNServer::start() {
|
|
std::cout << "Starting server at *:" << port << std::endl;
|
|
while (active) {
|
|
// the timeout is to ensure shard timers are ticking
|
|
int n = poll(fds.data(), fds.size(), 50);
|
|
if (SOCKETERROR(n)) {
|
|
#ifndef _WIN32
|
|
if (errno == EINTR)
|
|
continue;
|
|
#endif
|
|
std::cout << "[FATAL] poll() returned error" << std::endl;
|
|
printSocketError("poll");
|
|
terminate(0);
|
|
}
|
|
|
|
for (int i = 0; i < fds.size() && n > 0; i++) {
|
|
if (fds[i].revents == 0)
|
|
continue; // nothing in this one; don't decrement n
|
|
|
|
n--;
|
|
|
|
// is it the listener?
|
|
if (fds[i].fd == sock) {
|
|
// any sort of error on the listener
|
|
if (fds[i].revents & ~POLLIN) {
|
|
std::cout << "[FATAL] Error on listener socket" << std::endl;
|
|
terminate(0);
|
|
}
|
|
|
|
SOCKET newConnectionSocket = accept(sock, (struct sockaddr *)&address, (socklen_t*)&addressSize);
|
|
if (SOCKETINVALID(newConnectionSocket)) {
|
|
printSocketError("accept");
|
|
continue;
|
|
}
|
|
|
|
if (!setSockNonblocking(sock, newConnectionSocket))
|
|
continue;
|
|
|
|
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
|
|
|
addPollFD(newConnectionSocket);
|
|
|
|
// add connection to list!
|
|
CNSocket* tmp = new CNSocket(newConnectionSocket, address, pHandler);
|
|
connections[newConnectionSocket] = tmp;
|
|
newConnection(tmp);
|
|
|
|
} else if (checkExtraSockets(i)) {
|
|
// no-op. handled in checkExtraSockets().
|
|
|
|
} else {
|
|
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
|
|
|
|
// player sockets
|
|
if (connections.find(fds[i].fd) == connections.end()) {
|
|
std::cout << "[WARN] Event on non-existant socket?" << std::endl;
|
|
continue; // just to be safe
|
|
}
|
|
|
|
CNSocket* cSock = connections[fds[i].fd];
|
|
|
|
// kill the socket on hangup/error
|
|
if (fds[i].revents & ~POLLIN)
|
|
cSock->kill();
|
|
|
|
if (cSock->isAlive()) {
|
|
cSock->step();
|
|
} else {
|
|
killConnection(cSock);
|
|
connections.erase(fds[i].fd);
|
|
delete cSock;
|
|
|
|
removePollFD(i);
|
|
|
|
// a new entry was moved to this position, so we check it again
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
onStep();
|
|
}
|
|
}
|
|
|
|
void CNServer::kill() {
|
|
std::lock_guard<std::mutex> lock(activeCrit); // the lock will be removed when the function ends
|
|
active = false;
|
|
|
|
// kill all connections
|
|
for (auto& pair : connections) {
|
|
CNSocket *cSock = pair.second;
|
|
if (cSock->isAlive())
|
|
cSock->kill();
|
|
|
|
delete cSock;
|
|
}
|
|
|
|
connections.clear();
|
|
}
|
|
|
|
void CNServer::printPacket(CNPacketData *data, int type) {
|
|
if (settings::VERBOSITY < 2)
|
|
return;
|
|
|
|
if (settings::VERBOSITY < 3) switch (data->type) {
|
|
case P_CL2LS_REP_LIVE_CHECK:
|
|
case P_CL2FE_REP_LIVE_CHECK:
|
|
case P_CL2FE_REQ_PC_MOVE:
|
|
case P_CL2FE_REQ_PC_JUMP:
|
|
case P_CL2FE_REQ_PC_SLOPE:
|
|
case P_CL2FE_REQ_PC_MOVEPLATFORM:
|
|
case P_CL2FE_REQ_PC_MOVETRANSPORTATION:
|
|
case P_CL2FE_REQ_PC_ZIPLINE:
|
|
case P_CL2FE_REQ_PC_JUMPPAD:
|
|
case P_CL2FE_REQ_PC_LAUNCHER:
|
|
case P_CL2FE_REQ_PC_STOP:
|
|
return;
|
|
}
|
|
|
|
std::cout << "OpenFusion: received " << Defines::p2str(type, data->type) << " (" << data->type << ")" << std::endl;
|
|
}
|
|
|
|
bool CNServer::checkExtraSockets(int i) { return false; } // stubbed
|
|
void CNServer::newConnection(CNSocket* cns) {} // stubbed
|
|
void CNServer::killConnection(CNSocket* cns) {} // stubbed
|
|
void CNServer::onStep() {} // stubbed
|