patcher/PolygonDLL/Hooks/Http.cpp

199 lines
7.6 KiB
C++

#include "pch.h"
#include "Util.h"
#include "Hooks/Http.h"
#define CHECK(condition, code) \
if(!error) { \
if ((condition)) { \
error = code; \
} \
}
Http__httpGetPostWinInet_t Http__httpGetPostWinInet = (Http__httpGetPostWinInet_t)ADDRESS_HTTP__HTTPGETPOSTWININET;
Http__trustCheck_t Http__trustCheck = (Http__trustCheck_t)ADDRESS_HTTP__TRUSTCHECK;
size_t write(char* contents, size_t size, size_t memory, void* pointer)
{
((std::string*)pointer)->append((char*)contents, size * memory);
return size * memory;
}
void __fastcall Http__httpGetPostWinInet_hook(Http* _this, void*, bool isPost, int a3, bool compressData, LPCSTR additionalHeaders, int a6)
{
CURLU* curl = curl_url();
CURLUcode result = curl_url_set(curl, CURLUPART_URL, _this->url.c_str(), 0);
Http _changed = *_this;
if (!result)
{
char* path;
char* host;
char* query;
curl_url_get(curl, CURLUPART_PATH, &path, 0);
curl_url_get(curl, CURLUPART_HOST, &host, 0);
curl_url_get(curl, CURLUPART_QUERY, &query, 0);
curl_url_cleanup(curl);
if (path != NULL && host != NULL && query != NULL)
{
std::string _path = Util::toLower(std::string(path));
std::string _host = std::string(host);
std::string _query = std::string(query);
if (_host == "roblox.com" || _host == "www.roblox.com")
{
if (_path == "/asset" || _path == "/asset/" || _path == "/asset/default.ashx")
{
_changed.url = "https://assetdelivery.roblox.com/v1/asset/?" + _query;
_this = &_changed;
}
else if (_path == "/thumbs/asset.ashx" || _path == "/thumbs/avatar.ashx")
{
/*
Both Roblox endpoints require thumbnailFormatId to be set. We will make the default value for it as 0.
Asset.ashx -> requires overrideModeration (default false) -> /asset/request-thumbnail-fix
Avatar.ashx -> requires dummy (default false) -> /avatar/request-thumbnail-fix
1. Parse query
2. Construct a brand new blank query with thumbnailFormatId as 0 and dummy/overrideModeration as false (if Avatar.ashx or Asset.ashx)
3. Merge the old query with priority over the old query so that if they declared any of the special variables, ours gets overwritten
4. Rename id (if found) to assetId or userId (specific to the endpoint)
5. Append to the Roblox url (specific to the endpoint)
6. Fetch Roblox API
7. Parse JSON
8. Set the URL as the given url
*/
std::string api = "https://www.roblox.com/" + std::string(_path == "/thumbs/asset.ashx" ? "asset" : "avatar") + "/request-thumbnail-fix";
std::map<std::string, std::string> source = Util::parseQueryString(query);
std::map<std::string, std::string> fixed = {
{ _path == "/thumbs/asset.ashx" ? "overrideModeration" : "dummy", "false" },
{ "thumbnailFormatId", "0" }
};
// the modern Roblox API doesn't care about parameter capitalization because of asp.net quirks
// thus, we are able to do this :-)
for (auto& pair : source)
{
fixed[Util::toLower(pair.first)] = pair.second;
}
if (fixed.find("id") != fixed.end())
{
auto handler = fixed.extract("id");
handler.key() = _path == "/thumbs/asset.ashx" ? "assetId" : "userId";
fixed.insert(std::move(handler));
}
api += Util::joinQueryString(fixed);
// get the api response
CURL* curl = curl_easy_init();
CURLcode result;
long response = 0;
std::string data;
if (!curl)
{
throw std::runtime_error("Failed to initialize cURL");
}
curl_easy_setopt(curl, CURLOPT_URL, api);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
result = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
curl_easy_cleanup(curl);
if (result != CURLE_OK || response != 200)
{
throw std::runtime_error("Unexpected error occurred when fetching Roblox API: 0x0");
}
rapidjson::Document document;
document.Parse(data.c_str());
// Ryelow should fucking kill herself
int error = 0;
CHECK(document.HasParseError(), 0x01);
CHECK(!document.HasMember("d"), 0x02);
CHECK(!document["d"].IsObject(), 0x03);
CHECK(!document["d"].HasMember("url"), 0x04);
CHECK(!document["d"]["url"].IsString(), 0x05);
if (error != 0)
{
throw std::runtime_error("Unexpected error occurred when fetching Roblox API: 0x0" + std::to_string(error));
}
_changed.url = document["d"]["url"].GetString();
_this = &_changed;
}
}
}
}
Http__httpGetPostWinInet(_this, isPost, a3, compressData, additionalHeaders, a6);
}
bool __fastcall Http__trustCheck_hook(const char* url)
{
if (strlen(url) == 7 && !Util::isASCII(url))
{
// so the client does this really fucking stupid thing where if it opens an ie window,
// it passes a char**, and not a char*
// no idea if thats a detours quirk (i doubt it) or if thats how its just actually handled
// practically no url is ever going to be seven characters long so it doesn't really matter
url = *(char**)url; // wHOEVER DID THIS FUCKING CAST NEEDS TO GET RAPED BY 10 GORILLION GRIGRGERS
}
std::string _url = std::string(url);
// checking for embedded schemes must come BEFORE checking if it's valid
// cURL does not treat embedded resources as URLs
if (_url == "about:blank")
{
return true;
}
for (std::string allowedEmbeddedScheme : Util::allowedEmbeddedSchemes)
{
if (_url.rfind(allowedEmbeddedScheme + ":", 0) == 0)
{
return true;
}
}
CURLU* curl = curl_url();
CURLUcode result = curl_url_set(curl, CURLUPART_URL, url, 0);
if (result != CURLE_OK)
{
return false;
}
char* scheme;
char* host;
curl_url_get(curl, CURLUPART_SCHEME, &scheme, 0);
curl_url_get(curl, CURLUPART_HOST, &host, 0);
curl_url_cleanup(curl);
if (std::find(Util::allowedSchemes.begin(), Util::allowedSchemes.end(), std::string(scheme)) != Util::allowedSchemes.end())
{
return std::find(Util::allowedHosts.begin(), Util::allowedHosts.end(), std::string(host)) != Util::allowedHosts.end();
}
return false;
}