* 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:
m 2024-03-07 20:27:06 -06:00 committed by GitHub
parent 2495cd4c5d
commit 885908ff15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 3302 additions and 45 deletions

9
.gitmodules vendored
View File

@ -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

@ -0,0 +1 @@
Subproject commit 3f08d80b2244524646ce86915c585509ac54fb4c

1
deps/lua vendored Submodule

@ -0,0 +1 @@
Subproject commit 7923dbbf72da303ca1cca17efd24725668992f15

73
deps/premake/curl.lua vendored Normal file
View 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
View 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
View 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

@ -0,0 +1 @@
Subproject commit e8e122e9ce46f4f1c0b04003d8b703fe1b89755a

View File

@ -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

View File

@ -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)

View File

@ -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");

View File

@ -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);
});
}
};
}

View File

@ -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)
{

View File

@ -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

View File

@ -16,6 +16,9 @@ namespace scheduler
// The game's main thread
main,
// LUI context
lui,
count,
};

View File

@ -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);
}
}

View File

@ -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);

View 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)

View 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();
}

View 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)

View 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();
}

View File

@ -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;
}

View File

@ -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 };
}
}

View 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);
}
}

View 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);
}

View 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)
{
}
}

View 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_{};
};
}

View 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({});
}
}

View 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{};
};
}

View File

@ -12,3 +12,10 @@
#define RUNNER 305
#define ICON_IMAGE 306
/*
#define LUI_COMMON 307
#define LUI_UPDATER 308
#define LUA_JSON 309
*/

View File

@ -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);
});
}
}

View File

@ -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 = {});
}

View 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;
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace utils::properties
{
std::filesystem::path get_appdata_path();
}