[Proxy] Cache method params, so we don't have to disassemble everytime

This commit is contained in:
momo5502 2017-02-22 09:32:24 +01:00
parent f5d5e76ac1
commit 7f8f73e39e
2 changed files with 81 additions and 102 deletions

View File

@ -28,63 +28,46 @@ namespace Steam
std::function<Proxy::SteamFreeLastCallbackFn> Proxy::SteamFreeLastCallback; std::function<Proxy::SteamFreeLastCallbackFn> Proxy::SteamFreeLastCallback;
std::function<Proxy::SteamGetAPICallResultFn> Proxy::SteamGetAPICallResult; std::function<Proxy::SteamGetAPICallResultFn> Proxy::SteamGetAPICallResult;
void* Interface::getMethod(std::string method) std::pair<void*, uint16_t> Interface::getMethod(std::string method)
{ {
if(this->methodCache.find(method) != this->methodCache.end()) if(this->methodCache.find(method) != this->methodCache.end())
{ {
return this->methodCache[method]; return this->methodCache[method];
} }
void* methodPtr = Interface::lookupMethod(method); auto methodData = Interface::lookupMethod(method);
this->methodCache[method] = methodPtr; this->methodCache[method] = methodData;
return methodPtr; return methodData;
} }
void* Interface::lookupMethod(std::string method) std::pair<void*, uint16_t> Interface::lookupMethod(std::string method)
{ {
if (::Utils::Memory::IsBadReadPtr(this->interfacePtr)) return nullptr; if (!::Utils::Memory::IsBadReadPtr(this->interfacePtr))
unsigned char** vftbl = *static_cast<unsigned char***>(this->interfacePtr);
while (!::Utils::Memory::IsBadReadPtr(vftbl) && !::Utils::Memory::IsBadCodePtr((FARPROC(*vftbl))))
{ {
if(this->getMethodName(*vftbl) == method) return *vftbl; unsigned char** vftbl = *static_cast<unsigned char***>(this->interfacePtr);
++vftbl;
}
return nullptr; while (!::Utils::Memory::IsBadReadPtr(vftbl) && !::Utils::Memory::IsBadCodePtr((FARPROC(*vftbl))))
}
size_t Interface::getMethodParamSize(void* method)
{
if (::Utils::Memory::IsBadCodePtr(method)) return 0;
ud_t ud;
ud_init(&ud);
ud_set_mode(&ud, 32);
ud_set_pc(&ud, reinterpret_cast<uint64_t>(method));
ud_set_input_buffer(&ud, reinterpret_cast<uint8_t*>(method), INT32_MAX);
while (true)
{
ud_disassemble(&ud);
if (ud_insn_mnemonic(&ud) == UD_Iret)
{ {
auto operand = ud_insn_opr(&ud, 0); std::string name;
if (operand->type == UD_OP_IMM && operand->size == 16) uint16_t params;
if (this->getMethodData(*vftbl, &name, &params) && name == method)
{ {
return static_cast<size_t>(operand->lval.uword); return{ *vftbl, params };
} }
break; ++vftbl;
} }
} }
return 0; return { nullptr, 0 };
} }
std::string Interface::getMethodName(unsigned char* methodPtr) bool Interface::getMethodData(unsigned char* methodPtr, std::string* name, uint16_t* params)
{ {
if (::Utils::Memory::IsBadCodePtr(methodPtr)) return ""; name->clear();
*params = 0;
if (::Utils::Memory::IsBadCodePtr(methodPtr)) return false;
ud_t ud; ud_t ud;
ud_init(&ud); ud_init(&ud);
@ -95,20 +78,37 @@ namespace Steam
while (true) while (true)
{ {
ud_disassemble(&ud); ud_disassemble(&ud);
if (ud_insn_mnemonic(&ud) == UD_Iret) break;
if (ud_insn_mnemonic(&ud) == UD_Ipush) if (ud_insn_mnemonic(&ud) == UD_Iret)
{
const ud_operand* operand = ud_insn_opr(&ud, 0);
if (!operand) break;
if (operand->type == UD_OP_IMM && operand->size == 16)
{
*params = operand->lval.uword;
return true;
}
break;
}
if (ud_insn_mnemonic(&ud) == UD_Ipush && name->empty())
{ {
auto operand = ud_insn_opr(&ud, 0); auto operand = ud_insn_opr(&ud, 0);
if (operand->type == UD_OP_IMM && operand->size == 32) if (operand->type == UD_OP_IMM && operand->size == 32)
{ {
char* operandPtr = reinterpret_cast<char*>(operand->lval.udword); char* operandPtr = reinterpret_cast<char*>(operand->lval.udword);
if (!::Utils::Memory::IsBadReadPtr(operandPtr)) return operandPtr; if (!::Utils::Memory::IsBadReadPtr(operandPtr))
{
name->clear();
name->append(operandPtr);
}
} }
} }
} }
return ""; return false;
} }
void Proxy::SetGame(uint32_t appId) void Proxy::SetGame(uint32_t appId)
@ -147,56 +147,38 @@ namespace Steam
char* modId = "IW4x"; char* modId = "IW4x";
gameID.modID = *reinterpret_cast<unsigned int*>(modId) | 0x80000000; gameID.modID = *reinterpret_cast<unsigned int*>(modId) | 0x80000000;
Interface clientUtils(Proxy::ClientEngine->GetIClientUtils(Proxy::SteamPipe, "CLIENTUTILS_INTERFACE_VERSION001"));
clientUtils.invoke<void>("SetAppIDForCurrentPipe", Proxy::AppId, false);
// Interface clientApps(Proxy::ClientEngine->GetIClientApps(Proxy::SteamUser, Proxy::SteamPipe, "CLIENTAPPS_INTERFACE_VERSION001")); char ourPath[MAX_PATH] = { 0 };
// Interface clientShortcuts(Proxy::ClientEngine->GetIClientShortcuts(Proxy::SteamUser, Proxy::SteamPipe, "CLIENTSHORTCUTS_INTERFACE_VERSION001")); GetModuleFileNameA(GetModuleHandle(nullptr), ourPath, sizeof(ourPath));
// if (!clientApps || !clientShortcuts) return;
// char ourDirectory[MAX_PATH] = { 0 };
// KeyValuesBuilder builder; GetCurrentDirectoryA(sizeof(ourDirectory), ourDirectory);
// builder.packString("name", mod.data());
// builder.packUint64("gameid", gameID.bits); std::string cmdline = ::Utils::String::VA("\"%s\" -proc %d", ourPath, GetCurrentProcessId());
// builder.packString("installed", "1");
// builder.packString("gamedir", "IW4x"); // As of 02/19/2017, the SpawnProcess method doesn't require the app id anymore,
// builder.packString("serverbrowsername", "IW4x"); // but only for those who participate in the beta.
// builder.packEnd(); // Therefore we have to check how many bytes the method expects as arguments
// // and adapt our call accordingly!
// std::string str = builder.getString(); size_t expectedParams = Proxy::ClientUser.paramSize("SpawnProcess");
// uint32_t uniqueId = clientShortcuts.invoke<uint32_t>("GetUniqueLocalAppId"); if (expectedParams == 40) // Release
// if (clientApps.invoke<bool>("SetLocalAppConfig", uniqueId, str.data(), static_cast<uint32_t>(str.size())))
{ {
Interface clientUtils(Proxy::ClientEngine->GetIClientUtils(Proxy::SteamPipe, "CLIENTUTILS_INTERFACE_VERSION001")); Proxy::ClientUser.invoke<bool>("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, Proxy::AppId, mod.data(), 0, 0);
clientUtils.invoke<void>("SetAppIDForCurrentPipe", Proxy::AppId, false); }
else if (expectedParams == 36) // Beta
char ourPath[MAX_PATH] = { 0 }; {
GetModuleFileNameA(GetModuleHandle(nullptr), ourPath, sizeof(ourPath)); Proxy::ClientUser.invoke<bool>("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, mod.data(), 0, 0);
}
char ourDirectory[MAX_PATH] = { 0 }; else if (expectedParams == 48) // Legacy, expects VAC blob
GetCurrentDirectoryA(sizeof(ourDirectory), ourDirectory); {
char blob[8] = { 0 };
std::string cmdline = ::Utils::String::VA("\"%s\" -proc %d", ourPath, GetCurrentProcessId()); Proxy::ClientUser.invoke<bool>("SpawnProcess", blob, 0, ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, Proxy::AppId, mod.data(), 0, 0);
}
// As of 02/19/2017, the SpawnProcess method doesn't require the app id anymore, else
// but only for those who participate in the beta. {
// Therefore we have to check how many bytes the method expects as arguments OutputDebugStringA("Steam proxy was unable to match the arguments for SpawnProcess!\n");
// and adapt our call accordingly!
size_t expectedParams = Proxy::ClientUser.paramSize("SpawnProcess");
if(expectedParams == 40) // Release
{
Proxy::ClientUser.invoke<bool>("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, Proxy::AppId, mod.data(), 0, 0);
}
else if(expectedParams == 36) // Beta
{
Proxy::ClientUser.invoke<bool>("SpawnProcess", ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, mod.data(), 0, 0);
}
else if (expectedParams == 48) // Legacy, expects VAC blob
{
char blob[8] = { 0 };
Proxy::ClientUser.invoke<bool>("SpawnProcess", blob, 0, ourPath, cmdline.data(), 0, ourDirectory, gameID.bits, Proxy::AppId, mod.data(), 0, 0);
}
else
{
OutputDebugStringA("Steam proxy was unable to match the arguments for SpawnProcess!\n");
}
} }
} }

View File

@ -146,14 +146,14 @@ namespace Steam
return T(); return T();
} }
void* method = this->getMethod(methodName); auto method = this->getMethod(methodName);
if (!method) if (!method.first)
{ {
OutputDebugStringA(::Utils::String::VA("Steam interface method %s not found!\n", methodName.data())); OutputDebugStringA(::Utils::String::VA("Steam interface method %s not found!\n", methodName.data()));
return T(); return T();
} }
size_t argc = this->getMethodParamSize(method); size_t argc = method.second;
constexpr size_t passedArgc = Interface::AddSizes<sizeof(Args)...>::value; constexpr size_t passedArgc = Interface::AddSizes<sizeof(Args)...>::value;
if(passedArgc != argc) if(passedArgc != argc)
{ {
@ -161,7 +161,7 @@ namespace Steam
return T(); return T();
} }
return reinterpret_cast<T(__thiscall*)(void*, Args ...)>(method)(this->interfacePtr, args...); return reinterpret_cast<T(__thiscall*)(void*, Args ...)>(method.first)(this->interfacePtr, args...);
} }
explicit operator bool() const explicit operator bool() const
@ -171,14 +171,12 @@ namespace Steam
size_t paramSize(std::string methodName) size_t paramSize(std::string methodName)
{ {
void* method = this->getMethod(methodName); auto method = this->getMethod(methodName);
if (method) return this->getMethodParamSize(method); return method.second;
return 0;
} }
private: private:
// TODO: Use fold expressions in C++17 once available // TODO: Use fold expressions once available (C++17)
template<std::size_t ...> template<std::size_t ...>
struct AddSizes : std::integral_constant<std::size_t, 0> {}; struct AddSizes : std::integral_constant<std::size_t, 0> {};
@ -187,11 +185,10 @@ namespace Steam
struct AddSizes<X, Xs...> : std::integral_constant<std::size_t, X + ((AddSizes<Xs...>::value + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1))> {}; struct AddSizes<X, Xs...> : std::integral_constant<std::size_t, X + ((AddSizes<Xs...>::value + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1))> {};
void* interfacePtr; void* interfacePtr;
std::unordered_map<std::string, void*> methodCache; std::unordered_map<std::string, std::pair<void*, uint16_t>> methodCache;
void* getMethod(std::string method); std::pair<void*, uint16_t> getMethod(std::string method);
void* lookupMethod(std::string method); std::pair<void*, uint16_t> lookupMethod(std::string method);
size_t getMethodParamSize(void* method); bool getMethodData(unsigned char* methodPtr, std::string* name, uint16_t* params);
std::string getMethodName(unsigned char* methodPtr);
}; };
class KeyValuesBuilder class KeyValuesBuilder