mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-18 12:05:21 +02:00
Skate.exe contacts EA on every launch, offline mode included: error and session reports (collector.errors.ea.com), SDK telemetry (freeform-river.data.ea.com) and remote configuration (gcs.ea.com). - Block the game's lookups of ea.com and tnt-ea.com names inside the process (getaddrinfo and the other Winsock resolvers). Installed at bootstrap, and fails closed like the EA app launch block - Turn EA's error reporting (EadpErrorsData) off in game, next to the existing DingoTelemetry settings; the game ignores them on its command line - Leave EA's image CDN (dingo-dev-assets.akamaized.net) reachable for the fast-travel artwork, which only downloads images - Add a hostname test, contrib/ea-watch.ps1 to check the traffic, and a short note in the README's privacy section Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
155 lines
6.5 KiB
C++
155 lines
6.5 KiB
C++
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <Windows.h>
|
|
#include "ea_service_block.h"
|
|
|
|
#include "Engine/Core/Hooks/hooks.h"
|
|
#include "Engine/Core/Log/logging.h"
|
|
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
namespace dingosdk {
|
|
namespace {
|
|
|
|
using GetAddrInfoAFn = INT (WSAAPI*)(PCSTR, PCSTR, const ADDRINFOA*, PADDRINFOA*);
|
|
using GetAddrInfoWFn = INT (WSAAPI*)(PCWSTR, PCWSTR, const ADDRINFOW*, PADDRINFOW*);
|
|
using GetAddrInfoExAFn = INT (WSAAPI*)(PCSTR, PCSTR, DWORD, LPGUID, const ADDRINFOEXA*, PADDRINFOEXA*, timeval*,
|
|
LPOVERLAPPED, LPLOOKUPSERVICE_COMPLETION_ROUTINE, LPHANDLE);
|
|
using GetAddrInfoExWFn = INT (WSAAPI*)(PCWSTR, PCWSTR, DWORD, LPGUID, const ADDRINFOEXW*, PADDRINFOEXW*, timeval*,
|
|
LPOVERLAPPED, LPLOOKUPSERVICE_COMPLETION_ROUTINE, LPHANDLE);
|
|
using GetHostByNameFn = hostent* (WSAAPI*)(const char*);
|
|
|
|
std::atomic<GetAddrInfoAFn> original_get_addr_info_a{};
|
|
std::atomic<GetAddrInfoWFn> original_get_addr_info_w{};
|
|
std::atomic<GetAddrInfoExAFn> original_get_addr_info_ex_a{};
|
|
std::atomic<GetAddrInfoExWFn> original_get_addr_info_ex_w{};
|
|
std::atomic<GetHostByNameFn> original_get_host_by_name{};
|
|
|
|
// A refused name is logged once at info level; a service that cannot connect keeps retrying, so
|
|
// repeats are logged at debug level only. Never destroyed, because lookups can still arrive while
|
|
// the process exits.
|
|
struct RefusedNames {
|
|
std::mutex mutex;
|
|
std::set<std::wstring> names;
|
|
};
|
|
RefusedNames& refused_names() {
|
|
static auto* value = new RefusedNames;
|
|
return *value;
|
|
}
|
|
|
|
template<class Char>
|
|
bool refuse(const Char* name) noexcept {
|
|
if (!name) return false;
|
|
const std::basic_string_view<Char> host(name);
|
|
if (!ea_service_host(host)) return false;
|
|
try {
|
|
std::wstring wide;
|
|
wide.reserve(host.size());
|
|
for (const auto ch : host) wide.push_back(static_cast<wchar_t>(static_cast<std::make_unsigned_t<Char>>(ch)));
|
|
bool first{};
|
|
{
|
|
auto& refused = refused_names();
|
|
const std::lock_guard lock(refused.mutex);
|
|
first = refused.names.insert(wide).second;
|
|
}
|
|
if (first)
|
|
logging::write(logging::Level::info, logging::Channel::runtime,
|
|
L"Blocked the game's lookup of " + wide + L": ReSkate keeps it off EA's online services");
|
|
else if (logging::enabled(logging::Level::debug))
|
|
logging::write(logging::Level::debug, logging::Channel::runtime, L"Blocked the game's lookup of " + wide + L" again");
|
|
} catch (...) {}
|
|
return true;
|
|
}
|
|
|
|
// What the resolver itself answers for a name that does not exist.
|
|
template<class Result>
|
|
INT not_found(Result* result) noexcept {
|
|
if (result) *result = nullptr;
|
|
WSASetLastError(WSAHOST_NOT_FOUND);
|
|
return WSAHOST_NOT_FOUND;
|
|
}
|
|
|
|
INT WSAAPI get_addr_info_a(PCSTR name, PCSTR service, const ADDRINFOA* hints, PADDRINFOA* result) {
|
|
if (refuse(name)) return not_found(result);
|
|
return original_get_addr_info_a.load()(name, service, hints, result);
|
|
}
|
|
|
|
INT WSAAPI get_addr_info_w(PCWSTR name, PCWSTR service, const ADDRINFOW* hints, PADDRINFOW* result) {
|
|
if (refuse(name)) return not_found(result);
|
|
return original_get_addr_info_w.load()(name, service, hints, result);
|
|
}
|
|
|
|
// Failing before the lookup starts is a valid answer for an overlapped call too: the completion
|
|
// routine and event are only used once the function has returned WSA_IO_PENDING.
|
|
INT WSAAPI get_addr_info_ex_a(PCSTR name, PCSTR service, DWORD name_space, LPGUID provider, const ADDRINFOEXA* hints,
|
|
PADDRINFOEXA* result, timeval* timeout, LPOVERLAPPED overlapped,
|
|
LPLOOKUPSERVICE_COMPLETION_ROUTINE completion, LPHANDLE handle) {
|
|
if (refuse(name)) {
|
|
if (handle) *handle = nullptr;
|
|
return not_found(result);
|
|
}
|
|
return original_get_addr_info_ex_a.load()(name, service, name_space, provider, hints, result, timeout, overlapped,
|
|
completion, handle);
|
|
}
|
|
|
|
INT WSAAPI get_addr_info_ex_w(PCWSTR name, PCWSTR service, DWORD name_space, LPGUID provider, const ADDRINFOEXW* hints,
|
|
PADDRINFOEXW* result, timeval* timeout, LPOVERLAPPED overlapped,
|
|
LPLOOKUPSERVICE_COMPLETION_ROUTINE completion, LPHANDLE handle) {
|
|
if (refuse(name)) {
|
|
if (handle) *handle = nullptr;
|
|
return not_found(result);
|
|
}
|
|
return original_get_addr_info_ex_w.load()(name, service, name_space, provider, hints, result, timeout, overlapped,
|
|
completion, handle);
|
|
}
|
|
|
|
hostent* WSAAPI get_host_by_name(const char* name) {
|
|
if (refuse(name)) {
|
|
WSASetLastError(WSAHOST_NOT_FOUND);
|
|
return nullptr;
|
|
}
|
|
return original_get_host_by_name.load()(name);
|
|
}
|
|
|
|
template<class Fn>
|
|
void hook(const char* name, void* replacement, std::atomic<Fn>& original) {
|
|
const auto module = LoadLibraryExW(L"ws2_32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
|
const auto target = module ? reinterpret_cast<void*>(GetProcAddress(module, name)) : nullptr;
|
|
if (!target) throw std::runtime_error(std::string("Missing ") + name);
|
|
void* relay{};
|
|
const auto create = hook_prepare(target, replacement, &relay);
|
|
if (create != HookOk) throw std::runtime_error(std::string("Cannot hook ") + name + ": " + hook_status_string(create));
|
|
original.store(reinterpret_cast<Fn>(relay));
|
|
const auto enable = hook_enable(target);
|
|
if (enable != HookOk) throw std::runtime_error(std::string("Cannot enable ") + name + ": " + hook_status_string(enable));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool start_ea_service_block(std::string& error) noexcept {
|
|
try {
|
|
// Skate.exe resolves names through getaddrinfo alone; the other entry points close the
|
|
// same door for anything else loaded into the process.
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4191)
|
|
hook("getaddrinfo", reinterpret_cast<void*>(&get_addr_info_a), original_get_addr_info_a);
|
|
hook("GetAddrInfoW", reinterpret_cast<void*>(&get_addr_info_w), original_get_addr_info_w);
|
|
hook("GetAddrInfoExA", reinterpret_cast<void*>(&get_addr_info_ex_a), original_get_addr_info_ex_a);
|
|
hook("GetAddrInfoExW", reinterpret_cast<void*>(&get_addr_info_ex_w), original_get_addr_info_ex_w);
|
|
hook("gethostbyname", reinterpret_cast<void*>(&get_host_by_name), original_get_host_by_name);
|
|
#pragma warning(pop)
|
|
error.clear();
|
|
return true;
|
|
} catch (const std::exception& exception) {
|
|
error = exception.what();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace dingosdk
|