From 8c0d999a2aa511cd8867928fa0e2f067f32ace61 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 14 Jun 2022 23:56:15 +0200 Subject: [PATCH] HTTP functions #5 --- src/component/command.cpp | 2 +- src/component/gsc.cpp | 38 ++++++++ src/component/http.cpp | 177 ++++++++++++++++++++++++++++++++++ src/component/string.cpp | 7 +- src/game/game.cpp | 6 ++ src/game/game.hpp | 3 + src/game/scripting/array.hpp | 2 + src/game/scripting/object.cpp | 18 +--- src/game/scripting/object.hpp | 2 - src/game/symbols.hpp | 5 +- src/utils/http.cpp | 35 ++++--- src/utils/http.hpp | 12 ++- 12 files changed, 272 insertions(+), 35 deletions(-) create mode 100644 src/component/http.cpp diff --git a/src/component/command.cpp b/src/component/command.cpp index 749734b..42baa29 100644 --- a/src/component/command.cpp +++ b/src/component/command.cpp @@ -57,7 +57,7 @@ namespace command const scripting::entity player = game::Scr_GetEntityId(game::SCRIPTINSTANCE_SERVER, client_num, 0, 0); const auto command = utils::string::to_lower(params[0]); - const auto message = params.join(1); + const auto message = params.join(1).substr(1); for (const auto& callback : chat_callbacks) { diff --git a/src/component/gsc.cpp b/src/component/gsc.cpp index 5444a09..00165da 100644 --- a/src/component/gsc.cpp +++ b/src/component/gsc.cpp @@ -207,6 +207,18 @@ namespace gsc return scr_get_builtin_hook.invoke(inst, func_name); } + + // Scr_NotifyId doesn't exist, Scr_NotifyNum_Internal calls FindVariableId to get the variable id from entnum, classnum & clientNum + // to not have to recreate Scr_NotifyId we simply make FindVariableId return `entnum` (which in this case will be the id) if `clientNum` == -1 + unsigned int find_variable_id_stub(int inst, int entnum, unsigned int classnum, int client_num) + { + if (client_num == -1) + { + return entnum; + } + + return utils::hook::invoke(SELECT_VALUE(0x5E96E0, 0x40BEF0), inst, entnum, classnum, client_num); + } } namespace function @@ -251,6 +263,8 @@ namespace gsc utils::hook::set(SELECT_VALUE(0x9FC5C0 + 40, 0xAABA68 + 40), '\n'); utils::hook::set(SELECT_VALUE(0x9FC5C0 + 41, 0xAABA68 + 41), '\0'); + utils::hook::call(SELECT_VALUE(0x41D2B5, 0x416325), find_variable_id_stub); + gsc::function::add("array", [](const scripting::variadic_args& va) { scripting::array array{}; @@ -267,6 +281,30 @@ namespace gsc { return value.type_name(); }, "typeof", "type"); + + gsc::function::add("debug::get_var_count", []() + { + auto count = 0; + + if (game::environment::is_sp()) + { + for (auto i = 1; i < 0x5FFE; i++) + { + const auto var = game::scr_VarGlob->variableList_mp[i]; + count += var.w.status != 0; + } + } + else + { + for (auto i = 1; i < 0x7FFE; i++) + { + const auto var = game::scr_VarGlob->variableList_mp[i]; + count += var.w.status != 0; + } + } + + return count; + }); } }; } diff --git a/src/component/http.cpp b/src/component/http.cpp new file mode 100644 index 0000000..633af9b --- /dev/null +++ b/src/component/http.cpp @@ -0,0 +1,177 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/structs.hpp" +#include "game/game.hpp" + +#include "gsc.hpp" +#include "scheduler.hpp" +#include "scripting.hpp" + +#include +#include + +namespace http +{ + std::unordered_map active_requests{}; + uint64_t request_id{}; + + class component final : public component_interface + { + public: + void post_unpack() override + { + scripting::on_shutdown([]() + { + active_requests.clear(); + }); + + gsc::function::add_multiple([](const std::string& url) + { + const auto id = request_id++; + active_requests[id] = true; + + const auto object = scripting::object{}; + const auto object_id = object.get_entity_id(); + + scheduler::once([id, object_id, url]() + { + const auto data = utils::http::get_data(url); + scheduler::once([id, object_id, data]() + { + if (active_requests.find(id) == active_requests.end()) + { + return; + } + + if (!data.has_value()) + { + scripting::notify(object_id, "done", {{}, false, "Unknown error"}); + return; + } + + const auto& result = data.value(); + const auto error = curl_easy_strerror(result.code); + + if (result.code != CURLE_OK) + { + scripting::notify(object_id, "done", {{}, false, error}); + return; + } + + if (result.buffer.size() >= 0x5000) + { + printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast(result.buffer.size())); + } + + scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true}); + }, scheduler::pipeline::server); + }, scheduler::pipeline::async); + + return object; + }, "http::get", "httpget", "curl"); + + gsc::function::add("http::request", [](const std::string& url, const scripting::variadic_args& va) + { + const auto id = request_id++; + active_requests[id] = true; + + const auto object = scripting::object{}; + const auto object_id = object.get_entity_id(); + + std::string fields_string{}; + std::unordered_map headers_map{}; + + if (va.size() > 0) + { + const auto options = va[0].as(); + + const auto fields = options["parameters"]; + const auto body = options["body"]; + const auto headers = options["headers"]; + + if (fields.is()) + { + const auto fields_ = fields.as(); + const auto keys = fields_.get_keys(); + + for (const auto& key : keys) + { + if (!key.is()) + { + continue; + } + + const auto key_ = key.as(); + const auto value = fields_[key].to_string(); + fields_string += key_ + "=" + value + "&"; + } + + } + + if (body.is()) + { + fields_string = body.as(); + } + + if (headers.is()) + { + const auto headers_ = headers.as(); + const auto keys = headers_.get_keys(); + + for (const auto& key : keys) + { + if (!key.is()) + { + continue; + } + + const auto key_ = key.as(); + const auto value = headers_[key].to_string(); + + headers_map[key_] = value; + } + } + } + + scheduler::once([id, object_id, url, fields_string, headers_map]() + { + const auto data = utils::http::get_data(url, fields_string, headers_map); + scheduler::once([data, object_id, id] + { + if (active_requests.find(id) == active_requests.end()) + { + return; + } + + if (!data.has_value()) + { + scripting::notify(object_id, "done", {{}, false, "Unknown error"}); + return; + } + + const auto& result = data.value(); + const auto error = curl_easy_strerror(result.code); + + if (result.code != CURLE_OK) + { + scripting::notify(object_id, "done", {{}, false, error}); + return; + } + + if (result.buffer.size() >= 0x5000) + { + printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast(result.buffer.size())); + } + + scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true}); + }, scheduler::pipeline::server); + }, scheduler::pipeline::async); + + return object; + }); + } + }; +} + +REGISTER_COMPONENT(http::component) diff --git a/src/component/string.cpp b/src/component/string.cpp index 5df6155..2f049d8 100644 --- a/src/component/string.cpp +++ b/src/component/string.cpp @@ -172,11 +172,14 @@ namespace string gsc::function::add("print", [](const scripting::variadic_args& va) { + std::string buffer{}; + for (const auto& arg : va) { - printf("%s\t", arg.to_string().data()); + buffer.append(utils::string::va("%s\t", arg.to_string().data())); } - printf("\n"); + + printf("%s\n", buffer.data()); }); gsc::function::add_multiple(utils::string::to_upper, "toupper", "string::to_upper"); diff --git a/src/game/game.cpp b/src/game/game.cpp index 0bfbe80..1cef98c 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -67,6 +67,12 @@ namespace game return utils::hook::invoke(func, inst); } + void Scr_NotifyId(scriptInstance_t inst, int /*client_num*/, unsigned int id, + unsigned int string_value, unsigned int paramcount) + { + game::Scr_NotifyNum_Internal(inst, -1, id, 0, string_value, paramcount); + } + VariableValue Scr_GetArrayIndexValue(scriptInstance_t, unsigned int name) { VariableValue value{}; diff --git a/src/game/game.hpp b/src/game/game.hpp index c39e538..48ab1fd 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -65,6 +65,9 @@ namespace game VariableValue Scr_GetArrayIndexValue(scriptInstance_t inst, unsigned int name); unsigned int Scr_GetSelf(scriptInstance_t inst, unsigned int threadId); + + void Scr_NotifyId(scriptInstance_t inst, int client_num, unsigned int id, + unsigned int string_value, unsigned int paramcount); } #include "symbols.hpp" diff --git a/src/game/scripting/array.hpp b/src/game/scripting/array.hpp index 34b3e19..a7ba48f 100644 --- a/src/game/scripting/array.hpp +++ b/src/game/scripting/array.hpp @@ -72,6 +72,8 @@ namespace scripting { return {this->id_, this->get_value_id(key.as())}; } + + throw std::runtime_error("Invalid key type"); } private: diff --git a/src/game/scripting/object.cpp b/src/game/scripting/object.cpp index 5bf24ca..ebff53c 100644 --- a/src/game/scripting/object.cpp +++ b/src/game/scripting/object.cpp @@ -63,17 +63,7 @@ namespace scripting object::object() { - this->id_ = make_object(); - } - - object::object(std::unordered_map values) - { - this->id_ = make_object(); - - for (const auto& value : values) - { - this->set(value.first, value.second); - } + this->id_ = game::AllocObject(game::SCRIPTINSTANCE_SERVER); } object::~object() @@ -157,14 +147,14 @@ namespace scripting return game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, this->id_); } - void object::erase(const std::string& key) const + void object::erase(const std::string& /*key*/) const { - const auto string_value = game::SL_GetCanonicalString(key.data(), 0); + /*const auto string_value = game::SL_GetCanonicalString(key.data(), 0); const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value); if (variable_id) { game::RemoveVariableValue(game::SCRIPTINSTANCE_SERVER, this->id_, variable_id); - } + }*/ } script_value object::get(const std::string& /*key*/) const diff --git a/src/game/scripting/object.hpp b/src/game/scripting/object.hpp index d6732ea..974444f 100644 --- a/src/game/scripting/object.hpp +++ b/src/game/scripting/object.hpp @@ -19,8 +19,6 @@ namespace scripting object(); object(const unsigned int); - object(std::unordered_map); - object(const object& other); object(object&& other) noexcept; diff --git a/src/game/symbols.hpp b/src/game/symbols.hpp index d811451..b48129f 100644 --- a/src/game/symbols.hpp +++ b/src/game/symbols.hpp @@ -41,7 +41,7 @@ namespace game WEAK symbol Scr_ClearOutParams{0x654D10, 0x588680}; - WEAK symbol AllocObject{0x0, 0x0}; + WEAK symbol AllocObject{0x603400, 0x6970B0}; WEAK symbol AllocThread{0x69E140, 0x43CA60}; WEAK symbol RemoveRefToObject{0x5517B0, 0x698FA0}; WEAK symbol RemoveRefToVector{0x0, 0x0}; @@ -69,7 +69,8 @@ namespace game WEAK symbol GetEntityFieldValue{0x0, 0x0}; WEAK symbol Scr_Notify{0x0, 0x0}; - WEAK symbol Scr_NotifyId{0x0, 0x0}; + WEAK symbol Scr_NotifyNum_Internal{0x41D270, 0x4162E0}; WEAK symbol Scr_NotifyNum{0x0, 0x0}; WEAK symbol SL_GetString{0x463130, 0x4B1770}; diff --git a/src/utils/http.cpp b/src/utils/http.cpp index 03acabf..2daea66 100644 --- a/src/utils/http.cpp +++ b/src/utils/http.cpp @@ -1,6 +1,6 @@ #include #include "http.hpp" -#include + #include #pragma comment(lib, "ws2_32.lib") @@ -45,7 +45,8 @@ namespace utils::http } } - std::optional get_data(const std::string& url, const headers& headers, const std::function& callback) + std::optional get_data(const std::string& url, const std::string& fields, + const headers& headers, const std::function& callback) { curl_slist* header_list = nullptr; auto* curl = curl_easy_init(); @@ -78,9 +79,27 @@ namespace utils::http curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); - if (curl_easy_perform(curl) == CURLE_OK) + if (!fields.empty()) { - return {std::move(buffer)}; + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields.data()); + } + + const auto code = curl_easy_perform(curl); + + if (code == CURLE_OK) + { + result result; + result.code = code; + result.buffer = std::move(buffer); + + return result; + } + else + { + result result; + result.code = code; + + return result; } if (helper.exception) @@ -90,12 +109,4 @@ namespace utils::http return {}; } - - std::future> get_data_async(const std::string& url, const headers& headers) - { - return std::async(std::launch::async, [url, headers]() - { - return get_data(url, headers); - }); - } } \ No newline at end of file diff --git a/src/utils/http.hpp b/src/utils/http.hpp index 94c4451..4fc7e4a 100644 --- a/src/utils/http.hpp +++ b/src/utils/http.hpp @@ -4,10 +4,18 @@ #include #include +#include + namespace utils::http { + struct result + { + CURLcode code; + std::string buffer; + }; + using headers = std::unordered_map; - std::optional get_data(const std::string& url, const headers& headers = {}, const std::function& callback = {}); - std::future> get_data_async(const std::string& url, const headers& headers = {}); + std::optional get_data(const std::string& url, const std::string& fields = {}, + const headers& headers = {}, const std::function& callback = {}); } \ No newline at end of file