#include #include "loader/component_loader.hpp" #include #include #include "game/game.hpp" #include #include "game/demonware/servers/lobby_server.hpp" #include "game/demonware/servers/auth3_server.hpp" #include "game/demonware/servers/stun_server.hpp" #include "game/demonware/servers/umbrella_server.hpp" #include "game/demonware/server_registry.hpp" #include "console.hpp" #define TCP_BLOCKING true #define UDP_BLOCKING false namespace demonware { namespace { volatile bool exit_server; std::thread server_thread; utils::concurrency::container> blocking_sockets; utils::concurrency::container> socket_map; server_registry tcp_servers; server_registry udp_servers; tcp_server* find_server(const SOCKET socket) { return socket_map.access([&](const std::unordered_map& map) -> tcp_server* { const auto entry = map.find(socket); if (entry == map.end()) { return nullptr; } return entry->second; }); } bool socket_link(const SOCKET socket, const uint32_t address) { auto* server = tcp_servers.find(address); if (!server) { return false; } socket_map.access([&](std::unordered_map& map) { map[socket] = server; }); return true; } void socket_unlink(const SOCKET socket) { socket_map.access([&](std::unordered_map& map) { const auto entry = map.find(socket); if (entry != map.end()) { map.erase(entry); } }); } bool is_socket_blocking(const SOCKET socket, const bool def) { return blocking_sockets.access([&](std::unordered_map& map) { const auto entry = map.find(socket); if (entry == map.end()) { return def; } return entry->second; }); } void remove_blocking_socket(const SOCKET socket) { blocking_sockets.access([&](std::unordered_map& map) { const auto entry = map.find(socket); if (entry != map.end()) { map.erase(entry); } }); } void add_blocking_socket(const SOCKET socket, const bool block) { blocking_sockets.access([&](std::unordered_map& map) { map[socket] = block; }); } void server_main() { exit_server = false; while (!exit_server) { tcp_servers.frame(); udp_servers.frame(); std::this_thread::sleep_for(50ms); } } namespace io { int getaddrinfo_stub(const char* name, const char* service, const addrinfo* hints, addrinfo** res) { #ifdef DEBUG printf("[ network ]: [getaddrinfo]: \"%s\" \"%s\"\n", name, service); #endif base_server* server = tcp_servers.find(name); if (!server) { server = udp_servers.find(name); } if (!server) { return getaddrinfo(name, service, hints, res); } const auto address = utils::memory::get_allocator()->allocate(); const auto ai = utils::memory::get_allocator()->allocate(); auto in_addr = reinterpret_cast(address); in_addr->sin_addr.s_addr = server->get_address(); in_addr->sin_family = AF_INET; ai->ai_family = AF_INET; ai->ai_socktype = SOCK_STREAM; ai->ai_addr = address; ai->ai_addrlen = sizeof(sockaddr); ai->ai_next = nullptr; ai->ai_flags = 0; ai->ai_protocol = 0; ai->ai_canonname = const_cast(name); *res = ai; return 0; } void freeaddrinfo_stub(addrinfo* ai) { if (!utils::memory::get_allocator()->find(ai)) { return freeaddrinfo(ai); } utils::memory::get_allocator()->free(ai->ai_addr); utils::memory::get_allocator()->free(ai); } int getpeername_stub(const SOCKET s, sockaddr* addr, socklen_t* addrlen) { auto* server = find_server(s); printf("getpeername\n"); if (server) { auto in_addr = reinterpret_cast(addr); in_addr->sin_addr.s_addr = server->get_address(); in_addr->sin_family = AF_INET; *addrlen = sizeof(sockaddr); return 0; } return getpeername(s, addr, addrlen); } int getsockname_stub(const SOCKET s, sockaddr* addr, socklen_t* addrlen) { auto* server = find_server(s); if (server) { auto in_addr = reinterpret_cast(addr); in_addr->sin_addr.s_addr = server->get_address(); in_addr->sin_family = AF_INET; *addrlen = sizeof(sockaddr); return 0; } return getsockname(s, addr, addrlen); } hostent* gethostbyname_stub(const char* name) { #ifdef DEBUG printf("[ network ]: [gethostbyname]: \"%s\"\n", name); #endif base_server* server = tcp_servers.find(name); if (!server) { server = udp_servers.find(name); } if (!server) { #pragma warning(push) #pragma warning(disable: 4996) return gethostbyname(name); #pragma warning(pop) } static thread_local in_addr address{}; address.s_addr = server->get_address(); static thread_local in_addr* addr_list[2]{}; addr_list[0] = &address; addr_list[1] = nullptr; static thread_local hostent host{}; host.h_name = const_cast(name); host.h_aliases = nullptr; host.h_addrtype = AF_INET; host.h_length = sizeof(in_addr); host.h_addr_list = reinterpret_cast(addr_list); return &host; } int connect_stub(const SOCKET s, const struct sockaddr* addr, const int len) { if (len == sizeof(sockaddr_in)) { const auto* in_addr = reinterpret_cast(addr); if (socket_link(s, in_addr->sin_addr.s_addr)) return 0; } return connect(s, addr, len); } int closesocket_stub(const SOCKET s) { remove_blocking_socket(s); socket_unlink(s); return closesocket(s); } int send_stub(const SOCKET s, const char* buf, const int len, const int flags) { auto* server = find_server(s); printf("send: %s\n", buf); if (server) { server->handle_input(buf, len); return len; } return send(s, buf, len, flags); } int recv_stub(const SOCKET s, char* buf, const int len, const int flags) { auto* server = find_server(s); printf("recv: %s\n", buf); if (server) { if (server->pending_data()) { return static_cast(server->handle_output(buf, len)); } else { WSASetLastError(WSAEWOULDBLOCK); return -1; } } return recv(s, buf, len, flags); } int sendto_stub(const SOCKET s, const char* buf, const int len, const int flags, const sockaddr* to, const int tolen) { const auto* in_addr = reinterpret_cast(to); auto* server = udp_servers.find(in_addr->sin_addr.s_addr); printf("sendto: %s\n", buf); if (server) { server->handle_input(buf, len, { s, to, tolen }); return len; } return sendto(s, buf, len, flags, to, tolen); } int recvfrom_stub(const SOCKET s, char* buf, const int len, const int flags, struct sockaddr* from, int* fromlen) { //printf("recvfrom: %s\n", buf); // Not supported yet if (is_socket_blocking(s, UDP_BLOCKING)) { return recvfrom(s, buf, len, flags, from, fromlen); } size_t result = 0; udp_servers.for_each([&](udp_server& server) { if (server.pending_data(s)) { result = server.handle_output( s, buf, static_cast(len), from, fromlen); } }); if (result) { return static_cast(result); } return recvfrom(s, buf, len, flags, from, fromlen); } int select_stub(const int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout) { if (exit_server) { return select(nfds, readfds, writefds, exceptfds, timeout); } auto result = 0; std::vector read_sockets; std::vector write_sockets; socket_map.access([&](std::unordered_map& sockets) { for (auto& s : sockets) { if (readfds) { if (FD_ISSET(s.first, readfds)) { if (s.second->pending_data()) { read_sockets.push_back(s.first); FD_CLR(s.first, readfds); } } } if (writefds) { if (FD_ISSET(s.first, writefds)) { write_sockets.push_back(s.first); FD_CLR(s.first, writefds); } } if (exceptfds) { if (FD_ISSET(s.first, exceptfds)) { FD_CLR(s.first, exceptfds); } } } }); if ((!readfds || readfds->fd_count == 0) && (!writefds || writefds->fd_count == 0)) { timeout->tv_sec = 0; timeout->tv_usec = 0; } result = select(nfds, readfds, writefds, exceptfds, timeout); if (result < 0) result = 0; for (const auto& socket : read_sockets) { if (readfds) { FD_SET(socket, readfds); result++; } } for (const auto& socket : write_sockets) { if (writefds) { FD_SET(socket, writefds); result++; } } return result; } int ioctlsocket_stub(const SOCKET s, const long cmd, u_long* argp) { if (static_cast(cmd) == (FIONBIO)) { add_blocking_socket(s, *argp == 0); } return ioctlsocket(s, cmd, argp); } BOOL internet_get_connected_state_stub(LPDWORD, DWORD) { // Allow offline play return TRUE; } } void bd_logger_stub(int type, const char* const /*channelName*/, const char* /*fileLoc*/, const char* const /*file*/, const char* const function, const unsigned int /*line*/, const char* const msg, ...) { //static auto* enabled = // game::Dvar_RegisterBool("bd_logger", false, game::DVAR_FLAG_SAVED, "Log bd info to the console."); //if (!enabled->current.enabled) //{ // return; //} char buffer[2048]; va_list ap; va_start(ap, msg); vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap); console::print(type, "%s: %s", function, buffer); va_end(ap); } /*#ifdef DEBUG void a(unsigned int n) { printf("bdAuth: Auth task failed with HTTP code [%u]\n", n); } void b(unsigned int n) { printf("bdAuth: Decoded client ticket of unexpected size [%u]\n", n); } void c(unsigned int n) { printf("bdAuth: Decoded server ticket of unexpected size [%u]\n", n); } void d() { printf("bdAuth: Auth ticket magic number mismatch\n"); } void e() { printf("bdAuth: Cross Authentication completed\n"); } void f() { printf("bdAuth: Auth task reply contains invalid data / format\n"); } void g(unsigned int n) { printf("bdAuth: Auth task returned with error code [%u]\n", n); } void h(unsigned int n) { printf("bdAuth: Invalid or No Task ID [%u] in Auth reply\n", n); } void i() { printf("bdAuth: Received reply from DemonWare Auth server\n"); } void l() { printf("bdAuth: Unknown error\n"); } #endif*/ utils::hook::detour handle_auth_reply_hook; bool handle_auth_reply_stub(void* a1, void* a2, void* a3) { // Skip bdAuth::validateResponseSignature //utils::hook::set(0x7D4AB0_b, 0xC301B0); // Skip bdAuth::processPlatformData //utils::hook::set(0x7D55C0_b, 0xC301B0); //return handle_auth_reply_hook.invoke(a1, a2, a3); } void request_start_match_stub() { //const auto* args = "StartServer"; //utils::hook::invoke(0x1E35B0_b, 0, &args); } } class component final : public component_interface { public: component() { udp_servers.create("genesis.stun.us.demonware.net"); udp_servers.create("genesis.stun.eu.demonware.net"); udp_servers.create("genesis.stun.jp.demonware.net"); udp_servers.create("genesis.stun.au.demonware.net"); udp_servers.create("stun.us.demonware.net"); udp_servers.create("stun.eu.demonware.net"); udp_servers.create("stun.jp.demonware.net"); udp_servers.create("stun.au.demonware.net"); tcp_servers.create("iw-pc-steam-auth3.prod.demonware.net"); tcp_servers.create("iw-pc-steam-lobby.prod.demonware.net"); //tcp_servers.create("iw-pc-steam-qbl.prod.demonware.net"); tcp_servers.create("prod.umbrella.demonware.net"); } void post_load() override { server_thread = utils::thread::create_named_thread("Demonware", server_main); } void* load_import(const std::string& library, const std::string& function) override { if (library == "WS2_32.dll") { if (function == "#3") return io::closesocket_stub; if (function == "#4") return io::connect_stub; if (function == "#5") return io::getpeername_stub; if (function == "#6") return io::getsockname_stub; if (function == "#10") return io::ioctlsocket_stub; if (function == "#16") return io::recv_stub; if (function == "#17") return io::recvfrom_stub; if (function == "#18") return io::select_stub; if (function == "#19") return io::send_stub; if (function == "#20") return io::sendto_stub; if (function == "#52") return io::gethostbyname_stub; if (function == "getaddrinfo") return io::getaddrinfo_stub; if (function == "freeaddrinfo") return io::freeaddrinfo_stub; } if (function == "InternetGetConnectedState") { return io::internet_get_connected_state_stub; } return nullptr; } void post_unpack() override { utils::hook::jump(0x1285040_b, bd_logger_stub, true); utils::hook::set(0xB5BB96F_b, 0x0); // CURLOPT_SSL_VERIFYPEER utils::hook::set(0xB7C6CB1_b, 0xAF); // CURLOPT_SSL_VERIFYHOST utils::hook::set(0x15E4650_b, 0x0); // HTTPS -> HTTP // umbrella dev const char* umbrella_dev = "http://dev.umbrella.demonware.net"; std::memset(reinterpret_cast(0x15E8010_b), 0, strlen(umbrella_dev) + 1); std::memcpy(reinterpret_cast(0x15E8010_b), umbrella_dev, strlen(umbrella_dev)); // umbrella cert const char* umbrella_cert = "http://cert.umbrella.demonware.net"; std::memset(reinterpret_cast(0x15E8038_b), 0, strlen(umbrella_cert) + 1); std::memcpy(reinterpret_cast(0x15E8038_b), umbrella_cert, strlen(umbrella_cert)); // umbrella prod const char* umbrella_prod = "http://prod.umbrella.demonware.net"; std::memset(reinterpret_cast(0x15E8060_b), 0, strlen(umbrella_prod) + 1); std::memcpy(reinterpret_cast(0x15E8060_b), umbrella_prod, strlen(umbrella_prod)); // uno dev const char* uno_dev = "http://dev.uno.demonware.net/v1.0"; std::memset(reinterpret_cast(0x15E8418_b), 0, strlen(uno_dev) + 1); std::memcpy(reinterpret_cast(0x15E8418_b), uno_dev, strlen(uno_dev)); // uno cert const char* uno_cert = "http://cert.uno.demonware.net/v1.0"; std::memset(reinterpret_cast(0x15E8440_b), 0, strlen(uno_cert) + 1); std::memcpy(reinterpret_cast(0x15E8440_b), uno_cert, strlen(uno_cert)); // uno prod const char* uno = "http://prod.uno.demonware.net/v1.0"; std::memset(reinterpret_cast(0x15E8468_b), 0, strlen(uno) + 1); std::memcpy(reinterpret_cast(0x15E8468_b), uno, strlen(uno)); // auth const char* auth = "http://%s:%d/auth/"; std::memset(reinterpret_cast(0x15E3600_b), 0, strlen(auth) + 1); std::memcpy(reinterpret_cast(0x15E3600_b), auth, strlen(auth)); // utils::hook::set(0x19F8C0_b, 0xC3); // SV_SendMatchData, not sure //utils::hook::nop(0x19BB67_b, 5); // LiveStorage_SendMatchDataComplete //utils::hook::set(0x1A3340_b, 0xC3); // Live_CheckForFullDisconnect // Remove some while loop that freezes the rendering for a few secs while connecting //utils::hook::nop(0x625555_b, 5); //handle_auth_reply_hook.create(0x7AC600_b, handle_auth_reply_stub); // Skip update check in Live_SyncOnlineDataFlags //utils::hook::set(0x47A6D0_b, 0xC301B0); // Remove update failed popup //utils::hook::set(0x47B2B0_b, 0xC301B0); // xpartygo -> just start the match //utils::hook::jump(0x355B80_b, request_start_match_stub, true); //utils::hook::set(0x396AD0_b, 0xC301B0); // DB_IsZoneLoaded("ffotd") //utils::hook::set(0x4DD600_b, 0xC300B0); // dont use ffotd //utils::hook::set(0x4DD5B0_b, 0xC300B0); // dont dl ffotd } void pre_destroy() override { exit_server = true; if (server_thread.joinable()) { server_thread.join(); } } }; } //REGISTER_COMPONENT(demonware::component)