LUI (#126)
* lui scripting + server list * better hooks * add hks hook + fix dvars * add properties util for later * Add Heatbeats to Dedis (#109) * Add heatbeats to dedis * Address review changes * fix: server browser columns match game (#122) * fix: server browser columns match game * chore: remove unused debug logs --------- Co-authored-by: efinst0rm <efinst0rm@users.noreply.github.com> Co-authored-by: Liam <lierrmm@gmail.com>
This commit is contained in:
parent
2495cd4c5d
commit
885908ff15
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -35,3 +35,12 @@
|
||||
path = deps/gsc-tool
|
||||
url = https://github.com/Joelrau/gsc-tool.git
|
||||
branch = iw7
|
||||
[submodule "deps/sol2"]
|
||||
path = deps/sol2
|
||||
url = https://github.com/ThePhD/sol2.git
|
||||
[submodule "deps/lua"]
|
||||
path = deps/lua
|
||||
url = https://github.com/lua/lua.git
|
||||
[submodule "deps/curl"]
|
||||
path = deps/curl
|
||||
url = https://github.com/curl/curl.git
|
||||
|
1
deps/curl
vendored
Submodule
1
deps/curl
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 3f08d80b2244524646ce86915c585509ac54fb4c
|
1
deps/lua
vendored
Submodule
1
deps/lua
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7923dbbf72da303ca1cca17efd24725668992f15
|
73
deps/premake/curl.lua
vendored
Normal file
73
deps/premake/curl.lua
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
curl = {
|
||||
source = path.join(dependencies.basePath, "curl"),
|
||||
}
|
||||
|
||||
function curl.import()
|
||||
links { "curl" }
|
||||
|
||||
filter "toolset:msc*"
|
||||
links { "Crypt32.lib" }
|
||||
filter {}
|
||||
|
||||
curl.includes()
|
||||
end
|
||||
|
||||
function curl.includes()
|
||||
filter "toolset:msc*"
|
||||
includedirs {
|
||||
path.join(curl.source, "include"),
|
||||
}
|
||||
|
||||
defines {
|
||||
"CURL_STRICTER",
|
||||
"CURL_STATICLIB",
|
||||
"CURL_DISABLE_LDAP",
|
||||
}
|
||||
filter {}
|
||||
end
|
||||
|
||||
function curl.project()
|
||||
if not os.istarget("windows") then
|
||||
return
|
||||
end
|
||||
|
||||
project "curl"
|
||||
language "C"
|
||||
|
||||
curl.includes()
|
||||
|
||||
includedirs {
|
||||
path.join(curl.source, "lib"),
|
||||
}
|
||||
|
||||
files {
|
||||
path.join(curl.source, "lib/**.c"),
|
||||
path.join(curl.source, "lib/**.h"),
|
||||
}
|
||||
|
||||
defines {
|
||||
"BUILDING_LIBCURL",
|
||||
}
|
||||
|
||||
filter "toolset:msc*"
|
||||
|
||||
defines {
|
||||
"USE_SCHANNEL",
|
||||
"USE_WINDOWS_SSPI",
|
||||
"USE_THREADS_WIN32",
|
||||
}
|
||||
|
||||
filter "toolset:not msc*"
|
||||
|
||||
defines {
|
||||
"USE_GNUTLS",
|
||||
"USE_THREADS_POSIX",
|
||||
}
|
||||
|
||||
filter {}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, curl)
|
36
deps/premake/l_u_a.lua
vendored
Normal file
36
deps/premake/l_u_a.lua
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
l_u_a = {
|
||||
source = path.join(dependencies.basePath, "lua"),
|
||||
}
|
||||
|
||||
function l_u_a.import()
|
||||
links { "lua" }
|
||||
l_u_a.includes()
|
||||
end
|
||||
|
||||
function l_u_a.includes()
|
||||
includedirs {
|
||||
l_u_a.source
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
function l_u_a.project()
|
||||
project "lua"
|
||||
language "C"
|
||||
|
||||
l_u_a.includes()
|
||||
|
||||
files {
|
||||
path.join(l_u_a.source, "*.h"),
|
||||
path.join(l_u_a.source, "*.c"),
|
||||
}
|
||||
|
||||
removefiles {
|
||||
path.join(l_u_a.source, "onelua.c"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, l_u_a)
|
20
deps/premake/sol2.lua
vendored
Normal file
20
deps/premake/sol2.lua
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
sol2 = {
|
||||
source = path.join(dependencies.basePath, "sol2"),
|
||||
}
|
||||
|
||||
function sol2.import()
|
||||
sol2.includes()
|
||||
l_u_a.import()
|
||||
end
|
||||
|
||||
function sol2.includes()
|
||||
includedirs {
|
||||
path.join(sol2.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
function sol2.project()
|
||||
|
||||
end
|
||||
|
||||
table.insert(dependencies, sol2)
|
1
deps/sol2
vendored
Submodule
1
deps/sol2
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit e8e122e9ce46f4f1c0b04003d8b703fe1b89755a
|
@ -331,13 +331,19 @@ links {"common"}
|
||||
|
||||
prebuildcommands {"pushd %{_MAIN_SCRIPT_DIR}", "tools\\premake5 generate-buildinfo", "popd"}
|
||||
|
||||
COMPUTER_NAME = os.getenv('COMPUTERNAME')
|
||||
local COMPUTER_NAME = os.getenv('COMPUTERNAME')
|
||||
if COMPUTER_NAME == "JOEL-PC" then
|
||||
debugdir "D:\\Games\\PC\\IW7"
|
||||
debugcommand "D:\\Games\\PC\\IW7\\$(TargetName)$(TargetExt)"
|
||||
postbuildcommands {
|
||||
"copy /y \"$(OutDir)$(TargetName)$(TargetExt)\" \"D:\\Games\\PC\\IW7\\$(TargetName)$(TargetExt)\"",
|
||||
}
|
||||
elseif COMPUTER_NAME == "DESKTOP-P7PCR6I" or COMPUTER_NAME == "mikey" then
|
||||
debugdir "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Call of Duty - Infinite Warfare"
|
||||
debugcommand "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Call of Duty - Infinite Warfare\\$(TargetName)$(TargetExt)"
|
||||
postbuildcommands {
|
||||
"copy /y \"$(OutDir)$(TargetName)$(TargetExt)\" \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Call of Duty - Infinite Warfare\\$(TargetName)$(TargetExt)\"",
|
||||
}
|
||||
end
|
||||
|
||||
if _OPTIONS["copy-to"] then
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include "console/console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "server_list.hpp"
|
||||
#include "network.hpp"
|
||||
|
||||
#include <utils/json.hpp>
|
||||
|
||||
@ -23,6 +25,8 @@ namespace dedicated
|
||||
{
|
||||
utils::hook::detour com_quit_f_hook;
|
||||
|
||||
const game::dvar_t* sv_lanOnly;
|
||||
|
||||
void kill_server()
|
||||
{
|
||||
game::SV_MainMP_KillLocalServer();
|
||||
@ -77,6 +81,39 @@ namespace dedicated
|
||||
game::Scr_AddInt(0);
|
||||
}
|
||||
|
||||
void send_heartbeat()
|
||||
{
|
||||
if (sv_lanOnly->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::netadr_s target{};
|
||||
if (server_list::get_master_server(target))
|
||||
{
|
||||
network::send(target, "heartbeat", "IW7");
|
||||
}
|
||||
}
|
||||
|
||||
void sys_error_stub(const char* msg, ...)
|
||||
{
|
||||
char buffer[2048]{};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
scheduler::once([]
|
||||
{
|
||||
command::execute("map_rotate");
|
||||
}, scheduler::main, 3s);
|
||||
|
||||
game::Com_Error(game::ERR_DROP, "%s", buffer);
|
||||
}
|
||||
|
||||
void init_dedicated_server()
|
||||
{
|
||||
// R_RegisterDvars
|
||||
@ -189,7 +226,7 @@ namespace dedicated
|
||||
game::Dvar_RegisterBool("dedicated", true, game::DVAR_FLAG_READ, "Dedicated server");
|
||||
|
||||
// Add lanonly mode
|
||||
game::Dvar_RegisterBool("sv_lanOnly", false, game::DVAR_FLAG_NONE, "Don't send heartbeat");
|
||||
sv_lanOnly = game::Dvar_RegisterBool("sv_lanOnly", false, game::DVAR_FLAG_NONE, "Don't send heartbeat");
|
||||
|
||||
// Disable frontend
|
||||
//dvars::override::register_bool("frontEndSceneEnabled", false, game::DVAR_FLAG_READ);
|
||||
@ -203,6 +240,9 @@ namespace dedicated
|
||||
|
||||
dvars::override::register_bool("intro", false, game::DVAR_FLAG_READ);
|
||||
|
||||
// Stop crashing from sys_errors
|
||||
utils::hook::jump(0xD34180_b, sys_error_stub, true);
|
||||
|
||||
// Is party dedicated
|
||||
utils::hook::jump(0x5DFC10_b, party_is_server_dedicated_stub);
|
||||
|
||||
@ -340,6 +380,12 @@ namespace dedicated
|
||||
game::Cmd_RemoveCommand("disconnect");
|
||||
|
||||
execute_startup_command_queue();
|
||||
|
||||
// Send heartbeat to dpmaster
|
||||
scheduler::once(send_heartbeat, scheduler::pipeline::server);
|
||||
scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min);
|
||||
command::add("heartbeat", send_heartbeat);
|
||||
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
|
||||
// dedicated info
|
||||
@ -378,4 +424,4 @@ namespace dedicated
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dedicated::component)
|
||||
REGISTER_COMPONENT(dedicated::component)
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/properties.hpp>
|
||||
|
||||
namespace filesystem
|
||||
{
|
||||
@ -51,6 +52,7 @@ namespace filesystem
|
||||
|
||||
initialized = true;
|
||||
|
||||
filesystem::register_path(utils::properties::get_appdata_path() / "cdata"); // CLIENT_DATA_FOLDER
|
||||
filesystem::register_path(L".");
|
||||
filesystem::register_path(L"iw7-mod");
|
||||
filesystem::register_path(L"devraw_shared");
|
||||
|
@ -111,6 +111,13 @@ namespace lui
|
||||
game::LUI_CoD_Shutdown();
|
||||
game::LUI_CoD_Init(game::Com_FrontEnd_IsInFrontEnd(), false);
|
||||
});
|
||||
|
||||
command::add("runMenuScript", [](const command::params& params)
|
||||
{
|
||||
const auto args_str = params.join(1);
|
||||
const auto* args = args_str.data();
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "network.hpp"
|
||||
#include "profile_infos.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "server_list.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/info_string.hpp>
|
||||
@ -330,7 +331,7 @@ namespace party
|
||||
game::Com_SetLocalizedErrorMessage(error.data(), "MENU_NOTICE");
|
||||
}
|
||||
|
||||
connection_state get_server_connection_state()
|
||||
connection_state get_server_connection_state()
|
||||
{
|
||||
return server_connection_state;
|
||||
}
|
||||
@ -504,8 +505,8 @@ namespace party
|
||||
|
||||
network::on("infoResponse", [](const game::netadr_s& target, const std::string_view& data)
|
||||
{
|
||||
const utils::info_string info = data;
|
||||
//server_list::handle_info_response(target, info);
|
||||
const utils::info_string info{ data };
|
||||
server_list::handle_info_response(target, info);
|
||||
|
||||
if (server_connection_state.host != target)
|
||||
{
|
||||
|
@ -89,6 +89,7 @@ namespace scheduler
|
||||
utils::hook::detour r_end_frame_hook;
|
||||
utils::hook::detour g_run_frame_hook;
|
||||
utils::hook::detour main_frame_hook;
|
||||
utils::hook::detour hks_frame_hook;
|
||||
|
||||
void execute(const pipeline type)
|
||||
{
|
||||
@ -117,6 +118,16 @@ namespace scheduler
|
||||
|
||||
return main_frame_hook.invoke<void*>();
|
||||
}
|
||||
|
||||
void hks_frame_stub()
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
if (state)
|
||||
{
|
||||
execute(pipeline::lui);
|
||||
}
|
||||
hks_frame_hook.invoke<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
void schedule(const std::function<bool()>& callback, const pipeline type,
|
||||
@ -188,6 +199,7 @@ namespace scheduler
|
||||
r_end_frame_hook.create(0xE267B0_b, scheduler::r_end_frame_stub);
|
||||
g_run_frame_hook.create(0xB15E20_b, scheduler::server_frame_stub);
|
||||
main_frame_hook.create(0xB8E2D0_b, scheduler::main_frame_stub);
|
||||
hks_frame_hook.create(0x613440_b, scheduler::hks_frame_stub);
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
|
@ -16,6 +16,9 @@ namespace scheduler
|
||||
// The game's main thread
|
||||
main,
|
||||
|
||||
// LUI context
|
||||
lui,
|
||||
|
||||
count,
|
||||
};
|
||||
|
||||
|
@ -24,6 +24,8 @@ namespace scripting
|
||||
std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
|
||||
std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
|
||||
|
||||
utils::concurrency::container<shared_table_t> shared_table;
|
||||
|
||||
std::string current_file;
|
||||
|
||||
namespace
|
||||
@ -47,19 +49,16 @@ namespace scripting
|
||||
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
|
||||
game::VariableValue* top)
|
||||
{
|
||||
if (!game::Com_FrontEnd_IsInFrontEnd())
|
||||
const auto* string = game::SL_ConvertToString(string_value);
|
||||
if (string)
|
||||
{
|
||||
const auto* string = game::SL_ConvertToString(string_value);
|
||||
if (string)
|
||||
{
|
||||
event e{};
|
||||
e.name = string;
|
||||
e.entity = notify_list_owner_id;
|
||||
event e {};
|
||||
e.name = string;
|
||||
e.entity = notify_list_owner_id;
|
||||
|
||||
for (auto* value = top; value->type != game::VAR_PRECODEPOS; --value)
|
||||
{
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
for (auto* value = top; value->type != game::VAR_PRECODEPOS; --value)
|
||||
{
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,15 @@
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
using shared_table_t = std::unordered_map<std::string, std::string>;
|
||||
|
||||
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
extern std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
|
||||
extern std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
|
||||
|
||||
extern utils::concurrency::container<shared_table_t> shared_table;
|
||||
|
||||
extern std::string current_file;
|
||||
|
||||
void on_shutdown(const std::function<void(bool, bool)>& callback);
|
||||
|
525
src/client/component/server_list.cpp
Normal file
525
src/client/component/server_list.cpp
Normal file
@ -0,0 +1,525 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console/console.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "network.hpp"
|
||||
#include "party.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "server_list.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
#include "game/ui_scripting/execution.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace server_list
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const int server_limit = 100;
|
||||
|
||||
struct server_info
|
||||
{
|
||||
int clients;
|
||||
int max_clients;
|
||||
int bots;
|
||||
int ping;
|
||||
std::string host_name;
|
||||
std::string map_name;
|
||||
std::string game_type;
|
||||
std::string mod_name;
|
||||
game::GameModeType play_mode;
|
||||
char in_game;
|
||||
game::netadr_s address;
|
||||
bool is_private;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
game::netadr_s address{};
|
||||
volatile bool requesting = false;
|
||||
std::unordered_map<game::netadr_s, int> queued_servers{};
|
||||
} master_state;
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<server_info> servers;
|
||||
|
||||
size_t server_list_page = 0;
|
||||
volatile bool update_server_list = false;
|
||||
std::chrono::high_resolution_clock::time_point last_scroll{};
|
||||
|
||||
game::dvar_t* master_server_ip;
|
||||
game::dvar_t* master_server_port;
|
||||
|
||||
size_t get_page_count()
|
||||
{
|
||||
const auto count = servers.size() / server_limit;
|
||||
return count + (servers.size() % server_limit > 0);
|
||||
}
|
||||
|
||||
size_t get_page_base_index()
|
||||
{
|
||||
return server_list_page * server_limit;
|
||||
}
|
||||
|
||||
void refresh_server_list()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
servers.clear();
|
||||
master_state.queued_servers.clear();
|
||||
server_list_page = 0;
|
||||
}
|
||||
|
||||
party::reset_server_connection_state();
|
||||
|
||||
if (get_master_server(master_state.address))
|
||||
{
|
||||
master_state.requesting = true;
|
||||
network::send(master_state.address, "getservers", utils::string::va("IW7 %i full empty", PROTOCOL));
|
||||
}
|
||||
}
|
||||
|
||||
void join_server(int, int, const int index)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
const auto i = static_cast<size_t>(index) + get_page_base_index();
|
||||
if (i < servers.size())
|
||||
{
|
||||
static auto last_index = ~0ull;
|
||||
if (last_index != i)
|
||||
{
|
||||
last_index = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
party::connect(servers[i].address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_refresh()
|
||||
{
|
||||
update_server_list = true;
|
||||
}
|
||||
|
||||
|
||||
const char* ui_feeder_item_text(int arg0, int arg1, const int index, const int column, char* name)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
const auto i = get_page_base_index() + index;
|
||||
if (i >= servers.size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (column)
|
||||
{
|
||||
case 2:
|
||||
return servers[i].host_name.empty() ? "" : servers[i].host_name.data();
|
||||
case 3:
|
||||
{
|
||||
const auto& map_name = servers[i].map_name;
|
||||
if (map_name.empty())
|
||||
{
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
auto map_display_name = game::UI_GetMapDisplayName(map_name.data());
|
||||
if (!fastfiles::exists(map_name)) // TODO: add "false" 2nd parameter if usermaps come
|
||||
{
|
||||
map_display_name = utils::string::va("^1%s", map_display_name);
|
||||
}
|
||||
return map_display_name;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
const auto client_count = servers[i].clients - servers[i].bots;
|
||||
return utils::string::va("%d/%d [%d]", client_count, servers[i].max_clients,
|
||||
servers[i].clients);
|
||||
}
|
||||
case 5:
|
||||
return servers[i].game_type.empty() ? "" : servers[i].game_type.data();
|
||||
//case 10:
|
||||
//{
|
||||
// const auto ping = servers[i].ping ? servers[i].ping : 999;
|
||||
// if (ping < 75)
|
||||
// {
|
||||
// return utils::string::va("^2%d", ping);
|
||||
// }
|
||||
// else if (ping < 150)
|
||||
// {
|
||||
// return utils::string::va("^3%d", ping);
|
||||
// }
|
||||
// return utils::string::va("^1%d", ping);
|
||||
//}
|
||||
case 10:
|
||||
return servers[i].in_game ? "0" : "1";
|
||||
//case 6:
|
||||
// return servers[i].mod_name.empty() ? "" : servers[i].mod_name.data();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void sort_serverlist()
|
||||
{
|
||||
std::stable_sort(servers.begin(), servers.end(), [](const server_info& a, const server_info& b)
|
||||
{
|
||||
const auto a_players = a.clients - a.bots;
|
||||
const auto b_players = b.clients - b.bots;
|
||||
|
||||
if (a_players == b_players)
|
||||
{
|
||||
if (a.clients == b.clients)
|
||||
{
|
||||
return a.ping < b.ping;
|
||||
}
|
||||
|
||||
return a.clients > b.clients;
|
||||
}
|
||||
|
||||
return a_players > b_players;
|
||||
});
|
||||
}
|
||||
|
||||
void insert_server(server_info&& server)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
servers.emplace_back(std::move(server));
|
||||
sort_serverlist();
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
void do_frame_work()
|
||||
{
|
||||
auto& queue = master_state.queued_servers;
|
||||
if (queue.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
size_t queried_servers = 0;
|
||||
const size_t query_limit = 3;
|
||||
|
||||
for (auto i = queue.begin(); i != queue.end();)
|
||||
{
|
||||
if (i->second)
|
||||
{
|
||||
const auto now = game::Sys_Milliseconds();
|
||||
if (now - i->second > 10'000)
|
||||
{
|
||||
i = queue.erase(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (queried_servers++ < query_limit)
|
||||
{
|
||||
i->second = game::Sys_Milliseconds();
|
||||
network::send(i->first, "getInfo", utils::cryptography::random::get_challenge());
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_server_list_open()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
return false;
|
||||
#else
|
||||
// TODO
|
||||
return game::Menu_IsMenuOpenAndVisible(0, "menu_systemlink_join");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_scrolling_disabled()
|
||||
{
|
||||
return update_server_list || (std::chrono::high_resolution_clock::now() - last_scroll) < 500ms;
|
||||
}
|
||||
|
||||
bool scroll_down()
|
||||
{
|
||||
if (!is_server_list_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_scrolling_disabled() && server_list_page + 1 < get_page_count())
|
||||
{
|
||||
last_scroll = std::chrono::high_resolution_clock::now();
|
||||
++server_list_page;
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool scroll_up()
|
||||
{
|
||||
if (!is_server_list_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_scrolling_disabled() && server_list_page > 0)
|
||||
{
|
||||
last_scroll = std::chrono::high_resolution_clock::now();
|
||||
--server_list_page;
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
utils::hook::detour lui_open_menu_hook;
|
||||
|
||||
void lui_open_menu_stub(int controllerIndex, const char* menuName, int isPopup, int isModal, unsigned int isExclusive)
|
||||
{
|
||||
if (!strcmp(menuName, "SystemLinkMenu"))
|
||||
{
|
||||
refresh_server_list();
|
||||
}
|
||||
|
||||
lui_open_menu_hook.invoke<void>(controllerIndex, menuName, isPopup, isModal, isExclusive);
|
||||
}
|
||||
|
||||
void check_refresh()
|
||||
{
|
||||
if (update_server_list)
|
||||
{
|
||||
update_server_list = false;
|
||||
ui_scripting::notify("updateGameList", {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool sl_key_event(const int key, const int down)
|
||||
{
|
||||
if (down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_MWHEELUP)
|
||||
{
|
||||
return !scroll_up();
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_MWHEELDOWN)
|
||||
{
|
||||
return !scroll_down();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_master_server(game::netadr_s& address)
|
||||
{
|
||||
return game::NET_StringToAdr(utils::string::va("%s:%s",
|
||||
master_server_ip->current.string, master_server_port->current.string), &address);
|
||||
}
|
||||
|
||||
void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
|
||||
{
|
||||
// Don't show servers that aren't using the same protocol!
|
||||
const auto protocol = std::atoi(info.get("protocol").data());
|
||||
if (protocol != PROTOCOL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't show servers that aren't dedicated!
|
||||
const auto dedicated = std::atoi(info.get("dedicated").data());
|
||||
if (!dedicated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't show servers that aren't running!
|
||||
const auto sv_running = std::atoi(info.get("sv_running").data());
|
||||
if (!sv_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle servers of the same playmode!
|
||||
const auto playmode = game::GameModeType(std::atoi(info.get("playmode").data()));
|
||||
if (game::Com_GameMode_GetActiveGameMode() != playmode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.get("gamename") != "IW7")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int start_time{};
|
||||
const auto now = game::Sys_Milliseconds();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
const auto entry = master_state.queued_servers.find(address);
|
||||
|
||||
if (entry == master_state.queued_servers.end() || !entry->second)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
start_time = entry->second;
|
||||
master_state.queued_servers.erase(entry);
|
||||
}
|
||||
|
||||
server_info server{};
|
||||
server.address = address;
|
||||
server.host_name = info.get("hostname");
|
||||
server.map_name = info.get("mapname"); // UI_GetMapDisplayName?
|
||||
server.game_type = game::UI_GetGameTypeDisplayName(info.get("gametype").data());
|
||||
server.mod_name = info.get("fs_game");
|
||||
server.play_mode = playmode;
|
||||
server.clients = atoi(info.get("clients").data());
|
||||
server.max_clients = atoi(info.get("sv_maxclients").data());
|
||||
server.bots = atoi(info.get("bots").data());
|
||||
server.ping = std::min(now - start_time, 999);
|
||||
server.is_private = atoi(info.get("isPrivate").data()) == 1;
|
||||
|
||||
server.in_game = 1;
|
||||
|
||||
#ifdef DEBUG
|
||||
console::debug("inserting server \"%s\"\n", server.host_name.data());
|
||||
#endif
|
||||
|
||||
insert_server(std::move(server));
|
||||
}
|
||||
|
||||
int get_player_count()
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
auto count = 0;
|
||||
for (const auto& server : servers)
|
||||
{
|
||||
count += server.clients - server.bots;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int get_server_count()
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
return static_cast<int>(servers.size());
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
scheduler::once([]()
|
||||
{
|
||||
// add dvars to change destination master server ip/port
|
||||
master_server_ip = game::Dvar_RegisterString("masterServerIP", "server.alterware.dev", game::DVAR_FLAG_NONE,
|
||||
"IP of the destination master server to connect to");
|
||||
master_server_port = game::Dvar_RegisterString("masterServerPort", "20810", game::DVAR_FLAG_NONE,
|
||||
"Port of the destination master server to connect to");
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// hook LUI_OpenMenu to refresh server list for system link menu
|
||||
lui_open_menu_hook.create(game::LUI_OpenMenu, lui_open_menu_stub);
|
||||
|
||||
// replace UI_RunMenuScript call in LUI_CoD_LuaCall_RefreshServerList to our refresh_servers
|
||||
utils::hook::jump(0x69E019_b, utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.pushad64();
|
||||
a.call_aligned(refresh_server_list);
|
||||
a.popad64();
|
||||
|
||||
a.xor_(eax, eax);
|
||||
a.mov(rbx, qword_ptr(rsp, 0x38));
|
||||
a.add(rsp, 0x20);
|
||||
a.pop(rdi);
|
||||
a.ret();
|
||||
}), true);
|
||||
|
||||
utils::hook::jump(0x69E9F7_b, utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.mov(r8d, edi);
|
||||
a.mov(ecx, eax);
|
||||
a.mov(ebx, eax);
|
||||
|
||||
a.pushad64();
|
||||
a.call_aligned(join_server);
|
||||
a.popad64();
|
||||
|
||||
a.jmp(0x69EA03_b);
|
||||
}), true);
|
||||
|
||||
utils::hook::nop(0x69EA1D_b, 5);
|
||||
|
||||
utils::hook::call(0x69E45E_b, get_server_count);
|
||||
utils::hook::jump(0xCC5F00_b, ui_feeder_item_text);
|
||||
|
||||
scheduler::loop(do_frame_work, scheduler::pipeline::main);
|
||||
scheduler::loop(check_refresh, scheduler::pipeline::lui, 10ms);
|
||||
|
||||
network::on("getServersResponse", [](const game::netadr_s& target, const std::string_view& data)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
if (!master_state.requesting || master_state.address != target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
master_state.requesting = false;
|
||||
|
||||
std::optional<size_t> start{};
|
||||
for (std::size_t i = 0; i + 6 < data.size(); ++i)
|
||||
{
|
||||
if (data[i + 6] == '\\')
|
||||
{
|
||||
start.emplace(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!start.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = start.value(); i + 6 < data.size(); i += 7)
|
||||
{
|
||||
if (data[i + 6] != '\\')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
game::netadr_s address{};
|
||||
address.type = game::NA_IP;
|
||||
address.localNetID = game::NS_CLIENT1;
|
||||
std::memcpy(&address.ip[0], data.data() + i + 0, 4);
|
||||
std::memcpy(&address.port, data.data() + i + 4, 2);
|
||||
|
||||
master_state.queued_servers[address] = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(server_list::component)
|
15
src/client/component/server_list.hpp
Normal file
15
src/client/component/server_list.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include <utils/info_string.hpp>
|
||||
|
||||
namespace server_list
|
||||
{
|
||||
bool get_master_server(game::netadr_s& address);
|
||||
void handle_info_response(const game::netadr_s& address, const utils::info_string& info);
|
||||
|
||||
bool sl_key_event(int key, int down);
|
||||
|
||||
int get_player_count();
|
||||
int get_server_count();
|
||||
}
|
465
src/client/component/ui_scripting.cpp
Normal file
465
src/client/component/ui_scripting.cpp
Normal file
@ -0,0 +1,465 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console/console.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "game_module.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "party.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
#include "server_list.hpp"
|
||||
|
||||
#include "game/ui_scripting/execution.hpp"
|
||||
//#include "game/scripting/execution.hpp"
|
||||
|
||||
#include "ui_scripting.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/binary_resource.hpp>
|
||||
|
||||
#include "steam/steam.hpp"
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<game::hks::cclosure*, std::function<arguments(const function_arguments& args)>> converted_functions;
|
||||
|
||||
utils::hook::detour hks_start_hook;
|
||||
utils::hook::detour hks_shutdown_hook;
|
||||
utils::hook::detour hks_package_require_hook;
|
||||
|
||||
utils::hook::detour hks_load_hook;
|
||||
|
||||
/*
|
||||
const auto lui_common = utils::nt::load_resource(LUI_COMMON);
|
||||
const auto lui_updater = utils::nt::load_resource(LUI_UPDATER);
|
||||
const auto lua_json = utils::nt::load_resource(LUA_JSON);
|
||||
*/
|
||||
|
||||
struct globals_t
|
||||
{
|
||||
std::string in_require_script;
|
||||
std::unordered_map<std::string, std::string> loaded_scripts;
|
||||
bool load_raw_script{};
|
||||
std::string raw_script_name{};
|
||||
};
|
||||
|
||||
globals_t globals{};
|
||||
|
||||
bool is_loaded_script(const std::string& name)
|
||||
{
|
||||
return globals.loaded_scripts.contains(name);
|
||||
}
|
||||
|
||||
std::string get_root_script(const std::string& name)
|
||||
{
|
||||
const auto itr = globals.loaded_scripts.find(name);
|
||||
return itr == globals.loaded_scripts.end() ? std::string() : itr->second;
|
||||
}
|
||||
|
||||
void print_error(const std::string& error)
|
||||
{
|
||||
console::error("************** LUI script execution error **************\n");
|
||||
console::error("%s\n", error.data());
|
||||
console::error("********************************************************\n");
|
||||
}
|
||||
|
||||
void print_loading_script(const std::string& name)
|
||||
{
|
||||
console::info("Loading LUI script '%s'\n", name.data());
|
||||
}
|
||||
|
||||
std::string get_current_script()
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
game::hks::lua_Debug info{};
|
||||
game::hks::hksi_lua_getstack(state, 1, &info);
|
||||
game::hks::hksi_lua_getinfo(state, "nSl", &info);
|
||||
return info.short_src;
|
||||
}
|
||||
|
||||
int load_buffer(const std::string& name, const std::string& data)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto sharing_mode = state->m_global->m_bytecodeSharingMode;
|
||||
state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_ON;
|
||||
const auto _0 = gsl::finally([&]()
|
||||
{
|
||||
state->m_global->m_bytecodeSharingMode = sharing_mode;
|
||||
});
|
||||
|
||||
game::hks::HksCompilerSettings compiler_settings{};
|
||||
return game::hks::hksi_hksL_loadbuffer(state, &compiler_settings, data.data(), data.size(), name.data());
|
||||
}
|
||||
|
||||
void load_script(const std::string& name, const std::string& data)
|
||||
{
|
||||
globals.loaded_scripts[name] = name;
|
||||
|
||||
const auto lua = get_globals();
|
||||
const auto load_results = lua["loadstring"](data, name);
|
||||
|
||||
if (load_results[0].is<function>())
|
||||
{
|
||||
const auto results = lua["pcall"](load_results);
|
||||
if (!results[0].as<bool>())
|
||||
{
|
||||
print_error(results[1].as<std::string>());
|
||||
}
|
||||
}
|
||||
else if (load_results[1].is<std::string>())
|
||||
{
|
||||
print_error(load_results[1].as<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
void load_scripts(const std::string& script_dir)
|
||||
{
|
||||
if (!utils::io::directory_exists(script_dir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scripts = utils::io::list_files(script_dir);
|
||||
|
||||
for (const auto& script : scripts)
|
||||
{
|
||||
std::string data{};
|
||||
if (std::filesystem::is_directory(script) && utils::io::read_file(script + "/__init__.lua", &data))
|
||||
{
|
||||
print_loading_script(script);
|
||||
load_script(script + "/__init__.lua", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_functions()
|
||||
{
|
||||
const auto lua = get_globals();
|
||||
|
||||
lua["io"]["fileexists"] = utils::io::file_exists;
|
||||
lua["io"]["writefile"] = utils::io::write_file;
|
||||
lua["io"]["movefile"] = utils::io::move_file;
|
||||
lua["io"]["filesize"] = utils::io::file_size;
|
||||
lua["io"]["createdirectory"] = utils::io::create_directory;
|
||||
lua["io"]["directoryexists"] = utils::io::directory_exists;
|
||||
lua["io"]["directoryisempty"] = utils::io::directory_is_empty;
|
||||
lua["io"]["listfiles"] = utils::io::list_files;
|
||||
lua["io"]["removefile"] = utils::io::remove_file;
|
||||
lua["io"]["readfile"] = static_cast<std::string(*)(const std::string&)>(utils::io::read_file);
|
||||
|
||||
using game = table;
|
||||
auto game_type = game();
|
||||
lua["game"] = game_type;
|
||||
|
||||
/*
|
||||
game_type["addlocalizedstring"] = [](const game&, const std::string& string,
|
||||
const std::string& value)
|
||||
{
|
||||
localized_strings::override(string, value);
|
||||
};
|
||||
*/
|
||||
|
||||
game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value)
|
||||
{
|
||||
scripting::shared_table.access([key, value](scripting::shared_table_t& table)
|
||||
{
|
||||
table[key] = value;
|
||||
});
|
||||
};
|
||||
|
||||
game_type["sharedget"] = [](const game&, const std::string& key)
|
||||
{
|
||||
std::string result;
|
||||
scripting::shared_table.access([key, &result](scripting::shared_table_t& table)
|
||||
{
|
||||
result = table[key];
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
game_type["sharedclear"] = [](const game&)
|
||||
{
|
||||
scripting::shared_table.access([](scripting::shared_table_t& table)
|
||||
{
|
||||
table.clear();
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
game_type["assetlist"] = [](const game&, const std::string& type_string)
|
||||
{
|
||||
auto table_ = table();
|
||||
auto index = 1;
|
||||
auto type_index = -1;
|
||||
for (auto i = 0; i < ::game::XAssetType::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
if (type_string == ::game::g_assetNames[i])
|
||||
{
|
||||
type_index = i;
|
||||
}
|
||||
}
|
||||
if (type_index == -1)
|
||||
{
|
||||
throw std::runtime_error("Asset type does not exist");
|
||||
}
|
||||
const auto type = static_cast<::game::XAssetType>(type_index);
|
||||
fastfiles::enum_assets(type, [type, &table_, &index](const ::game::XAssetHeader header)
|
||||
{
|
||||
const auto asset = ::game::XAsset{type, header};
|
||||
const std::string asset_name = ::game::DB_GetXAssetName(&asset);
|
||||
table_[index++] = asset_name;
|
||||
}, true);
|
||||
return table_;
|
||||
};
|
||||
*/
|
||||
|
||||
game_type["getcurrentgamelanguage"] = [](const game&)
|
||||
{
|
||||
return steam::SteamApps()->GetCurrentGameLanguage();
|
||||
};
|
||||
|
||||
game_type["isdefaultmaterial"] = [](const game&, const std::string& material)
|
||||
{
|
||||
return static_cast<bool>(::game::DB_IsXAssetDefault(::game::ASSET_TYPE_MATERIAL,
|
||||
material.data()));
|
||||
};
|
||||
|
||||
auto server_list_table = table();
|
||||
lua["serverlist"] = server_list_table;
|
||||
|
||||
server_list_table["getplayercount"] = server_list::get_player_count;
|
||||
server_list_table["getservercount"] = server_list::get_server_count;
|
||||
}
|
||||
|
||||
void enable_globals()
|
||||
{
|
||||
const auto lua = get_globals();
|
||||
const std::string code =
|
||||
"local g = getmetatable(_G)\n"
|
||||
"if not g then\n"
|
||||
"g = {}\n"
|
||||
"setmetatable(_G, g)\n"
|
||||
"end\n"
|
||||
"g.__newindex = nil\n";
|
||||
|
||||
lua["loadstring"](code)[0]();
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
globals = {};
|
||||
const auto lua = get_globals();
|
||||
enable_globals(); // EnableGlobals() isn't a thing?
|
||||
|
||||
setup_functions();
|
||||
|
||||
lua["print"] = [](const variadic_args& va)
|
||||
{
|
||||
std::string buffer{};
|
||||
const auto to_string = get_globals()["tostring"];
|
||||
|
||||
for (auto i = 0; i < va.size(); i++)
|
||||
{
|
||||
const auto& arg = va[i];
|
||||
const auto str = to_string(arg)[0].as<std::string>();
|
||||
buffer.append(str);
|
||||
|
||||
if (i < va.size() - 1)
|
||||
{
|
||||
buffer.append("\t");
|
||||
}
|
||||
}
|
||||
|
||||
console::info("%s\n", buffer.data());
|
||||
};
|
||||
|
||||
lua["table"]["unpack"] = lua["unpack"];
|
||||
lua["luiglobals"] = lua;
|
||||
|
||||
/*
|
||||
load_script("lui_common", lui_common);
|
||||
load_script("lui_updater", lui_updater);
|
||||
load_script("lua_json", lua_json);
|
||||
*/
|
||||
|
||||
for (const auto& path : filesystem::get_search_paths_rev())
|
||||
{
|
||||
load_scripts(path + "/ui_scripts/");
|
||||
}
|
||||
}
|
||||
|
||||
void try_start()
|
||||
{
|
||||
try
|
||||
{
|
||||
start();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("Failed to load LUI scripts: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void* hks_start_stub(char a1, char a2)
|
||||
{
|
||||
const auto _0 = gsl::finally(&try_start);
|
||||
return hks_start_hook.invoke<void*>(a1, a2);
|
||||
}
|
||||
|
||||
void hks_shutdown_stub()
|
||||
{
|
||||
converted_functions.clear();
|
||||
globals = {};
|
||||
return hks_shutdown_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void* hks_package_require_stub(game::hks::lua_State* state)
|
||||
{
|
||||
const auto script = get_current_script();
|
||||
const auto root = get_root_script(script);
|
||||
globals.in_require_script = root;
|
||||
return hks_package_require_hook.invoke<void*>(state);
|
||||
}
|
||||
|
||||
game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
|
||||
{
|
||||
game::XAssetHeader header{.luaFile = nullptr};
|
||||
|
||||
if (!is_loaded_script(globals.in_require_script))
|
||||
{
|
||||
return game::DB_FindXAssetHeader(type, name, allow_create_default);
|
||||
}
|
||||
|
||||
const auto folder = globals.in_require_script.substr(0, globals.in_require_script.find_last_of("/\\"));
|
||||
const std::string name_ = name;
|
||||
const std::string target_script = folder + "/" + name_ + ".lua";
|
||||
|
||||
if (utils::io::file_exists(target_script))
|
||||
{
|
||||
globals.load_raw_script = true;
|
||||
globals.raw_script_name = target_script;
|
||||
header.luaFile = reinterpret_cast<game::LuaFile*>(1);
|
||||
}
|
||||
else if (name_.starts_with("ui/"))
|
||||
{
|
||||
return game::DB_FindXAssetHeader(type, name, allow_create_default);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
int hks_load_stub(game::hks::lua_State* state, void* compiler_options,
|
||||
void* reader, void* reader_data, const char* chunk_name)
|
||||
{
|
||||
if (globals.load_raw_script)
|
||||
{
|
||||
globals.load_raw_script = false;
|
||||
globals.loaded_scripts[globals.raw_script_name] = globals.in_require_script;
|
||||
return load_buffer(globals.raw_script_name, utils::io::read_file(globals.raw_script_name));
|
||||
}
|
||||
|
||||
return hks_load_hook.invoke<int>(state, compiler_options, reader,
|
||||
reader_data, chunk_name);
|
||||
}
|
||||
|
||||
std::string current_error;
|
||||
int main_handler(game::hks::lua_State* state)
|
||||
{
|
||||
bool error = false;
|
||||
|
||||
try
|
||||
{
|
||||
const auto value = state->m_apistack.base[-1];
|
||||
if (value.t != game::hks::TCFUNCTION)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto closure = value.v.cClosure;
|
||||
if (!converted_functions.contains(closure))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& function = converted_functions[closure];
|
||||
|
||||
const auto args = get_return_values();
|
||||
const auto results = function(args);
|
||||
|
||||
for (const auto& result : results)
|
||||
{
|
||||
push_value(result);
|
||||
}
|
||||
|
||||
return static_cast<int>(results.size());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
current_error = e.what();
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
game::hks::hksi_luaL_error(state, current_error.data());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
table get_globals()
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
return state->globals.v.table;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
game::hks::cclosure* convert_function(F f)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto closure = game::hks::cclosure_Create(state, main_handler, 0, 0, 0);
|
||||
converted_functions[closure] = wrap_function(f);
|
||||
return closure;
|
||||
}
|
||||
|
||||
bool lui_running()
|
||||
{
|
||||
return *game::hks::lua_state != nullptr;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::call(0x5FC2F7_b, db_find_x_asset_header_stub);
|
||||
utils::hook::call(0x5FC0AB_b, db_find_x_asset_header_stub);
|
||||
|
||||
hks_load_hook.create(0x11E0B00_b, hks_load_stub);
|
||||
|
||||
hks_package_require_hook.create(0x11C7F00_b, hks_package_require_stub);
|
||||
hks_start_hook.create(0x615090_b, hks_start_stub);
|
||||
hks_shutdown_hook.create(0x6124B0_b, hks_shutdown_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(ui_scripting::component)
|
51
src/client/component/ui_scripting.hpp
Normal file
51
src/client/component/ui_scripting.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
template <class... Args, std::size_t... I>
|
||||
auto wrap_function(const std::function<void(Args...)>& f, std::index_sequence<I...>)
|
||||
{
|
||||
return [f](const function_arguments& args)
|
||||
{
|
||||
f(args[I]...);
|
||||
return arguments{{}};
|
||||
};
|
||||
}
|
||||
|
||||
template <class... Args, std::size_t... I>
|
||||
auto wrap_function(const std::function<arguments(Args...)>& f, std::index_sequence<I...>)
|
||||
{
|
||||
return [f](const function_arguments& args)
|
||||
{
|
||||
return f(args[I]...);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename R, class... Args, std::size_t... I>
|
||||
auto wrap_function(const std::function<R(Args...)>& f, std::index_sequence<I...>)
|
||||
{
|
||||
return [f](const function_arguments& args)
|
||||
{
|
||||
return arguments{f(args[I]...)};
|
||||
};
|
||||
}
|
||||
|
||||
template <typename R, class... Args>
|
||||
auto wrap_function(const std::function<R(Args...)>& f)
|
||||
{
|
||||
return wrap_function(f, std::index_sequence_for<Args...>{});
|
||||
}
|
||||
|
||||
template <class F>
|
||||
auto wrap_function(F f)
|
||||
{
|
||||
return wrap_function(std::function(f));
|
||||
}
|
||||
|
||||
table get_globals();
|
||||
|
||||
template <typename F>
|
||||
game::hks::cclosure* convert_function(F f);
|
||||
|
||||
bool lui_running();
|
||||
}
|
@ -1047,5 +1047,429 @@ namespace game
|
||||
PhysicalMemoryPrim prim[2];
|
||||
};
|
||||
}
|
||||
|
||||
namespace hks
|
||||
{
|
||||
struct lua_State;
|
||||
struct HashTable;
|
||||
struct cclosure;
|
||||
|
||||
struct GenericChunkHeader
|
||||
{
|
||||
unsigned __int64 m_flags;
|
||||
};
|
||||
|
||||
struct ChunkHeader : GenericChunkHeader
|
||||
{
|
||||
ChunkHeader* m_next;
|
||||
};
|
||||
|
||||
struct UserData : ChunkHeader
|
||||
{
|
||||
unsigned __int64 m_envAndSizeOffsetHighBits;
|
||||
unsigned __int64 m_metaAndSizeOffsetLowBits;
|
||||
char m_data[8];
|
||||
};
|
||||
|
||||
struct InternString
|
||||
{
|
||||
unsigned __int64 m_flags;
|
||||
unsigned __int64 m_lengthbits;
|
||||
unsigned int m_hash;
|
||||
char m_data[30];
|
||||
};
|
||||
|
||||
union HksValue
|
||||
{
|
||||
cclosure* cClosure;
|
||||
void* closure;
|
||||
UserData* userData;
|
||||
HashTable* table;
|
||||
void* tstruct;
|
||||
InternString* str;
|
||||
void* thread;
|
||||
void* ptr;
|
||||
float number;
|
||||
long long i64;
|
||||
unsigned long long ui64;
|
||||
unsigned int native;
|
||||
bool boolean;
|
||||
};
|
||||
|
||||
enum HksObjectType
|
||||
{
|
||||
TANY = 0xFFFFFFFE,
|
||||
TNONE = 0xFFFFFFFF,
|
||||
TNIL = 0x0,
|
||||
TBOOLEAN = 0x1,
|
||||
TLIGHTUSERDATA = 0x2,
|
||||
TNUMBER = 0x3,
|
||||
TSTRING = 0x4,
|
||||
TTABLE = 0x5,
|
||||
TFUNCTION = 0x6, // idk
|
||||
TUSERDATA = 0x7,
|
||||
TTHREAD = 0x8,
|
||||
TIFUNCTION = 0x9, // Lua function
|
||||
TCFUNCTION = 0xA, // C function
|
||||
TUI64 = 0xB,
|
||||
TSTRUCT = 0xC,
|
||||
NUM_TYPE_OBJECTS = 0xE,
|
||||
};
|
||||
|
||||
struct HksObject
|
||||
{
|
||||
HksObjectType t;
|
||||
HksValue v;
|
||||
};
|
||||
|
||||
const struct hksInstruction
|
||||
{
|
||||
unsigned int code;
|
||||
};
|
||||
|
||||
struct ActivationRecord
|
||||
{
|
||||
HksObject* m_base;
|
||||
const hksInstruction* m_returnAddress;
|
||||
__int16 m_tailCallDepth;
|
||||
__int16 m_numVarargs;
|
||||
int m_numExpectedReturns;
|
||||
};
|
||||
|
||||
struct CallStack
|
||||
{
|
||||
ActivationRecord* m_records;
|
||||
ActivationRecord* m_lastrecord;
|
||||
ActivationRecord* m_current;
|
||||
const hksInstruction* m_current_lua_pc;
|
||||
const hksInstruction* m_hook_return_addr;
|
||||
int m_hook_level;
|
||||
};
|
||||
|
||||
struct ApiStack
|
||||
{
|
||||
HksObject* top;
|
||||
HksObject* base;
|
||||
HksObject* alloc_top;
|
||||
HksObject* bottom;
|
||||
};
|
||||
|
||||
struct UpValue : ChunkHeader
|
||||
{
|
||||
HksObject m_storage;
|
||||
HksObject* loc;
|
||||
UpValue* m_next;
|
||||
};
|
||||
|
||||
struct CallSite
|
||||
{
|
||||
_SETJMP_FLOAT128 m_jumpBuffer[16];
|
||||
CallSite* m_prev;
|
||||
};
|
||||
|
||||
enum Status
|
||||
{
|
||||
NEW = 0x1,
|
||||
RUNNING = 0x2,
|
||||
YIELDED = 0x3,
|
||||
DEAD_ERROR = 0x4,
|
||||
};
|
||||
|
||||
enum HksError
|
||||
{
|
||||
HKS_NO_ERROR = 0x0,
|
||||
HKS_ERRSYNTAX = 0xFFFFFFFC,
|
||||
HKS_ERRFILE = 0xFFFFFFFB,
|
||||
HKS_ERRRUN = 0xFFFFFF9C,
|
||||
HKS_ERRMEM = 0xFFFFFF38,
|
||||
HKS_ERRERR = 0xFFFFFED4,
|
||||
HKS_THROWING_ERROR = 0xFFFFFE0C,
|
||||
HKS_GC_YIELD = 0x1,
|
||||
};
|
||||
|
||||
struct lua_Debug
|
||||
{
|
||||
int event;
|
||||
const char* name;
|
||||
const char* namewhat;
|
||||
const char* what;
|
||||
const char* source;
|
||||
int currentline;
|
||||
int nups;
|
||||
int nparams;
|
||||
int ishksfunc;
|
||||
int linedefined;
|
||||
int lastlinedefined;
|
||||
char short_src[512];
|
||||
int callstack_level;
|
||||
int is_tail_call;
|
||||
};
|
||||
|
||||
using lua_function = int(__fastcall*)(lua_State*);
|
||||
|
||||
struct luaL_Reg
|
||||
{
|
||||
const char* name;
|
||||
lua_function function;
|
||||
};
|
||||
|
||||
struct Node
|
||||
{
|
||||
HksObject m_key;
|
||||
HksObject m_value;
|
||||
};
|
||||
|
||||
struct Metatable
|
||||
{
|
||||
};
|
||||
|
||||
struct HashTable : ChunkHeader
|
||||
{
|
||||
Metatable* m_meta;
|
||||
unsigned int m_version;
|
||||
unsigned int m_mask;
|
||||
Node* m_hashPart;
|
||||
HksObject* m_arrayPart;
|
||||
unsigned int m_arraySize;
|
||||
Node* m_freeNode;
|
||||
};
|
||||
|
||||
struct cclosure : ChunkHeader
|
||||
{
|
||||
lua_function m_function;
|
||||
HashTable* m_env;
|
||||
__int16 m_numUpvalues;
|
||||
__int16 m_flags;
|
||||
InternString* m_name;
|
||||
HksObject m_upvalues[1];
|
||||
};
|
||||
|
||||
enum HksCompilerSettings_BytecodeSharingFormat
|
||||
{
|
||||
BYTECODE_DEFAULT = 0x0,
|
||||
BYTECODE_INPLACE = 0x1,
|
||||
BYTECODE_REFERENCED = 0x2,
|
||||
};
|
||||
|
||||
enum HksCompilerSettings_IntLiteralOptions
|
||||
{
|
||||
INT_LITERALS_NONE = 0x0,
|
||||
INT_LITERALS_LUD = 0x1,
|
||||
INT_LITERALS_32BIT = 0x1,
|
||||
INT_LITERALS_UI64 = 0x2,
|
||||
INT_LITERALS_64BIT = 0x2,
|
||||
INT_LITERALS_ALL = 0x3,
|
||||
};
|
||||
|
||||
struct HksCompilerSettings
|
||||
{
|
||||
int m_emitStructCode;
|
||||
const char** m_stripNames;
|
||||
int m_emitGlobalMemoization;
|
||||
int _m_isHksGlobalMemoTestingMode;
|
||||
HksCompilerSettings_BytecodeSharingFormat m_bytecodeSharingFormat;
|
||||
HksCompilerSettings_IntLiteralOptions m_enableIntLiterals;
|
||||
int(__fastcall* m_debugMap)(const char*, int);
|
||||
};
|
||||
|
||||
enum HksBytecodeSharingMode
|
||||
{
|
||||
HKS_BYTECODE_SHARING_OFF = 0x0,
|
||||
HKS_BYTECODE_SHARING_ON = 0x1,
|
||||
HKS_BYTECODE_SHARING_SECURE = 0x2,
|
||||
};
|
||||
|
||||
struct HksGcWeights
|
||||
{
|
||||
int m_removeString;
|
||||
int m_finalizeUserdataNoMM;
|
||||
int m_finalizeUserdataGcMM;
|
||||
int m_cleanCoroutine;
|
||||
int m_removeWeak;
|
||||
int m_markObject;
|
||||
int m_traverseString;
|
||||
int m_traverseUserdata;
|
||||
int m_traverseCoroutine;
|
||||
int m_traverseWeakTable;
|
||||
int m_freeChunk;
|
||||
int m_sweepTraverse;
|
||||
};
|
||||
|
||||
struct GarbageCollector_Stack
|
||||
{
|
||||
void* m_storage;
|
||||
unsigned int m_numEntries;
|
||||
unsigned int m_numAllocated;
|
||||
};
|
||||
|
||||
struct ProtoList
|
||||
{
|
||||
void** m_protoList;
|
||||
unsigned __int16 m_protoSize;
|
||||
unsigned __int16 m_protoAllocSize;
|
||||
};
|
||||
|
||||
struct GarbageCollector
|
||||
{
|
||||
int m_target;
|
||||
int m_stepsLeft;
|
||||
int m_stepLimit;
|
||||
HksGcWeights m_costs;
|
||||
int m_unit;
|
||||
_SETJMP_FLOAT128(*m_jumpPoint)[16];
|
||||
lua_State* m_mainState;
|
||||
lua_State* m_finalizerState;
|
||||
void* m_memory;
|
||||
int m_phase;
|
||||
GarbageCollector_Stack m_resumeStack;
|
||||
GarbageCollector_Stack m_greyStack;
|
||||
GarbageCollector_Stack m_remarkStack;
|
||||
GarbageCollector_Stack m_weakStack;
|
||||
int m_finalizing;
|
||||
HksObject m_safeTableValue;
|
||||
lua_State* m_startOfStateStackList;
|
||||
lua_State* m_endOfStateStackList;
|
||||
lua_State* m_currentState;
|
||||
HksObject m_safeValue;
|
||||
void* m_compiler;
|
||||
void* m_bytecodeReader;
|
||||
void* m_bytecodeWriter;
|
||||
int m_pauseMultiplier;
|
||||
int m_stepMultiplier;
|
||||
bool m_stopped;
|
||||
int(__fastcall* m_gcPolicy)(lua_State*);
|
||||
unsigned __int64 m_pauseTriggerMemoryUsage;
|
||||
int m_stepTriggerCountdown;
|
||||
unsigned int m_stringTableIndex;
|
||||
unsigned int m_stringTableSize;
|
||||
UserData* m_lastBlackUD;
|
||||
UserData* m_activeUD;
|
||||
};
|
||||
|
||||
enum MemoryManager_ChunkColor
|
||||
{
|
||||
RED = 0x0,
|
||||
BLACK = 0x1,
|
||||
};
|
||||
|
||||
struct ChunkList
|
||||
{
|
||||
ChunkHeader m_prevToStart;
|
||||
};
|
||||
|
||||
enum Hks_DeleteCheckingMode
|
||||
{
|
||||
HKS_DELETE_CHECKING_OFF = 0x0,
|
||||
HKS_DELETE_CHECKING_ACCURATE = 0x1,
|
||||
HKS_DELETE_CHECKING_SAFE = 0x2,
|
||||
};
|
||||
|
||||
struct MemoryManager
|
||||
{
|
||||
void* (__fastcall* m_allocator)(void*, void*, unsigned __int64, unsigned __int64);
|
||||
void* m_allocatorUd;
|
||||
MemoryManager_ChunkColor m_chunkColor;
|
||||
unsigned __int64 m_used;
|
||||
unsigned __int64 m_highwatermark;
|
||||
ChunkList m_allocationList;
|
||||
ChunkList m_sweepList;
|
||||
ChunkHeader* m_lastKeptChunk;
|
||||
lua_State* m_state;
|
||||
ChunkList m_deletedList;
|
||||
int m_deleteMode;
|
||||
Hks_DeleteCheckingMode m_deleteCheckingMode;
|
||||
};
|
||||
|
||||
struct StaticStringCache
|
||||
{
|
||||
HksObject m_objects[41];
|
||||
};
|
||||
|
||||
enum HksBytecodeEndianness
|
||||
{
|
||||
HKS_BYTECODE_DEFAULT_ENDIAN = 0x0,
|
||||
HKS_BYTECODE_BIG_ENDIAN = 0x1,
|
||||
HKS_BYTECODE_LITTLE_ENDIAN = 0x2,
|
||||
};
|
||||
|
||||
struct RuntimeProfileData_Stats
|
||||
{
|
||||
unsigned __int64 hksTime;
|
||||
unsigned __int64 callbackTime;
|
||||
unsigned __int64 gcTime;
|
||||
unsigned __int64 cFinalizerTime;
|
||||
unsigned __int64 compilerTime;
|
||||
unsigned int hkssTimeSamples;
|
||||
unsigned int callbackTimeSamples;
|
||||
unsigned int gcTimeSamples;
|
||||
unsigned int compilerTimeSamples;
|
||||
unsigned int num_newuserdata;
|
||||
unsigned int num_tablerehash;
|
||||
unsigned int num_pushstring;
|
||||
unsigned int num_pushcfunction;
|
||||
unsigned int num_newtables;
|
||||
};
|
||||
|
||||
struct RuntimeProfileData
|
||||
{
|
||||
__int64 stackDepth;
|
||||
__int64 callbackDepth;
|
||||
unsigned __int64 lastTimer;
|
||||
RuntimeProfileData_Stats frameStats;
|
||||
unsigned __int64 gcStartTime;
|
||||
unsigned __int64 finalizerStartTime;
|
||||
unsigned __int64 compilerStartTime;
|
||||
unsigned __int64 compilerStartGCTime;
|
||||
unsigned __int64 compilerStartGCFinalizerTime;
|
||||
unsigned __int64 compilerCallbackStartTime;
|
||||
__int64 compilerDepth;
|
||||
void* outFile;
|
||||
lua_State* rootState;
|
||||
};
|
||||
|
||||
struct HksGlobal
|
||||
{
|
||||
MemoryManager m_memory;
|
||||
GarbageCollector m_collector;
|
||||
StringTable m_stringTable;
|
||||
HksBytecodeSharingMode m_bytecodeSharingMode;
|
||||
unsigned int m_tableVersionInitializer;
|
||||
HksObject m_registry;
|
||||
ProtoList m_protoList;
|
||||
HashTable* m_structProtoByName;
|
||||
ChunkList m_userDataList;
|
||||
lua_State* m_root;
|
||||
StaticStringCache m_staticStringCache;
|
||||
void* m_debugger;
|
||||
void* m_profiler;
|
||||
RuntimeProfileData m_runProfilerData;
|
||||
HksCompilerSettings m_compilerSettings;
|
||||
int(__fastcall* m_panicFunction)(lua_State*);
|
||||
void* m_luaplusObjectList;
|
||||
int m_heapAssertionFrequency;
|
||||
int m_heapAssertionCount;
|
||||
void (*m_logFunction)(lua_State*, const char*, ...);
|
||||
HksBytecodeEndianness m_bytecodeDumpEndianness;
|
||||
};
|
||||
|
||||
struct lua_State : ChunkHeader
|
||||
{
|
||||
HksGlobal* m_global;
|
||||
CallStack m_callStack;
|
||||
ApiStack m_apistack;
|
||||
UpValue* pending;
|
||||
HksObject globals;
|
||||
HksObject m_cEnv;
|
||||
CallSite* m_callsites;
|
||||
int m_numberOfCCalls;
|
||||
void* m_context;
|
||||
InternString* m_name;
|
||||
lua_State* m_nextState;
|
||||
lua_State* m_nextStateStack;
|
||||
Status m_status;
|
||||
HksError m_error;
|
||||
};
|
||||
}
|
||||
|
||||
using namespace pmem;
|
||||
}
|
||||
|
@ -154,6 +154,9 @@ namespace game
|
||||
|
||||
WEAK symbol<PartyData* ()> Lobby_GetPartyData{ 0x9C3E20 };
|
||||
|
||||
WEAK symbol<void()> LUI_EnterCriticalSection{ 0x600080 };
|
||||
WEAK symbol<void()> LUI_LeaveCriticalSection{ 0x602280 };
|
||||
|
||||
WEAK symbol<Material* (const char* material)> Material_RegisterHandle{ 0xE11CE0 };
|
||||
|
||||
WEAK symbol<void(netadr_s*, sockaddr*)> NetadrToSockadr{ 0xCE6B90 };
|
||||
@ -216,7 +219,8 @@ namespace game
|
||||
WEAK symbol<void(int)> Scr_AddInt{ 0xC0A580 };
|
||||
WEAK symbol<bool(VariableValue* value)> Scr_CastString{ 0xC06AE0 };
|
||||
WEAK symbol<void()> Scr_ClearOutParams{ 0xC0ABC0 };
|
||||
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{ 0xC09050 };
|
||||
WEAK symbol<unsigned int(int classnum, unsigned int entnum)> Scr_GetEntityId{ 0xC08FA0 };
|
||||
WEAK symbol<scr_entref_t(unsigned int entnum)> Scr_GetEntityIdRef{ 0xC09050 };
|
||||
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{ 0x40B6E0 };
|
||||
WEAK symbol<int()> Scr_GetInt{ 0xC0B950 };
|
||||
WEAK symbol<void()> Scr_ErrorInternal{ 0xC0AC30 };
|
||||
@ -251,6 +255,10 @@ namespace game
|
||||
WEAK symbol<void(int)> SND_StopSounds{ 0xCA06E0 };
|
||||
WEAK symbol<void(const char*)> SND_SetMusicState{ 0xC9E110 };
|
||||
|
||||
WEAK symbol<const char* (const char*)> UI_GetMapDisplayName{ 0xCC6270 };
|
||||
WEAK symbol<const char* (const char*)> UI_GetGameTypeDisplayName{ 0xCC61C0 };
|
||||
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{ 0xCC9710 };
|
||||
|
||||
WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{ 0x12C0758 };
|
||||
WEAK symbol<int(jmp_buf* Buf)> _setjmp{ 0x1423110 };
|
||||
|
||||
@ -318,4 +326,29 @@ namespace game
|
||||
WEAK symbol<unsigned __int64> g_streamPos{ 0x5687E30 };
|
||||
|
||||
WEAK symbol<bool> g_quitRequested{ 0x779CD44 };
|
||||
|
||||
WEAK symbol<unsigned int> gameEntityId{ 0x665A124 };
|
||||
WEAK symbol<int> level_time{ 0x3C986D8 };
|
||||
|
||||
namespace hks
|
||||
{
|
||||
WEAK symbol<lua_State*> lua_state{ 0x4FC35F0 };
|
||||
WEAK symbol<void(lua_State* s, const char* str, unsigned int l)> hksi_lua_pushlstring{ 0x309E0 };
|
||||
WEAK symbol<HksObject* (HksObject* result, lua_State* s, const HksObject* table, const HksObject* key)> hks_obj_getfield{ 0x11E14D0 };
|
||||
WEAK symbol<HksObject* (HksObject* result, lua_State* s, const HksObject* table, const HksObject* key)> hks_obj_gettable{ 0x11E19B0 };
|
||||
WEAK symbol<void(lua_State* s, const HksObject* tbl, const HksObject* key, const HksObject* val)> hks_obj_settable{ 0x11E26F0 };
|
||||
WEAK symbol<void(lua_State* s, int nargs, int nresults, const unsigned int* pc)> vm_call_internal{ 0x120CCE0 };
|
||||
WEAK symbol<HashTable* (lua_State* s, unsigned int arraySize, unsigned int hashSize)> Hashtable_Create{ 0x11D0590 };
|
||||
WEAK symbol<cclosure* (lua_State* s, lua_function function, int num_upvalues,
|
||||
int internal_, int profilerTreatClosureAsFunc)> cclosure_Create{ 0x11D07B0 };
|
||||
WEAK symbol<int(lua_State* s, int t)> hksi_luaL_ref{ 0x11EAE10 };
|
||||
WEAK symbol<int(lua_State* s, const HksCompilerSettings* options, const char* buff,
|
||||
unsigned __int64 sz, const char* name)> hksi_hksL_loadbuffer{ 0x11E2F50 };
|
||||
WEAK symbol<int(lua_State* s, const char* what, lua_Debug* ar)> hksi_lua_getinfo{ 0x11E48C0 };
|
||||
WEAK symbol<int(lua_State* s, int level, lua_Debug* ar)> hksi_lua_getstack{ 0x11E4AA0 };
|
||||
WEAK symbol<void(lua_State* s, const char* fmt, ...)> hksi_luaL_error{ 0x11EA860 };
|
||||
WEAK symbol<void(lua_State* s, int what, int data)> hksi_lua_gc{ 0x11EAF00 };
|
||||
WEAK symbol<void(lua_State* s, int t, int ref)> hksi_luaL_unref{ 0x11E4460 };
|
||||
WEAK symbol<const char*> s_compilerTypeName{ 0x1BDEEF0 };
|
||||
}
|
||||
}
|
||||
|
174
src/client/game/ui_scripting/execution.cpp
Normal file
174
src/client/game/ui_scripting/execution.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
#include <std_include.hpp>
|
||||
#include "execution.hpp"
|
||||
#include "component/ui_scripting.hpp"
|
||||
#include "component/console/console.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
script_value get_field(void* ptr, game::hks::HksObjectType type, const script_value& key)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto top = state->m_apistack.top;
|
||||
|
||||
push_value(key);
|
||||
|
||||
game::hks::HksObject value{};
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = type;
|
||||
obj.v.ptr = ptr;
|
||||
|
||||
game::hks::hks_obj_gettable(&value, state, &obj, &state->m_apistack.top[-1]);
|
||||
state->m_apistack.top = top;
|
||||
return value;
|
||||
}
|
||||
|
||||
void set_field(void* ptr, game::hks::HksObjectType type, const script_value& key, const script_value& value)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = type;
|
||||
obj.v.ptr = ptr;
|
||||
|
||||
game::hks::hks_obj_settable(state, &obj, &key.get_raw(), &value.get_raw());
|
||||
}
|
||||
}
|
||||
|
||||
void push_value(const script_value& value)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
*state->m_apistack.top = value.get_raw();
|
||||
state->m_apistack.top++;
|
||||
}
|
||||
|
||||
void push_value(const game::hks::HksObject& value)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
*state->m_apistack.top = value;
|
||||
state->m_apistack.top++;
|
||||
}
|
||||
|
||||
script_value get_return_value(int offset)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
return state->m_apistack.top[-1 - offset];
|
||||
}
|
||||
|
||||
arguments get_return_values()
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
|
||||
arguments values;
|
||||
|
||||
for (auto i = count - 1; i >= 0; i--)
|
||||
{
|
||||
const auto v = get_return_value(i);
|
||||
values.push_back(v);
|
||||
}
|
||||
|
||||
if (values.empty())
|
||||
{
|
||||
values.push_back({});
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
arguments get_return_values(game::hks::HksObject* base)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto count = static_cast<int>(state->m_apistack.top - base);
|
||||
arguments values;
|
||||
|
||||
for (auto i = count - 1; i >= 0; i--)
|
||||
{
|
||||
const auto v = get_return_value(i);
|
||||
values.push_back(v);
|
||||
}
|
||||
|
||||
if (values.empty())
|
||||
{
|
||||
values.push_back({});
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
bool notify(const std::string& name, const event_arguments& arguments)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
if (state == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto _1 = gsl::finally(game::LUI_LeaveCriticalSection);
|
||||
game::LUI_EnterCriticalSection();
|
||||
|
||||
try
|
||||
{
|
||||
const auto engine = get_globals().get("Engine").as<table>();
|
||||
const auto root = engine.get("GetLuiRoot")()[0].as<userdata>();
|
||||
const auto process_event = root.get("processEvent");
|
||||
|
||||
table event{};
|
||||
event.set("name", name);
|
||||
event.set("dispatchChildren", true);
|
||||
|
||||
for (const auto& arg : arguments)
|
||||
{
|
||||
event.set(arg.first, arg.second);
|
||||
}
|
||||
|
||||
process_event(root, event);
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("Error processing event '%s' %s\n", name.data(), e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
arguments call_script_function(const function& function, const arguments& arguments)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto top = state->m_apistack.top;
|
||||
|
||||
push_value(function);
|
||||
for (auto i = arguments.begin(); i != arguments.end(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
game::hks::vm_call_internal(state, static_cast<int>(arguments.size()), -1, 0);
|
||||
const auto args = get_return_values(top);
|
||||
state->m_apistack.top = top;
|
||||
return args;
|
||||
}
|
||||
|
||||
script_value get_field(const userdata& self, const script_value& key)
|
||||
{
|
||||
return get_field(self.ptr, game::hks::TUSERDATA, key);
|
||||
}
|
||||
|
||||
script_value get_field(const table& self, const script_value& key)
|
||||
{
|
||||
return get_field(self.ptr, game::hks::TTABLE, key);
|
||||
}
|
||||
|
||||
void set_field(const userdata& self, const script_value& key, const script_value& value)
|
||||
{
|
||||
set_field(self.ptr, game::hks::TUSERDATA, key, value);
|
||||
}
|
||||
|
||||
void set_field(const table& self, const script_value& key, const script_value& value)
|
||||
{
|
||||
set_field(self.ptr, game::hks::TTABLE, key, value);
|
||||
}
|
||||
}
|
23
src/client/game/ui_scripting/execution.hpp
Normal file
23
src/client/game/ui_scripting/execution.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "types.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
void push_value(const script_value& value);
|
||||
void push_value(const game::hks::HksObject& value);
|
||||
|
||||
script_value get_return_value(int offset);
|
||||
arguments get_return_values();
|
||||
arguments get_return_values(game::hks::HksObject* base);
|
||||
|
||||
bool notify(const std::string& name, const event_arguments& arguments);
|
||||
|
||||
arguments call_script_function(const function& function, const arguments& arguments);
|
||||
|
||||
script_value get_field(const userdata& self, const script_value& key);
|
||||
script_value get_field(const table& self, const script_value& key);
|
||||
void set_field(const userdata& self, const script_value& key, const script_value& value);
|
||||
void set_field(const table& self, const script_value& key, const script_value& value);
|
||||
}
|
449
src/client/game/ui_scripting/script_value.cpp
Normal file
449
src/client/game/ui_scripting/script_value.cpp
Normal file
@ -0,0 +1,449 @@
|
||||
#include <std_include.hpp>
|
||||
#include "execution.hpp"
|
||||
#include "types.hpp"
|
||||
#include "script_value.hpp"
|
||||
#include "../../component/ui_scripting.hpp"
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
hks_object::hks_object(const game::hks::HksObject& value)
|
||||
{
|
||||
this->assign(value);
|
||||
}
|
||||
|
||||
hks_object::hks_object(const hks_object& other) noexcept
|
||||
{
|
||||
this->operator=(other);
|
||||
}
|
||||
|
||||
hks_object::hks_object(hks_object&& other) noexcept
|
||||
{
|
||||
this->operator=(std::move(other));
|
||||
}
|
||||
|
||||
hks_object& hks_object::operator=(const hks_object& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->release();
|
||||
this->assign(other.value_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
hks_object& hks_object::operator=(hks_object&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->release();
|
||||
this->value_ = other.value_;
|
||||
other.value_.t = game::hks::TNONE;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
hks_object::~hks_object()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
const game::hks::HksObject& hks_object::get() const
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
void hks_object::assign(const game::hks::HksObject& value)
|
||||
{
|
||||
this->value_ = value;
|
||||
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto top = state->m_apistack.top;
|
||||
|
||||
push_value(this->value_);
|
||||
this->ref_ = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
|
||||
state->m_apistack.top = top;
|
||||
}
|
||||
|
||||
void hks_object::release()
|
||||
{
|
||||
if (this->ref_)
|
||||
{
|
||||
game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref_);
|
||||
this->value_.t = game::hks::TNONE;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Constructors
|
||||
**************************************************************/
|
||||
|
||||
script_value::script_value(const game::hks::HksObject& value)
|
||||
: value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const int value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TNUMBER;
|
||||
obj.v.number = static_cast<float>(value);
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const unsigned int value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TNUMBER;
|
||||
obj.v.number = static_cast<float>(value);
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const long long value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TUI64;
|
||||
obj.v.i64 = value;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const unsigned long long value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TUI64;
|
||||
obj.v.ui64 = value;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const bool value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TBOOLEAN;
|
||||
obj.v.boolean = value;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const float value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TNUMBER;
|
||||
obj.v.number = static_cast<float>(value);
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const double value)
|
||||
: script_value(static_cast<float>(value))
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const char* value, const size_t len)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
|
||||
const auto state = *game::hks::lua_state;
|
||||
if (state == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto top = state->m_apistack.top;
|
||||
game::hks::hksi_lua_pushlstring(state, value, static_cast<unsigned int>(len));
|
||||
obj = state->m_apistack.top[-1];
|
||||
state->m_apistack.top = top;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const char* value)
|
||||
: script_value(value, strlen(value))
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const std::string& value)
|
||||
: script_value(value.data(), value.size())
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const lightuserdata& value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TLIGHTUSERDATA;
|
||||
obj.v.ptr = value.ptr;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const userdata& value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TUSERDATA;
|
||||
obj.v.ptr = value.ptr;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const table& value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TTABLE;
|
||||
obj.v.ptr = value.ptr;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
script_value::script_value(const function& value)
|
||||
{
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = value.type;
|
||||
obj.v.ptr = value.ptr;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Integer
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<int>() const
|
||||
{
|
||||
const auto number = this->get_raw().v.number;
|
||||
return this->get_raw().t == game::hks::TNUMBER && static_cast<int>(number) == number;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<unsigned int>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
int script_value::get() const
|
||||
{
|
||||
return static_cast<int>(this->get_raw().v.number);
|
||||
}
|
||||
|
||||
template <>
|
||||
unsigned int script_value::get() const
|
||||
{
|
||||
return static_cast<unsigned int>(this->get_raw().v.number);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Integer 64
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<long long>() const
|
||||
{
|
||||
return this->get_raw().t == game::hks::TUI64;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<unsigned long long>() const
|
||||
{
|
||||
return this->is<long long>();
|
||||
}
|
||||
|
||||
template <>
|
||||
long long script_value::get() const
|
||||
{
|
||||
return static_cast<long long>(this->get_raw().v.ui64);
|
||||
}
|
||||
|
||||
template <>
|
||||
unsigned long long script_value::get() const
|
||||
{
|
||||
return static_cast<unsigned long long>(this->get_raw().v.ui64);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Boolean
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<bool>() const
|
||||
{
|
||||
return this->get_raw().t == game::hks::TBOOLEAN;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::get() const
|
||||
{
|
||||
return this->get_raw().v.boolean;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Float
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<float>() const
|
||||
{
|
||||
return this->get_raw().t == game::hks::TNUMBER;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<double>() const
|
||||
{
|
||||
return this->is<float>();
|
||||
}
|
||||
|
||||
template <>
|
||||
float script_value::get() const
|
||||
{
|
||||
return this->get_raw().v.number;
|
||||
}
|
||||
|
||||
template <>
|
||||
double script_value::get() const
|
||||
{
|
||||
return static_cast<double>(this->get_raw().v.number);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* String
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<const char*>() const
|
||||
{
|
||||
return this->get_raw().t == game::hks::TSTRING;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::string>() const
|
||||
{
|
||||
return this->is<const char*>();
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* script_value::get() const
|
||||
{
|
||||
return this->get_raw().v.str->m_data;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string script_value::get() const
|
||||
{
|
||||
return this->get<const char*>();
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Lightuserdata
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<lightuserdata>() const
|
||||
{
|
||||
return this->get_raw().t == game::hks::TLIGHTUSERDATA;
|
||||
}
|
||||
|
||||
template <>
|
||||
lightuserdata script_value::get() const
|
||||
{
|
||||
return this->get_raw().v.ptr;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Userdata
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<userdata>() const
|
||||
{
|
||||
return this->get_raw().t == game::hks::TUSERDATA;
|
||||
}
|
||||
|
||||
template <>
|
||||
userdata script_value::get() const
|
||||
{
|
||||
return this->get_raw().v.ptr;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Table
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<table>() const
|
||||
{
|
||||
return this->get_raw().t == game::hks::TTABLE;
|
||||
}
|
||||
|
||||
template <>
|
||||
table script_value::get() const
|
||||
{
|
||||
return this->get_raw().v.table;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Function
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<function>() const
|
||||
{
|
||||
return this->get_raw().t == game::hks::TIFUNCTION
|
||||
|| this->get_raw().t == game::hks::TCFUNCTION;
|
||||
}
|
||||
|
||||
template <>
|
||||
function script_value::get() const
|
||||
{
|
||||
return {this->get_raw().v.cClosure, this->get_raw().t};
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
const game::hks::HksObject& script_value::get_raw() const
|
||||
{
|
||||
return this->value_.get();
|
||||
}
|
||||
|
||||
bool script_value::operator==(const script_value& other) const
|
||||
{
|
||||
if (this->get_raw().t != other.get_raw().t)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->get_raw().t == game::hks::TSTRING)
|
||||
{
|
||||
return this->get<std::string>() == other.get<std::string>();
|
||||
}
|
||||
|
||||
return this->get_raw().v.native == other.get_raw().v.native;
|
||||
}
|
||||
|
||||
arguments script_value::operator()() const
|
||||
{
|
||||
return this->as<function>()();
|
||||
}
|
||||
|
||||
arguments script_value::operator()(const arguments& arguments) const
|
||||
{
|
||||
return this->as<function>()(arguments);
|
||||
}
|
||||
|
||||
function_argument::function_argument(const arguments& args, const script_value& value, const int index)
|
||||
: values_(args)
|
||||
, value_(value)
|
||||
, index_(index)
|
||||
{
|
||||
}
|
||||
|
||||
function_arguments::function_arguments(const arguments& values)
|
||||
: values_(values)
|
||||
{
|
||||
}
|
||||
}
|
259
src/client/game/ui_scripting/script_value.hpp
Normal file
259
src/client/game/ui_scripting/script_value.hpp
Normal file
@ -0,0 +1,259 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
class lightuserdata;
|
||||
class userdata_value;
|
||||
class userdata;
|
||||
class table_value;
|
||||
class table;
|
||||
class function;
|
||||
class script_value;
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
std::string get_typename()
|
||||
{
|
||||
auto& info = typeid(T);
|
||||
|
||||
if (info == typeid(std::string) ||
|
||||
info == typeid(const char*))
|
||||
{
|
||||
return "string";
|
||||
}
|
||||
|
||||
if (info == typeid(lightuserdata))
|
||||
{
|
||||
return "lightuserdata";
|
||||
}
|
||||
|
||||
if (info == typeid(userdata))
|
||||
{
|
||||
return "userdata";
|
||||
}
|
||||
|
||||
if (info == typeid(table))
|
||||
{
|
||||
return "table";
|
||||
}
|
||||
|
||||
if (info == typeid(function))
|
||||
{
|
||||
return "function";
|
||||
}
|
||||
|
||||
if (info == typeid(int) ||
|
||||
info == typeid(float) ||
|
||||
info == typeid(unsigned int))
|
||||
{
|
||||
return "number";
|
||||
}
|
||||
|
||||
if (info == typeid(bool))
|
||||
{
|
||||
return "boolean";
|
||||
}
|
||||
|
||||
return info.name();
|
||||
}
|
||||
}
|
||||
|
||||
class hks_object
|
||||
{
|
||||
public:
|
||||
hks_object() = default;
|
||||
hks_object(const game::hks::HksObject& value);
|
||||
hks_object(const hks_object& other) noexcept;
|
||||
hks_object(hks_object&& other) noexcept;
|
||||
|
||||
hks_object& operator=(const hks_object& other) noexcept;
|
||||
hks_object& operator=(hks_object&& other) noexcept;
|
||||
|
||||
~hks_object();
|
||||
|
||||
const game::hks::HksObject& get() const;
|
||||
|
||||
private:
|
||||
void assign(const game::hks::HksObject& value);
|
||||
void release();
|
||||
|
||||
game::hks::HksObject value_{game::hks::TNONE, {}};
|
||||
int ref_{};
|
||||
};
|
||||
|
||||
using arguments = std::vector<script_value>;
|
||||
using event_arguments = std::unordered_map<std::string, script_value>;
|
||||
|
||||
class script_value
|
||||
{
|
||||
public:
|
||||
script_value() = default;
|
||||
script_value(const game::hks::HksObject& value);
|
||||
|
||||
script_value(int value);
|
||||
script_value(unsigned int value);
|
||||
script_value(long long value);
|
||||
script_value(unsigned long long value);
|
||||
script_value(bool value);
|
||||
|
||||
script_value(float value);
|
||||
script_value(double value);
|
||||
|
||||
script_value(const char* value);
|
||||
script_value(const char* value, const size_t len);
|
||||
script_value(const std::string& value);
|
||||
|
||||
script_value(const lightuserdata& value);
|
||||
script_value(const userdata& value);
|
||||
script_value(const table& value);
|
||||
script_value(const function& value);
|
||||
|
||||
template <template<class, class> class C, class T, typename TableType = table>
|
||||
script_value(const C<T, std::allocator<T>>& container)
|
||||
{
|
||||
TableType table_{};
|
||||
int index = 1;
|
||||
|
||||
for (const auto& value : container)
|
||||
{
|
||||
table_.set(index++, value);
|
||||
}
|
||||
|
||||
game::hks::HksObject obj{};
|
||||
obj.t = game::hks::TTABLE;
|
||||
obj.v.ptr = table_.ptr;
|
||||
|
||||
this->value_ = obj;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
script_value(F f)
|
||||
: script_value(function(f))
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const script_value& other) const;
|
||||
|
||||
arguments operator()() const;
|
||||
arguments operator()(const arguments& arguments) const;
|
||||
|
||||
template<class ...T>
|
||||
arguments operator()(T... arguments) const
|
||||
{
|
||||
return this->as<function>().call({arguments...});
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
table_value operator[](const char(&key)[Size]) const
|
||||
{
|
||||
return {this->as<table>(), key};
|
||||
}
|
||||
|
||||
template <typename T = script_value>
|
||||
table_value operator[](const T& key) const
|
||||
{
|
||||
return {this->as<table>(), key};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool is() const;
|
||||
|
||||
template <typename T>
|
||||
T as() const
|
||||
{
|
||||
if (!this->is<T>())
|
||||
{
|
||||
const auto hks_typename = game::hks::s_compilerTypeName[this->get_raw().t + 2];
|
||||
const auto typename_ = get_typename<T>();
|
||||
|
||||
throw std::runtime_error(utils::string::va("%s expected, got %s",
|
||||
typename_.data(), hks_typename));
|
||||
}
|
||||
|
||||
return get<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
operator T() const
|
||||
{
|
||||
return this->as<T>();
|
||||
}
|
||||
|
||||
const game::hks::HksObject& get_raw() const;
|
||||
|
||||
hks_object value_{};
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
T get() const;
|
||||
|
||||
};
|
||||
|
||||
class variadic_args : public arguments
|
||||
{
|
||||
};
|
||||
|
||||
class function_argument
|
||||
{
|
||||
public:
|
||||
function_argument(const arguments& args, const script_value& value, const int index);
|
||||
|
||||
template <typename T>
|
||||
T as() const
|
||||
{
|
||||
try
|
||||
{
|
||||
return this->value_.as<T>();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("bad argument #%d (%s)",
|
||||
this->index_ + 1, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
variadic_args as() const
|
||||
{
|
||||
variadic_args args{};
|
||||
for (auto i = this->index_; i < this->values_.size(); i++)
|
||||
{
|
||||
args.push_back(this->values_[i]);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
operator T() const
|
||||
{
|
||||
return this->as<T>();
|
||||
}
|
||||
|
||||
private:
|
||||
arguments values_{};
|
||||
script_value value_{};
|
||||
int index_{};
|
||||
};
|
||||
|
||||
class function_arguments
|
||||
{
|
||||
public:
|
||||
function_arguments(const arguments& values);
|
||||
|
||||
function_argument operator[](const int index) const
|
||||
{
|
||||
if (index >= values_.size())
|
||||
{
|
||||
return {values_, {}, index};
|
||||
}
|
||||
|
||||
return {values_, values_[index], index};
|
||||
}
|
||||
private:
|
||||
arguments values_{};
|
||||
};
|
||||
}
|
355
src/client/game/ui_scripting/types.cpp
Normal file
355
src/client/game/ui_scripting/types.cpp
Normal file
@ -0,0 +1,355 @@
|
||||
#include <std_include.hpp>
|
||||
#include "types.hpp"
|
||||
#include "execution.hpp"
|
||||
#include "../../component/ui_scripting.hpp"
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
/***************************************************************
|
||||
* Lightuserdata
|
||||
**************************************************************/
|
||||
|
||||
lightuserdata::lightuserdata(void* ptr_)
|
||||
: ptr(ptr_)
|
||||
{
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Userdata
|
||||
**************************************************************/
|
||||
|
||||
userdata::userdata(void* ptr_)
|
||||
: ptr(ptr_)
|
||||
{
|
||||
this->add();
|
||||
}
|
||||
|
||||
userdata::userdata(const userdata& other)
|
||||
{
|
||||
this->operator=(other);
|
||||
}
|
||||
|
||||
userdata::userdata(userdata&& other) noexcept
|
||||
{
|
||||
this->ptr = other.ptr;
|
||||
this->ref = other.ref;
|
||||
other.ref = 0;
|
||||
}
|
||||
|
||||
userdata::~userdata()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
userdata& userdata::operator=(const userdata& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->ptr = other.ptr;
|
||||
this->ref = other.ref;
|
||||
this->add();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
userdata& userdata::operator=(userdata&& other) noexcept
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->ptr = other.ptr;
|
||||
this->ref = other.ref;
|
||||
other.ref = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void userdata::add()
|
||||
{
|
||||
game::hks::HksObject value{};
|
||||
value.v.ptr = this->ptr;
|
||||
value.t = game::hks::TUSERDATA;
|
||||
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto top = state->m_apistack.top;
|
||||
|
||||
push_value(value);
|
||||
|
||||
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
|
||||
state->m_apistack.top = top;
|
||||
}
|
||||
|
||||
void userdata::release()
|
||||
{
|
||||
if (this->ref)
|
||||
{
|
||||
game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
|
||||
}
|
||||
}
|
||||
|
||||
void userdata::set(const script_value& key, const script_value& value) const
|
||||
{
|
||||
set_field(*this, key, value);
|
||||
}
|
||||
|
||||
script_value userdata::get(const script_value& key) const
|
||||
{
|
||||
return get_field(*this, key);
|
||||
}
|
||||
|
||||
userdata_value userdata::operator[](const script_value& key) const
|
||||
{
|
||||
return {*this, key};
|
||||
}
|
||||
|
||||
userdata_value::userdata_value(const userdata& table, const script_value& key)
|
||||
: userdata_(table)
|
||||
, key_(key)
|
||||
{
|
||||
this->value_ = this->userdata_.get(key).get_raw();
|
||||
}
|
||||
|
||||
void userdata_value::operator=(const script_value& value)
|
||||
{
|
||||
this->userdata_.set(this->key_, value);
|
||||
this->value_ = value.get_raw();
|
||||
}
|
||||
|
||||
bool userdata_value::operator==(const script_value& value)
|
||||
{
|
||||
return this->userdata_.get(this->key_) == value;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Table
|
||||
**************************************************************/
|
||||
|
||||
table::table()
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
this->ptr = game::hks::Hashtable_Create(state, 0, 0);
|
||||
this->add();
|
||||
}
|
||||
|
||||
table::table(game::hks::HashTable* ptr_)
|
||||
: ptr(ptr_)
|
||||
{
|
||||
this->add();
|
||||
}
|
||||
|
||||
table::table(const table& other)
|
||||
{
|
||||
this->operator=(other);
|
||||
}
|
||||
|
||||
table::table(table&& other) noexcept
|
||||
{
|
||||
this->ptr = other.ptr;
|
||||
this->ref = other.ref;
|
||||
other.ref = 0;
|
||||
}
|
||||
|
||||
table::~table()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
table& table::operator=(const table& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->ptr = other.ptr;
|
||||
this->ref = other.ref;
|
||||
this->add();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
table& table::operator=(table&& other) noexcept
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->ptr = other.ptr;
|
||||
this->ref = other.ref;
|
||||
other.ref = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void table::add()
|
||||
{
|
||||
game::hks::HksObject value{};
|
||||
value.v.table = this->ptr;
|
||||
value.t = game::hks::TTABLE;
|
||||
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto top = state->m_apistack.top;
|
||||
|
||||
push_value(value);
|
||||
|
||||
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
|
||||
state->m_apistack.top = top;
|
||||
}
|
||||
|
||||
void table::release()
|
||||
{
|
||||
if (this->ref)
|
||||
{
|
||||
game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
|
||||
}
|
||||
}
|
||||
|
||||
void table::set(const script_value& key, const script_value& value) const
|
||||
{
|
||||
set_field(*this, key, value);
|
||||
}
|
||||
|
||||
table_value table::operator[](const script_value& key) const
|
||||
{
|
||||
return {*this, key};
|
||||
}
|
||||
|
||||
script_value table::get(const script_value& key) const
|
||||
{
|
||||
return get_field(*this, key);
|
||||
}
|
||||
|
||||
table_value::table_value(const table& table, const script_value& key)
|
||||
: table_(table)
|
||||
, key_(key)
|
||||
{
|
||||
this->value_ = this->table_.get(key).get_raw();
|
||||
}
|
||||
|
||||
void table_value::operator=(const script_value& value)
|
||||
{
|
||||
this->table_.set(this->key_, value);
|
||||
this->value_ = value.get_raw();
|
||||
}
|
||||
|
||||
void table_value::operator=(const table_value& value)
|
||||
{
|
||||
this->table_.set(this->key_, value);
|
||||
this->value_ = value.get_raw();
|
||||
}
|
||||
|
||||
bool table_value::operator==(const script_value& value)
|
||||
{
|
||||
return this->table_.get(this->key_) == value;
|
||||
}
|
||||
|
||||
bool table_value::operator==(const table_value& value)
|
||||
{
|
||||
return this->table_.get(this->key_) == value;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Function
|
||||
**************************************************************/
|
||||
|
||||
function::function(game::hks::lua_function func)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
this->ptr = game::hks::cclosure_Create(state, func, 0, 0, 0);
|
||||
this->type = game::hks::HksObjectType::TCFUNCTION;
|
||||
this->add();
|
||||
}
|
||||
|
||||
function::function(game::hks::cclosure* ptr_, game::hks::HksObjectType type_)
|
||||
: ptr(ptr_)
|
||||
, type(type_)
|
||||
{
|
||||
this->add();
|
||||
}
|
||||
|
||||
function::function(const function& other)
|
||||
{
|
||||
this->operator=(other);
|
||||
}
|
||||
|
||||
function::function(function&& other) noexcept
|
||||
{
|
||||
this->ptr = other.ptr;
|
||||
this->type = other.type;
|
||||
this->ref = other.ref;
|
||||
other.ref = 0;
|
||||
}
|
||||
|
||||
function::~function()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
function& function::operator=(const function& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->ptr = other.ptr;
|
||||
this->type = other.type;
|
||||
this->ref = other.ref;
|
||||
this->add();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
function& function::operator=(function&& other) noexcept
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->ptr = other.ptr;
|
||||
this->type = other.type;
|
||||
this->ref = other.ref;
|
||||
other.ref = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void function::add()
|
||||
{
|
||||
game::hks::HksObject value{};
|
||||
value.v.cClosure = this->ptr;
|
||||
value.t = this->type;
|
||||
|
||||
const auto state = *game::hks::lua_state;
|
||||
const auto top = state->m_apistack.top;
|
||||
|
||||
push_value(value);
|
||||
|
||||
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
|
||||
state->m_apistack.top = top;
|
||||
}
|
||||
|
||||
void function::release()
|
||||
{
|
||||
if (this->ref)
|
||||
{
|
||||
game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
|
||||
}
|
||||
}
|
||||
|
||||
arguments function::call(const arguments& arguments) const
|
||||
{
|
||||
return call_script_function(*this, arguments);
|
||||
}
|
||||
|
||||
arguments function::operator()(const arguments& arguments) const
|
||||
{
|
||||
return this->call(arguments);
|
||||
}
|
||||
|
||||
arguments function::operator()() const
|
||||
{
|
||||
return this->call({});
|
||||
}
|
||||
}
|
140
src/client/game/ui_scripting/types.hpp
Normal file
140
src/client/game/ui_scripting/types.hpp
Normal file
@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "script_value.hpp"
|
||||
#include "../../component/ui_scripting.hpp"
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
class lightuserdata
|
||||
{
|
||||
public:
|
||||
lightuserdata(void*);
|
||||
void* ptr;
|
||||
};
|
||||
|
||||
class userdata_value;
|
||||
|
||||
class userdata
|
||||
{
|
||||
public:
|
||||
userdata(void*);
|
||||
|
||||
userdata(const userdata& other);
|
||||
userdata(userdata&& other) noexcept;
|
||||
|
||||
~userdata();
|
||||
|
||||
userdata& operator=(const userdata& other);
|
||||
userdata& operator=(userdata&& other) noexcept;
|
||||
|
||||
script_value get(const script_value& key) const;
|
||||
void set(const script_value& key, const script_value& value) const;
|
||||
|
||||
userdata_value operator[](const script_value& key) const;
|
||||
|
||||
void* ptr;
|
||||
|
||||
private:
|
||||
void add();
|
||||
void release();
|
||||
|
||||
int ref{};
|
||||
};
|
||||
|
||||
class userdata_value : public script_value
|
||||
{
|
||||
public:
|
||||
userdata_value(const userdata& table, const script_value& key);
|
||||
void operator=(const script_value& value);
|
||||
bool operator==(const script_value& value);
|
||||
private:
|
||||
userdata userdata_;
|
||||
script_value key_;
|
||||
};
|
||||
|
||||
class table_value;
|
||||
|
||||
class table
|
||||
{
|
||||
public:
|
||||
table();
|
||||
table(game::hks::HashTable* ptr_);
|
||||
|
||||
table(const table& other);
|
||||
table(table&& other) noexcept;
|
||||
|
||||
~table();
|
||||
|
||||
table& operator=(const table& other);
|
||||
table& operator=(table&& other) noexcept;
|
||||
|
||||
script_value get(const script_value& key) const;
|
||||
void set(const script_value& key, const script_value& value) const;
|
||||
|
||||
table_value operator[](const script_value& key) const;
|
||||
|
||||
game::hks::HashTable* ptr;
|
||||
|
||||
private:
|
||||
void add();
|
||||
void release();
|
||||
|
||||
int ref{};
|
||||
};
|
||||
|
||||
class table_value : public script_value
|
||||
{
|
||||
public:
|
||||
table_value(const table& table, const script_value& key);
|
||||
void operator=(const script_value& value);
|
||||
void operator=(const table_value& value);
|
||||
bool operator==(const script_value& value);
|
||||
bool operator==(const table_value& value);
|
||||
private:
|
||||
table table_;
|
||||
script_value key_;
|
||||
};
|
||||
|
||||
class function
|
||||
{
|
||||
public:
|
||||
function(game::hks::lua_function);
|
||||
function(game::hks::cclosure*, game::hks::HksObjectType);
|
||||
|
||||
template <typename F>
|
||||
function(F f)
|
||||
{
|
||||
this->ptr = ui_scripting::convert_function(f);
|
||||
this->type = game::hks::TCFUNCTION;
|
||||
}
|
||||
|
||||
function(const function& other);
|
||||
function(function&& other) noexcept;
|
||||
|
||||
~function();
|
||||
|
||||
function& operator=(const function& other);
|
||||
function& operator=(function&& other) noexcept;
|
||||
|
||||
arguments call(const arguments& arguments) const;
|
||||
|
||||
arguments operator()(const arguments& arguments) const;
|
||||
|
||||
template<class ...T>
|
||||
arguments operator()(T... arguments) const
|
||||
{
|
||||
return this->call({arguments...});
|
||||
}
|
||||
|
||||
arguments operator()() const;
|
||||
|
||||
game::hks::cclosure* ptr;
|
||||
game::hks::HksObjectType type;
|
||||
|
||||
private:
|
||||
void add();
|
||||
void release();
|
||||
|
||||
int ref{};
|
||||
};
|
||||
}
|
@ -12,3 +12,10 @@
|
||||
#define RUNNER 305
|
||||
|
||||
#define ICON_IMAGE 306
|
||||
|
||||
/*
|
||||
#define LUI_COMMON 307
|
||||
#define LUI_UPDATER 308
|
||||
|
||||
#define LUA_JSON 309
|
||||
*/
|
||||
|
@ -4,45 +4,117 @@
|
||||
|
||||
namespace utils::http
|
||||
{
|
||||
std::optional<std::string> get_data(const std::string& url)
|
||||
namespace
|
||||
{
|
||||
CComPtr<IStream> stream;
|
||||
|
||||
if (FAILED(URLOpenBlockingStreamA(nullptr, url.data(), &stream, 0, nullptr)))
|
||||
struct progress_helper
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const std::function<int(size_t, size_t)>* callback{};
|
||||
std::exception_ptr exception{};
|
||||
};
|
||||
|
||||
char buffer[0x1000];
|
||||
std::string result;
|
||||
|
||||
HRESULT status{};
|
||||
|
||||
do
|
||||
int progress_callback(void* clientp, const curl_off_t dltotal, const curl_off_t dlnow, const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/)
|
||||
{
|
||||
DWORD bytes_read = 0;
|
||||
status = stream->Read(buffer, sizeof(buffer), &bytes_read);
|
||||
auto* helper = static_cast<progress_helper*>(clientp);
|
||||
|
||||
if (bytes_read > 0)
|
||||
try
|
||||
{
|
||||
result.append(buffer, bytes_read);
|
||||
if (*helper->callback && (*helper->callback)(dltotal, dlnow) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
helper->exception = std::current_exception();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
while (SUCCEEDED(status) && status != S_FALSE);
|
||||
|
||||
if (FAILED(status))
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
auto* buffer = static_cast<std::string*>(userp);
|
||||
|
||||
return {result};
|
||||
const auto total_size = size * nmemb;
|
||||
buffer->append(static_cast<char*>(contents), total_size);
|
||||
return total_size;
|
||||
}
|
||||
}
|
||||
|
||||
std::future<std::optional<std::string>> get_data_async(const std::string& url)
|
||||
std::optional<result> get_data(const std::string& url, const std::string& fields,
|
||||
const headers& headers, const std::function<int(size_t, size_t)>& callback, int timeout)
|
||||
{
|
||||
return std::async(std::launch::async, [url]()
|
||||
curl_slist* header_list = nullptr;
|
||||
auto* curl = curl_easy_init();
|
||||
if (!curl)
|
||||
{
|
||||
return get_data(url);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto _ = gsl::finally([&]()
|
||||
{
|
||||
curl_slist_free_all(header_list);
|
||||
curl_easy_cleanup(curl);
|
||||
});
|
||||
|
||||
for (const auto& header : headers)
|
||||
{
|
||||
auto data = header.first + ": " + header.second;
|
||||
header_list = curl_slist_append(header_list, data.data());
|
||||
}
|
||||
|
||||
std::string buffer{};
|
||||
progress_helper helper{};
|
||||
helper.callback = &callback;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.data());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
|
||||
|
||||
if (!fields.empty())
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields.data());
|
||||
}
|
||||
|
||||
const auto code = curl_easy_perform(curl);
|
||||
unsigned int response_code{};
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
|
||||
if (code == CURLE_OK)
|
||||
{
|
||||
result result;
|
||||
result.code = code;
|
||||
result.response_code = response_code;
|
||||
result.buffer = std::move(buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (helper.exception)
|
||||
{
|
||||
std::rethrow_exception(helper.exception);
|
||||
}
|
||||
|
||||
result result;
|
||||
result.code = code;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::future<std::optional<result>> get_data_async(const std::string& url, const std::string& fields,
|
||||
const headers& headers, const std::function<int(size_t, size_t)>& callback)
|
||||
{
|
||||
return std::async(std::launch::async, [url, fields, headers, callback]()
|
||||
{
|
||||
return get_data(url, fields, headers, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,23 @@
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <future>
|
||||
#include <gsl/gsl>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace utils::http
|
||||
{
|
||||
std::optional<std::string> get_data(const std::string& url);
|
||||
std::future<std::optional<std::string>> get_data_async(const std::string& url);
|
||||
struct result
|
||||
{
|
||||
CURLcode code{};
|
||||
unsigned int response_code{};
|
||||
std::string buffer{};
|
||||
};
|
||||
|
||||
using headers = std::unordered_map<std::string, std::string>;
|
||||
|
||||
std::optional<result> get_data(const std::string& url, const std::string& fields = {},
|
||||
const headers& headers = {}, const std::function<int(size_t, size_t)>& callback = {}, int timeout = 0);
|
||||
std::future<std::optional<result>> get_data_async(const std::string& url, const std::string& fields = {},
|
||||
const headers& headers = {}, const std::function<int(size_t, size_t)>& callback = {});
|
||||
}
|
||||
|
24
src/common/utils/properties.cpp
Normal file
24
src/common/utils/properties.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
#include "io.hpp"
|
||||
#include "properties.hpp"
|
||||
#include <gsl/gsl>
|
||||
#include <ShlObj.h>
|
||||
|
||||
namespace utils::properties
|
||||
{
|
||||
std::filesystem::path get_appdata_path()
|
||||
{
|
||||
PWSTR path;
|
||||
if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path)))
|
||||
{
|
||||
throw std::runtime_error("Failed to read APPDATA path!");
|
||||
}
|
||||
|
||||
auto _ = gsl::finally([&path]
|
||||
{
|
||||
CoTaskMemFree(path);
|
||||
});
|
||||
|
||||
static auto appdata = std::filesystem::path(path) / "iw7-mod";
|
||||
return appdata;
|
||||
}
|
||||
}
|
6
src/common/utils/properties.hpp
Normal file
6
src/common/utils/properties.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace utils::properties
|
||||
{
|
||||
std::filesystem::path get_appdata_path();
|
||||
}
|
Loading…
Reference in New Issue
Block a user