The actual vtable hook function - replaces CClientUnifiedServiceTransport::vtable[5]
| 1942 | |
| 1943 | // The actual vtable hook function - replaces CClientUnifiedServiceTransport::vtable[5] |
| 1944 | static bool __fastcall ServiceMethodHook(void* thisptr, const char* methodName, |
| 1945 | void* request, void* response, int64_t* flags) { |
| 1946 | HookGuard guard; |
| 1947 | if (g_shuttingDown.load(std::memory_order_acquire)) |
| 1948 | return g_originalSlot5(thisptr, methodName, request, response, flags); |
| 1949 | if (!methodName) { |
| 1950 | return g_originalSlot5(thisptr, methodName, request, response, flags); |
| 1951 | } |
| 1952 | |
| 1953 | if (g_parentalBypassPlaytime.load() && |
| 1954 | strcmp(methodName, "Parental.GetSignedParentalSettings#1") == 0) { |
| 1955 | bool result = g_originalSlot5(thisptr, methodName, request, response, flags); |
| 1956 | if (result && response) { |
| 1957 | void* respBody = *(void**)((uintptr_t)response + 48); |
| 1958 | if (respBody) { |
| 1959 | auto respBytes = SerializeBodyToBytes(respBody); |
| 1960 | auto respFields = PB::Parse(respBytes.data(), respBytes.size()); |
| 1961 | const PB::Field* sf = PB::FindField(respFields, 1); |
| 1962 | if (sf && sf->wireType == PB::LengthDelimited && sf->data) { |
| 1963 | auto stripped = ParentalBypass::StripPlaytimeRestrictions(sf->data, sf->dataLen); |
| 1964 | PB::Writer newResp; |
| 1965 | newResp.WriteBytes(1, stripped.data(), stripped.size()); |
| 1966 | for (const auto& f : respFields) { |
| 1967 | if (f.fieldNum == 1) continue; |
| 1968 | if (f.wireType == PB::Varint) newResp.WriteVarint(f.fieldNum, f.varintVal); |
| 1969 | else if (f.wireType == PB::Fixed64) newResp.WriteFixed64(f.fieldNum, f.varintVal); |
| 1970 | else if (f.wireType == PB::LengthDelimited) newResp.WriteBytes(f.fieldNum, f.data, f.dataLen); |
| 1971 | } |
| 1972 | if (ParseBytesToBody(respBody, newResp.Data().data(), newResp.Size())) |
| 1973 | LOG("[Parental] Stripped restrictions from GetSignedParentalSettings (slot5)"); |
| 1974 | } |
| 1975 | } |
| 1976 | } |
| 1977 | return result; |
| 1978 | } |
| 1979 | |
| 1980 | // ---- Native stats / playtime service RPCs -------------------------------- |
| 1981 | // Player.GetUserStats#1: answer namespace apps from our store for the achievement page. |
| 1982 | if (strcmp(methodName, StatsHandlers::RPC_GET_USER_STATS) == 0 |
| 1983 | && (g_cloudSaveOnly.load(std::memory_order_relaxed) |
| 1984 | ? MetadataSync::schemaFetch.load(std::memory_order_relaxed) |
| 1985 | : MetadataSync::SchemaFetchEnabled())) { |
| 1986 | if (request && response) { |
| 1987 | void* reqBody = *(void**)((uintptr_t)request + 48); |
| 1988 | if (reqBody) { |
| 1989 | auto reqBytes = SerializeBodyToBytes(reqBody); |
| 1990 | auto reqFields = PB::Parse(reqBytes.data(), reqBytes.size()); |
| 1991 | // appid = field 2 in CPlayer_GetUserStats_Request |
| 1992 | uint32_t appId = 0; |
| 1993 | if (auto* f = PB::FindField(reqFields, 2)) appId = (uint32_t)f->varintVal; |
| 1994 | LOG("[Stats] slot5 GetUserStats seen: app=%u namespace=%d", appId, IsNamespaceApp(appId) ? 1 : 0); |
| 1995 | if (appId != 0 && IsNamespaceApp(appId)) { |
| 1996 | auto res = StatsHandlers::HandleGetUserStats(appId, reqFields); |
| 1997 | void* respBody = *(void**)((uintptr_t)response + 48); |
| 1998 | if (respBody && res.body.Size() > 0 && |
| 1999 | ParseBytesToBody(respBody, res.body.Data().data(), res.body.Size())) { |
| 2000 | if (flags) *flags = 0; |
| 2001 | LOG("[Stats] GetUserStats app=%u handled locally (%zu bytes)", |
nothing calls this directly
no test coverage detected