Merge branch 'develop' into rich-presence

This commit is contained in:
Federico Cecchetto 2022-04-10 21:43:37 +02:00
commit f8f61d6634
47 changed files with 1651 additions and 239 deletions

View File

@ -0,0 +1,3 @@
if (game:issingleplayer()) then
require("loading")
end

View File

@ -0,0 +1,116 @@
game:addlocalizedstring("MENU_MODS", "MODS")
game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.")
game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.")
game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3")
game:addlocalizedstring("LUA_MENU_OPEN_STORE", "Open store")
game:addlocalizedstring("LUA_MENU_OPEN_STORE_DESC", "Download and install mods.")
game:addlocalizedstring("LUA_MENU_LOADED_MOD", "Loaded mod: ^2&&1")
game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods")
game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload")
game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.")
function createdivider(menu, text)
local element = LUI.UIElement.new( {
leftAnchor = true,
rightAnchor = true,
left = 0,
right = 0,
topAnchor = true,
bottomAnchor = false,
top = 0,
bottom = 33.33
})
element.scrollingToNext = true
element:addElement(LUI.MenuBuilder.BuildRegisteredType("h1_option_menu_titlebar", {
title_bar_text = Engine.ToUpperCase(text)
}))
menu.list:addElement(element)
end
function string:truncate(length)
if (#self <= length) then
return self
end
return self:sub(1, length - 3) .. "..."
end
LUI.addmenubutton("main_campaign", {
index = 6,
text = "@MENU_MODS",
description = Engine.Localize("@MENU_MODS_DESC"),
callback = function()
LUI.FlowManager.RequestAddMenu(nil, "mods_menu")
end
})
function getmodname(path)
local name = path
local desc = Engine.Localize("@LUA_MENU_MOD_DESC_DEFAULT", name)
local infofile = path .. "/info.json"
if (io.fileexists(infofile)) then
pcall(function()
local data = json.decode(io.readfile(infofile))
desc = Engine.Localize("@LUA_MENU_MOD_DESC",
data.description, data.author, data.version)
name = data.name
end)
end
return name, desc
end
LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1)
local menu = LUI.MenuTemplate.new(a1, {
menu_title = "@MENU_MODS",
exclusiveController = 0,
menu_width = 400,
menu_top_indent = LUI.MenuTemplate.spMenuOffset,
showTopRightSmallBar = true
})
local modfolder = game:getloadedmod()
if (modfolder ~= "") then
createdivider(menu, Engine.Localize("@LUA_MENU_LOADED_MOD", getmodname(modfolder):truncate(24)))
menu:AddButton("@LUA_MENU_UNLOAD", function()
Engine.Exec("unloadmod")
end, nil, true, nil, {
desc_text = Engine.Localize("@LUA_MENU_UNLOAD_DESC")
})
end
createdivider(menu, Engine.Localize("@LUA_MENU_AVAILABLE_MODS"))
if (io.directoryexists("mods")) then
local mods = io.listfiles("mods/")
for i = 1, #mods do
if (io.directoryexists(mods[i]) and not io.directoryisempty(mods[i])) then
local name, desc = getmodname(mods[i])
if (mods[i] ~= modfolder) then
game:addlocalizedstring(name, name)
menu:AddButton(name, function()
Engine.Exec("loadmod " .. mods[i])
end, nil, true, nil, {
desc_text = desc
})
end
end
end
end
menu:AddBackButton(function(a1)
Engine.PlaySound(CoD.SFX.MenuBack)
LUI.FlowManager.RequestLeaveMenu(a1)
end)
LUI.Options.InitScrollingList(menu.list, nil)
menu:CreateBottomDivider()
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
return menu
end

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit 4377f6e603c64a86c934f1546aa9db482f2e1a4e
Subproject commit 383723676cd548d615159701ac3d050f8dd1e128

2
deps/asmjit vendored

@ -1 +1 @@
Subproject commit f1a399c4fe74d1535a4190a2b8727c51045cc914
Subproject commit 752eb38a4dbe590995cbadaff06baadd8378eeeb

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit 673f5ce29015a9bba3c96792920a10601b5b0718
Subproject commit 06a81aeb227424182125363f7554fad5146d6d2a

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 66de86426e9cdb88526974c765108f01554af2b0
Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 2014a993addbc8f1b9785d97f55fd189792c2f78
Subproject commit ec3df00224d4b396e2ac6586ab5d25f673caa4c2

View File

@ -264,6 +264,7 @@ filter {}
filter "configurations:Debug"
optimize "Debug"
buildoptions {"/bigobj"}
defines {"DEBUG", "_DEBUG"}
filter {}
@ -320,6 +321,10 @@ if _OPTIONS["copy-to"] then
postbuildcommands {"copy /y \"$(TargetPath)\" \"" .. _OPTIONS["copy-to"] .. "\""}
end
if _OPTIONS["debug-dir"] then
debugdir ( _OPTIONS["debug-dir"] )
end
dependencies.imports()
project "tlsdll"

View File

@ -8,6 +8,7 @@
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/io.hpp>
namespace filesystem
{
@ -70,6 +71,40 @@ namespace filesystem
return this->name_;
}
std::unordered_set<std::string>& get_search_paths()
{
static std::unordered_set<std::string> search_paths{};
return search_paths;
}
std::string read_file(const std::string& path)
{
for (const auto& search_path : get_search_paths())
{
const auto path_ = search_path + "/" + path;
if (utils::io::file_exists(path_))
{
return utils::io::read_file(path_);
}
}
return {};
}
bool read_file(const std::string& path, std::string* data)
{
for (const auto& search_path : get_search_paths())
{
const auto path_ = search_path + "/" + path;
if (utils::io::read_file(path_, data))
{
return true;
}
}
return false;
}
class component final : public component_interface
{
public:
@ -87,6 +122,10 @@ namespace filesystem
utils::hook::call(SELECT_VALUE(0x1403B8D31, 0x1404EE3D0), register_custom_path_stub);
utils::hook::call(SELECT_VALUE(0x1403B8D51, 0x1404EE3F0), register_custom_path_stub);
utils::hook::call(SELECT_VALUE(0x1403B8D90, 0x1404EE42F), register_custom_path_stub);
get_search_paths().insert(".");
get_search_paths().insert("h1-mod");
get_search_paths().insert("data");
}
};
}

View File

@ -16,4 +16,8 @@ namespace filesystem
std::string name_;
std::string buffer_;
};
std::unordered_set<std::string>& get_search_paths();
std::string read_file(const std::string& path);
bool read_file(const std::string& path, std::string* data);
}

View File

@ -0,0 +1,136 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "fonts.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/image.hpp>
#include <utils/concurrency.hpp>
namespace fonts
{
namespace
{
struct font_data_t
{
std::unordered_map<std::string, game::TTF*> fonts;
std::unordered_map<std::string, std::string> raw_fonts;
};
utils::concurrency::container<font_data_t> font_data;
game::TTF* create_font(const std::string& name, const std::string& data)
{
const auto font = utils::memory::get_allocator()->allocate<game::TTF>();
font->name = utils::memory::get_allocator()->duplicate_string(name);
font->buffer = utils::memory::get_allocator()->duplicate_string(data);
font->len = static_cast<int>(data.size());
font->fontFace = 0;
return font;
}
void free_font(game::TTF* font)
{
utils::memory::get_allocator()->free(font->buffer);
utils::memory::get_allocator()->free(font->name);
utils::memory::get_allocator()->free(font);
}
game::TTF* load_font(const std::string& name)
{
return font_data.access<game::TTF*>([&](font_data_t& data_) -> game::TTF*
{
if (const auto i = data_.fonts.find(name); i != data_.fonts.end())
{
return i->second;
}
std::string data{};
if (const auto i = data_.raw_fonts.find(name); i != data_.raw_fonts.end())
{
data = i->second;
}
if (data.empty() && !filesystem::read_file(name, &data))
{
return nullptr;
}
const auto material = create_font(name, data);
data_.fonts[name] = material;
return material;
});
}
game::TTF* try_load_font(const std::string& name)
{
try
{
return load_font(name);
}
catch (const std::exception& e)
{
console::error("Failed to load font %s: %s\n", name.data(), e.what());
}
return nullptr;
}
game::TTF* db_find_xasset_header_stub(game::XAssetType type, const char* name, int create_default)
{
auto result = try_load_font(name);
if (result == nullptr)
{
result = game::DB_FindXAssetHeader(type, name, create_default).ttf;
}
return result;
}
}
void add(const std::string& name, const std::string& data)
{
font_data.access([&](font_data_t& data_)
{
data_.raw_fonts[name] = data;
});
}
void clear()
{
font_data.access([&](font_data_t& data_)
{
for (auto& font : data_.fonts)
{
free_font(font.second);
}
data_.fonts.clear();
utils::hook::set<int>(SELECT_VALUE(0x14F09DBB8, 0x14FD61EE8), 0); // reset registered font count
});
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
utils::hook::call(SELECT_VALUE(0x1404D41B6, 0x1405D9296), db_find_xasset_header_stub);
}
};
}
REGISTER_COMPONENT(fonts::component)

View File

@ -0,0 +1,7 @@
#pragma once
namespace fonts
{
void add(const std::string& name, const std::string& data);
void clear();
}

View File

@ -571,7 +571,7 @@ namespace game_console
{
if (key == game::keyNum_t::K_F10)
{
if (!game::Com_InFrontEnd())
if (!game::Com_InFrontend())
{
return false;
}

View File

@ -4,6 +4,7 @@
#include "game/game.hpp"
#include "game_console.hpp"
#include "game/ui_scripting/execution.hpp"
#include <utils/hook.hpp>
@ -14,8 +15,22 @@ namespace input
utils::hook::detour cl_char_event_hook;
utils::hook::detour cl_key_event_hook;
bool lui_running()
{
return *game::hks::lua_state != nullptr;
}
void cl_char_event_stub(const int local_client_num, const int key)
{
if (lui_running())
{
ui_scripting::notify("keypress",
{
{"keynum", key},
{"key", game::Key_KeynumToString(key, 0, 1)},
});
}
if (!game_console::console_char_event(local_client_num, key))
{
return;
@ -26,6 +41,15 @@ namespace input
void cl_key_event_stub(const int local_client_num, const int key, const int down)
{
if (lui_running())
{
ui_scripting::notify(down ? "keydown" : "keyup",
{
{"keynum", key},
{"key", game::Key_KeynumToString(key, 0, 1)},
});
}
if (!game_console::console_key_event(local_client_num, key, down))
{
return;

View File

@ -212,7 +212,7 @@ namespace logfile
return false;
}
const auto hook = vm_execute_hooks[pos];
const auto& hook = vm_execute_hooks[pos];
const auto state = hook.lua_state();
const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
@ -296,6 +296,8 @@ namespace logfile
public:
void post_unpack() override
{
utils::hook::jump(SELECT_VALUE(0x140376655, 0x140444645), utils::hook::assemble(vm_execute_stub), true);
if (game::environment::is_sp())
{
return;
@ -308,8 +310,6 @@ namespace logfile
utils::hook::call(0x140484EC0, g_shutdown_game_stub);
utils::hook::call(0x1404853C1, g_shutdown_game_stub);
utils::hook::jump(SELECT_VALUE(0x140376655, 0x140444645), utils::hook::assemble(vm_execute_stub), true);
}
};
}

View File

@ -3,6 +3,7 @@
#include "materials.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
@ -20,6 +21,7 @@ namespace materials
{
utils::hook::detour db_material_streaming_fail_hook;
utils::hook::detour material_register_handle_hook;
utils::hook::detour db_get_material_index_hook;
struct material_data_t
{
@ -27,6 +29,8 @@ namespace materials
std::unordered_map<std::string, std::string> images;
};
char constant_table[0x20] = {};
utils::concurrency::container<material_data_t> material_data;
game::GfxImage* setup_image(game::GfxImage* image, const utils::image& raw_image)
@ -47,8 +51,7 @@ namespace materials
game::Material* create_material(const std::string& name, const std::string& data)
{
const auto white = *reinterpret_cast<game::Material**>(SELECT_VALUE(0x141F3D860, 0x14282C330));
const auto white = material_register_handle_hook.invoke<game::Material*>("white");
const auto material = utils::memory::get_allocator()->allocate<game::Material>();
const auto texture_table = utils::memory::get_allocator()->allocate<game::MaterialTextureDef>();
const auto image = utils::memory::get_allocator()->allocate<game::GfxImage>();
@ -57,6 +60,7 @@ namespace materials
std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef));
std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage));
material->constantTable = &constant_table;
material->name = utils::memory::get_allocator()->duplicate_string(name);
image->name = material->name;
@ -66,6 +70,16 @@ namespace materials
return material;
}
void free_material(game::Material* material)
{
material->textureTable->u.image->textures.___u0.map->Release();
material->textureTable->u.image->textures.shaderView->Release();
utils::memory::get_allocator()->free(material->textureTable->u.image);
utils::memory::get_allocator()->free(material->textureTable);
utils::memory::get_allocator()->free(material->name);
utils::memory::get_allocator()->free(material);
}
game::Material* load_material(const std::string& name)
{
return material_data.access<game::Material*>([&](material_data_t& data_) -> game::Material*
@ -81,10 +95,9 @@ namespace materials
data = i->second;
}
if (data.empty()
&& !utils::io::read_file(utils::string::va("h1-mod/materials/%s.png", name.data()), &data)
&& !utils::io::read_file(utils::string::va("data/materials/%s.png", name.data()), &data))
if (data.empty() && !filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data))
{
data_.materials[name] = nullptr;
return nullptr;
}
@ -124,24 +137,24 @@ namespace materials
return result;
}
bool db_material_streaming_fail_stub(game::Material* material)
int db_material_streaming_fail_stub(game::Material* material)
{
const auto found = material_data.access<bool>([material](material_data_t& data_)
if (material->constantTable == &constant_table)
{
if (data_.materials.find(material->name) != data_.materials.end())
{
return true;
return 0;
}
return false;
});
if (found)
{
return false;
return db_material_streaming_fail_hook.invoke<int>(material);
}
return db_material_streaming_fail_hook.invoke<bool>(material);
unsigned int db_get_material_index_stub(game::Material* material)
{
if (material->constantTable == &constant_table)
{
return 0;
}
return db_get_material_index_hook.invoke<unsigned int>(material);
}
}
@ -153,6 +166,24 @@ namespace materials
});
}
void clear()
{
material_data.access([&](material_data_t& data_)
{
for (auto& material : data_.materials)
{
if (material.second == nullptr)
{
continue;
}
free_material(material.second);
}
data_.materials.clear();
});
}
class component final : public component_interface
{
public:
@ -165,6 +196,7 @@ namespace materials
material_register_handle_hook.create(game::Material_RegisterHandle, material_register_handle_stub);
db_material_streaming_fail_hook.create(SELECT_VALUE(0x1401D3180, 0x1402C6260), db_material_streaming_fail_stub);
db_get_material_index_hook.create(SELECT_VALUE(0x1401CAD00, 0x1402BBB20), db_get_material_index_stub);
}
};
}

View File

@ -3,4 +3,5 @@
namespace materials
{
void add(const std::string& name, const std::string& data);
void clear();
}

View File

@ -0,0 +1,119 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "command.hpp"
#include "console.hpp"
#include "scheduler.hpp"
#include "filesystem.hpp"
#include "materials.hpp"
#include "fonts.hpp"
#include "mods.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
namespace mods
{
std::string mod_path{};
namespace
{
utils::hook::detour db_release_xassets_hook;
bool release_assets = false;
void db_release_xassets_stub()
{
if (release_assets)
{
materials::clear();
fonts::clear();
}
db_release_xassets_hook.invoke<void>();
}
void restart()
{
scheduler::once([]()
{
release_assets = true;
game::Com_Shutdown("");
release_assets = false;
}, scheduler::pipeline::main);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_sp())
{
return;
}
if (!utils::io::directory_exists("mods"))
{
utils::io::create_directory("mods");
}
db_release_xassets_hook.create(SELECT_VALUE(0x1401CD560, 0x1402BF160), db_release_xassets_stub);
command::add("loadmod", [](const command::params& params)
{
if (params.size() < 2)
{
console::info("Usage: loadmod mods/<modname>");
return;
}
if (!game::Com_InFrontend())
{
console::info("Cannot load mod while in-game!\n");
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
return;
}
const auto path = params.get(1);
if (!utils::io::directory_exists(path))
{
console::info("Mod %s not found!\n", path);
return;
}
console::info("Loading mod %s\n", path);
filesystem::get_search_paths().erase(mod_path);
filesystem::get_search_paths().insert(path);
mod_path = path;
restart();
});
command::add("unloadmod", [](const command::params& params)
{
if (mod_path.empty())
{
console::info("No mod loaded\n");
return;
}
if (!game::Com_InFrontend())
{
console::info("Cannot unload mod while in-game!\n");
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
return;
}
console::info("Unloading mod %s\n", mod_path.data());
filesystem::get_search_paths().erase(mod_path);
mod_path.clear();
restart();
});
}
};
}
REGISTER_COMPONENT(mods::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace mods
{
extern std::string mod_path;
}

View File

@ -241,10 +241,10 @@ namespace patches
// unlock safeArea_*
utils::hook::jump(0x1402624F5, 0x140262503);
utils::hook::jump(0x14026251C, 0x140262547);
dvars::override::register_int("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_int("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_int("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_int("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_float("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_float("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_float("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_float("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
// allow servers to check for new packages more often
dvars::override::register_int("sv_network_fps", 1000, 20, 1000, game::DVAR_FLAG_SAVED);
@ -257,7 +257,7 @@ namespace patches
dvars::register_int("scr_game_spectatetype", 1, 0, 99, game::DVAR_FLAG_REPLICATED, "");
dvars::override::register_bool("ui_drawcrosshair", true, game::DVAR_FLAG_WRITE);
dvars::override::register_bool("ui_drawCrosshair", true, game::DVAR_FLAG_WRITE);
dvars::override::register_int("com_maxfps", 0, 0, 1000, game::DVAR_FLAG_SAVED);

View File

@ -2,7 +2,7 @@
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include "game/dvars.hpp"
#include "game/scripting/entity.hpp"
#include "game/scripting/functions.hpp"
@ -13,14 +13,20 @@
#include "scheduler.hpp"
#include "scripting.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace scripting
{
std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
utils::concurrency::container<shared_table_t> shared_table;
namespace
{
utils::hook::detour vm_notify_hook;
utils::hook::detour vm_execute_hook;
utils::hook::detour scr_load_level_hook;
utils::hook::detour g_shutdown_game_hook;
@ -29,7 +35,14 @@ namespace scripting
utils::hook::detour scr_set_thread_position_hook;
utils::hook::detour process_script_hook;
utils::hook::detour sl_get_canonical_string_hook;
utils::hook::detour db_find_xasset_header_hook;
std::string current_file;
unsigned int current_file_id{};
game::dvar_t* g_dump_scripts;
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
game::VariableValue* top)
@ -48,11 +61,6 @@ namespace scripting
e.arguments.emplace_back(*value);
}
if (e.name == "entitydeleted")
{
scripting::clear_entity_fields(e.entity);
}
lua::engine::notify(e);
}
}
@ -60,6 +68,16 @@ namespace scripting
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
}
unsigned int vm_execute_stub()
{
if (!lua::engine::is_running())
{
lua::engine::start();
}
return vm_execute_hook.invoke<unsigned int>();
}
void scr_load_level_stub()
{
scr_load_level_hook.invoke<void>();
@ -71,20 +89,25 @@ namespace scripting
void g_shutdown_game_stub(const int free_scripts)
{
if (free_scripts)
{
script_function_table.clear();
}
lua::engine::stop();
return g_shutdown_game_hook.invoke<void>(free_scripts);
}
void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t _name, unsigned int canonicalString, unsigned int offset)
void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t name, unsigned int canonical_string, unsigned int offset)
{
const auto name = game::SL_ConvertToString(_name);
const auto name_str = game::SL_ConvertToString(name);
if (fields_table[classnum].find(name) == fields_table[classnum].end())
if (fields_table[classnum].find(name_str) == fields_table[classnum].end())
{
fields_table[classnum][name] = offset;
fields_table[classnum][name_str] = offset;
}
scr_add_class_field_hook.invoke<void>(classnum, _name, canonicalString, offset);
scr_add_class_field_hook.invoke<void>(classnum, name, canonical_string, offset);
}
void process_script_stub(const char* filename)
@ -92,21 +115,69 @@ namespace scripting
const auto file_id = atoi(filename);
if (file_id)
{
current_file = scripting::find_token(file_id);
current_file_id = file_id;
}
else
{
current_file_id = 0;
current_file = filename;
}
process_script_hook.invoke<void>(filename);
}
void scr_set_thread_position_stub(unsigned int threadName, const char* codePos)
void add_function(const std::string& file, unsigned int id, const char* pos)
{
const auto function_name = scripting::find_token(threadName);
script_function_table[current_file][function_name] = codePos;
scr_set_thread_position_hook.invoke<void>(threadName, codePos);
const auto function_names = scripting::find_token(id);
for (const auto& name : function_names)
{
script_function_table[file][name] = pos;
}
}
void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos)
{
if (current_file_id)
{
const auto names = scripting::find_token(current_file_id);
for (const auto& name : names)
{
add_function(name, thread_name, code_pos);
}
}
else
{
add_function(current_file, thread_name, code_pos);
}
scr_set_thread_position_hook.invoke<void>(thread_name, code_pos);
}
unsigned int sl_get_canonical_string_stub(const char* str)
{
const auto result = sl_get_canonical_string_hook.invoke<unsigned int>(str);
scripting::token_map[str] = result;
return result;
}
game::XAssetHeader db_find_xasset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
{
const auto result = db_find_xasset_header_hook.invoke<game::XAssetHeader>(type, name, allow_create_default);
if (!g_dump_scripts->current.enabled || type != game::XAssetType::ASSET_TYPE_SCRIPTFILE)
{
return result;
}
std::string buffer;
buffer.append(result.scriptfile->name, strlen(result.scriptfile->name) + 1);
buffer.append(reinterpret_cast<char*>(&result.scriptfile->compressedLen), 4);
buffer.append(reinterpret_cast<char*>(&result.scriptfile->len), 4);
buffer.append(reinterpret_cast<char*>(&result.scriptfile->bytecodeLen), 4);
buffer.append(result.scriptfile->buffer, result.scriptfile->compressedLen);
buffer.append(result.scriptfile->bytecode, result.scriptfile->bytecodeLen);
utils::io::write_file(utils::string::va("gsc_dump/%s.gscbin", name), buffer);
return result;
}
}
@ -115,21 +186,28 @@ namespace scripting
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
vm_notify_hook.create(SELECT_VALUE(0x140379A00, 0x1404479F0), vm_notify_stub);
scr_add_class_field_hook.create(SELECT_VALUE(0x140370370, 0x14043E2C0), scr_add_class_field_stub);
scr_set_thread_position_hook.create(SELECT_VALUE(0x14036A180, 0x140437D10), scr_set_thread_position_stub);
process_script_hook.create(SELECT_VALUE(0x1403737E0, 0x1404417E0), process_script_stub);
sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub);
if (!game::environment::is_sp())
{
scr_load_level_hook.create(SELECT_VALUE(0x1402A5BE0, 0x1403727C0), scr_load_level_stub);
}
else
{
vm_execute_hook.create(SELECT_VALUE(0x140376590, 0x140444580), vm_execute_stub);
}
g_shutdown_game_hook.create(SELECT_VALUE(0x140277D40, 0x140345A60), g_shutdown_game_stub);
db_find_xasset_header_hook.create(game::DB_FindXAssetHeader, db_find_xasset_header_stub);
g_dump_scripts = dvars::register_bool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts");
scheduler::loop([]()
{
lua::engine::run_frame();

View File

@ -3,6 +3,9 @@
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 utils::concurrency::container<shared_table_t> shared_table;
}

View File

@ -21,7 +21,7 @@ namespace stats
utils::hook::detour is_item_unlocked_hook2;
utils::hook::detour is_item_unlocked_hook3;
int is_item_unlocked_stub(void* a1, void* a2, void* a3)
int is_item_unlocked_stub(int a1, void* a2, int a3)
{
if (cg_unlock_all_items->current.enabled)
{
@ -31,7 +31,7 @@ namespace stats
return is_item_unlocked_hook.invoke<int>(a1, a2, a3);
}
int is_item_unlocked_stub2(void* a1, void* a2, void* a3, void* a4, void* a5, void* a6)
int is_item_unlocked_stub2(int a1, void* a2, void* a3, void* a4, int a5, void* a6)
{
if (cg_unlock_all_items->current.enabled)
{
@ -41,7 +41,7 @@ namespace stats
return is_item_unlocked_hook2.invoke<int>(a1, a2, a3, a4, a5, a6);
}
int is_item_unlocked_stub3(void* a1)
int is_item_unlocked_stub3(int a1)
{
if (cg_unlock_all_items->current.enabled)
{
@ -50,6 +50,11 @@ namespace stats
return is_item_unlocked_hook3.invoke<int>(a1);
}
int is_item_unlocked()
{
return 0;
}
}
class component final : public component_interface
@ -57,11 +62,19 @@ namespace stats
public:
void post_unpack() override
{
if (!game::environment::is_mp())
if (game::environment::is_sp())
{
return;
}
if (game::environment::is_dedi())
{
utils::hook::jump(0x140413E60, is_item_unlocked);
utils::hook::jump(0x140413860, is_item_unlocked);
utils::hook::jump(0x140412B70, is_item_unlocked);
}
else
{
cg_unlock_all_items = dvars::register_bool("cg_unlockall_items", false, game::DVAR_FLAG_SAVED,
"Whether items should be locked based on the player's stats or always unlocked.");
dvars::register_bool("cg_unlockall_classes", false, game::DVAR_FLAG_SAVED,
@ -71,6 +84,7 @@ namespace stats
is_item_unlocked_hook2.create(0x140413860, is_item_unlocked_stub2);
is_item_unlocked_hook3.create(0x140412B70, is_item_unlocked_stub3);
}
}
};
}

View File

@ -145,47 +145,6 @@ namespace scripting
return exec_ent_thread(entity, pos, arguments);
}
static std::unordered_map<unsigned int, std::unordered_map<std::string, script_value>> custom_fields;
script_value get_custom_field(const entity& entity, const std::string& field)
{
auto& fields = custom_fields[entity.get_entity_id()];
const auto _field = fields.find(field);
if (_field != fields.end())
{
return _field->second;
}
return {};
}
void set_custom_field(const entity& entity, const std::string& field, const script_value& value)
{
const auto id = entity.get_entity_id();
if (custom_fields[id].find(field) != custom_fields[id].end())
{
custom_fields[id][field] = value;
return;
}
custom_fields[id].insert(std::make_pair(field, value));
}
void clear_entity_fields(const entity& entity)
{
const auto id = entity.get_entity_id();
if (custom_fields.find(id) != custom_fields.end())
{
custom_fields[id].clear();
}
}
void clear_custom_fields()
{
custom_fields.clear();
}
void set_entity_field(const entity& entity, const std::string& field, const script_value& value)
{
const auto entref = entity.get_entity_reference();
@ -206,8 +165,7 @@ namespace scripting
}
else
{
// Read custom fields
set_custom_field(entity, field, value);
set_object_variable(entity.get_entity_id(), field, value);
}
}
@ -234,8 +192,7 @@ namespace scripting
return value;
}
// Add custom fields
return get_custom_field(entity, field);
return get_object_variable(entity.get_entity_id(), field);
}
unsigned int make_array()
@ -248,4 +205,47 @@ namespace scripting
return index;
}
void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value)
{
const auto offset = 0xFA00 * (parent_id & 3);
const auto variable_id = game::GetVariable(parent_id, id);
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
const auto& raw_value = value.get_raw();
game::AddRefToValue(raw_value.type, raw_value.u);
game::RemoveRefToValue(variable->type, variable->u.u);
variable->type = static_cast<char>(raw_value.type);
variable->u.u = raw_value.u;
}
void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value)
{
const auto id = scripting::find_token_id(name);
set_object_variable(parent_id, id, value);
}
script_value get_object_variable(const unsigned int parent_id, const unsigned int id)
{
const auto offset = 0xFA00 * (parent_id & 3);
const auto variable_id = game::FindVariable(parent_id, id);
if (!variable_id)
{
return {};
}
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
game::VariableValue value{};
value.type = static_cast<int>(variable->type);
value.u = variable->u.u;
return value;
}
script_value get_object_variable(const unsigned int parent_id, const std::string& name)
{
const auto id = scripting::find_token_id(name);
return get_object_variable(parent_id, id);
}
}

View File

@ -27,13 +27,16 @@ namespace scripting
script_value call_script_function(const entity& entity, const std::string& filename,
const std::string& function, const std::vector<script_value>& arguments);
void clear_entity_fields(const entity& entity);
void clear_custom_fields();
void set_entity_field(const entity& entity, const std::string& field, const script_value& value);
script_value get_entity_field(const entity& entity, const std::string& field);
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments);
unsigned int make_array();
script_value get_object_variable(const unsigned int parent_id, const unsigned int id);
script_value get_object_variable(const unsigned int parent_id, const std::string& name);
void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value);
void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value);
}

View File

@ -931,12 +931,12 @@ namespace scripting
{"setstablemissile", 0x8092}, // SP 0x1402AD800 MP 0x000000000
{"playersetgroundreferenceent", 0x8093}, // SP 0x1402A9070 MP 0x1403752A0
{"dontinterpolate", 0x8094}, // SP 0x1402A0070 MP 0x140358360
{"_meth_8095", 0x8095}, // SP 0x1402AAC80 MP 0x000000000
{"_meth_8096", 0x8096}, // SP 0x1402AAD20 MP 0x000000000
{"dospawn", 0x8095}, // SP 0x1402AAC80 MP 0x000000000
{"stalingradspawn", 0x8096}, // SP 0x1402AAD20 MP 0x000000000
{"getorigin", 0x8097}, // SP 0x1402AADC0 MP 0x140377CA0
{"_meth_8098", 0x8098}, // SP 0x1402AAE70 MP 0x000000000
{"_meth_8099", 0x8099}, // SP 0x1402AAF90 MP 0x000000000
{"_meth_809a", 0x809A}, // SP 0x1402AC1D0 MP 0x000000000
{"getcentroid", 0x8098}, // SP 0x1402AAE70 MP 0x000000000
{"getshootatpos", 0x8099}, // SP 0x1402AAF90 MP 0x000000000
{"getdebugeye", 0x809A}, // SP 0x1402AC1D0 MP 0x000000000
{"useby", 0x809B}, // SP 0x1402AC470 MP 0x140377D00
{"playsound", 0x809C}, // SP 0x1402AC9B0 MP 0x140377F40
{"_meth_809d", 0x809D},
@ -1336,14 +1336,14 @@ namespace scripting
{"vehicleturretcontroloff", 0x8227}, // SP 0x140464260 MP 0x140562970
{"isturretready", 0x8228}, // SP 0x140464340 MP 0x1405629E0
{"_meth_8229", 0x8229}, // SP 0x140464570 MP 0x140562C70
{"dospawn", 0x822A}, // SP 0x1404646D0 MP 0x140562D90
{"isphysveh", 0x822B}, // SP 0x1404647A0 MP 0x140562E80
{"crash", 0x822C}, // SP 0x140464840 MP 0x140562F80
{"launch", 0x822D}, // SP 0x140464980 MP 0x1405630A0
{"disablecrashing", 0x822E}, // SP 0x140464B20 MP 0x140563240
{"enablecrashing", 0x822F}, // SP 0x140464C10 MP 0x140563310
{"setspeed", 0x8230}, // SP 0x140464C90 MP 0x1405633E0
{"setconveyorbelt", 0x8231}, // SP 0x140464E50 MP 0x140563610
{"vehicle_dospawn", 0x822A}, // SP 0x1404646D0 MP 0x140562D90
{"vehicle_isphysveh", 0x822B}, // SP 0x1404647A0 MP 0x140562E80
{"vehphys_crash", 0x822C}, // SP 0x140464840 MP 0x140562F80
{"vehphys_launch", 0x822D}, // SP 0x140464980 MP 0x1405630A0
{"vehphys_disablecrashing", 0x822E}, // SP 0x140464B20 MP 0x140563240
{"vehphys_enablecrashing", 0x822F}, // SP 0x140464C10 MP 0x140563310
{"vehphys_setspeed", 0x8230}, // SP 0x140464C90 MP 0x1405633E0
{"vehphys_setconveyorbelt", 0x8231}, // SP 0x140464E50 MP 0x140563610
{"freevehicle", 0x8232}, // SP 0x000000000 MP 0x1405609B0
{"_meth_8233", 0x8233}, // SP 0x140290E70 MP 0x000000000
{"_meth_8234", 0x8234}, // SP 0x1402910E0 MP 0x000000000
@ -1393,8 +1393,8 @@ namespace scripting
{"canturrettargetpoint", 0x8260}, // SP 0x1404635A0 MP 0x140561DF0
{"setlookatent", 0x8261}, // SP 0x1404638A0 MP 0x1405620F0
{"clearlookatent", 0x8262}, // SP 0x140463950 MP 0x1405621A0
{"setweapon", 0x8263}, // SP 0x140463AB0 MP 0x140562280
{"_meth_8264", 0x8264}, // SP 0x140463B20 MP 0x1405622F0
{"setvehweapon", 0x8263}, // SP 0x140463AB0 MP 0x140562280
{"fireweapon", 0x8264}, // SP 0x140463B20 MP 0x1405622F0
{"vehicleturretcontrolon", 0x8265}, // SP 0x1404641D0 MP 0x1405628F0
{"finishplayerdamage", 0x8266}, // SP 0x000000000 MP 0x1403337A0
{"suicide", 0x8267}, // SP 0x000000000 MP 0x140333E20
@ -1421,16 +1421,16 @@ namespace scripting
{"setswitchnode", 0x827C}, // SP 0x140465740 MP 0x14055F4D0
{"setwaitspeed", 0x827D}, // SP 0x140465840 MP 0x14055F560
{"finishdamage", 0x827E}, // SP 0x000000000 MP 0x14055F5E0
{"setspeed", 0x827F}, // SP 0x1404658C0 MP 0x14055F840
{"setspeedimmediate", 0x8280}, // SP 0x140465930 MP 0x14055F8B0
{"_meth_8281", 0x8281}, // SP 0x140465AC0 MP 0x14055FA60
{"getspeed", 0x8282}, // SP 0x140465BE0 MP 0x14055FB80
{"getvelocity", 0x8283}, // SP 0x140465CD0 MP 0x14055FC70
{"getbodyvelocity", 0x8284}, // SP 0x140465D40 MP 0x14055FCE0
{"getsteering", 0x8285}, // SP 0x140465DB0 MP 0x14055FD50
{"getthrottle", 0x8286}, // SP 0x140465E30 MP 0x14055FDE0
{"turnengineoff", 0x8287}, // SP 0x140465EA0 MP 0x14055FE50
{"turnengineon", 0x8288}, // SP 0x140465F00 MP 0x14055FEC0
{"vehicle_setspeed", 0x827F}, // SP 0x1404658C0 MP 0x14055F840
{"vehicle_setspeedimmediate", 0x8280}, // SP 0x140465930 MP 0x14055F8B0
{"vehicle_rotateyaw", 0x8281}, // SP 0x140465AC0 MP 0x14055FA60
{"vehicle_getspeed", 0x8282}, // SP 0x140465BE0 MP 0x14055FB80
{"vehicle_getvelocity", 0x8283}, // SP 0x140465CD0 MP 0x14055FC70
{"vehicle_getbodyvelocity", 0x8284}, // SP 0x140465D40 MP 0x14055FCE0
{"vehicle_getsteering", 0x8285}, // SP 0x140465DB0 MP 0x14055FD50
{"vehicle_getthrottle", 0x8286}, // SP 0x140465E30 MP 0x14055FDE0
{"vehicle_turnengineoff", 0x8287}, // SP 0x140465EA0 MP 0x14055FE50
{"vehicle_turnengineon", 0x8288}, // SP 0x140465F00 MP 0x14055FEC0
{"_meth_8289", 0x8289}, // SP 0x140465F60 MP 0x000000000
{"getgoalspeedmph", 0x828A}, // SP 0x140466020 MP 0x14055FF30
{"_meth_828b", 0x828B}, // SP 0x140466090 MP 0x14055FFA0
@ -1457,36 +1457,36 @@ namespace scripting
{"visionsyncwithplayer", 0x82A0}, // SP 0x000000000 MP 0x14032ED90
{"showhudsplash", 0x82A1}, // SP 0x140263850 MP 0x14032FB10
{"setperk", 0x82A2}, // SP 0x140265490 MP 0x1403297E0
{"_meth_82a3", 0x82A3}, // SP 0x1402659A0 MP 0x140329D00
{"_meth_82a4", 0x82A4}, // SP 0x1402661B0 MP 0x14032A460
{"_meth_82a5", 0x82A5}, // SP 0x140265D40 MP 0x14032A0A0
{"hasperk", 0x82A3}, // SP 0x1402659A0 MP 0x140329D00
{"clearperks", 0x82A4}, // SP 0x1402661B0 MP 0x14032A460
{"unsetperk", 0x82A5}, // SP 0x140265D40 MP 0x14032A0A0
{"registerparty", 0x82A6}, // SP 0x000000000 MP 0x1403323C0
{"_meth_82a7", 0x82A7}, // SP 0x000000000 MP 0x1403324F0
{"_meth_82a8", 0x82A8}, // SP 0x1405D92F0 MP 0x14032A8F0
{"_meth_82a9", 0x82A9}, // SP 0x1405D92F0 MP 0x14032A900
{"getfireteammembers", 0x82A7}, // SP 0x000000000 MP 0x1403324F0
{"noclip", 0x82A8}, // SP 0x1405D92F0 MP 0x14032A8F0
{"ufo", 0x82A9}, // SP 0x1405D92F0 MP 0x14032A900
{"moveto", 0x82AA}, // SP 0x1402B2A10 MP 0x14037E950
{"rotatepitch", 0x82AB}, // SP 0x1402B2F60 MP 0x14037EEB0
{"rotateyaw", 0x82AC}, // SP 0x1402B2F70 MP 0x14037EEC0
{"rotateroll", 0x82AD}, // SP 0x1402B2F90 MP 0x14037EEE0
{"movex", 0x82AB}, // SP 0x1402B2F60 MP 0x14037EEB0
{"movey", 0x82AC}, // SP 0x1402B2F70 MP 0x14037EEC0
{"movez", 0x82AD}, // SP 0x1402B2F90 MP 0x14037EEE0
{"movegravity", 0x82AE}, // SP 0x1402B2C10 MP 0x14037EB00
{"_meth_82af", 0x82AF}, // SP 0x1402B2D70 MP 0x14037EC90
{"_meth_82b0", 0x82B0}, // SP 0x1402B2EE0 MP 0x14037EE20
{"moveslide", 0x82AF}, // SP 0x1402B2D70 MP 0x14037EC90
{"stopmoveslide", 0x82B0}, // SP 0x1402B2EE0 MP 0x14037EE20
{"rotateto", 0x82B1}, // SP 0x1402B3030 MP 0x14037EF10
{"_meth_82b2", 0x82B2}, // SP 0x1402B3460 MP 0x14037F060
{"rotatepitch", 0x82B2}, // SP 0x1402B3460 MP 0x14037F060
{"rotateyaw", 0x82B3}, // SP 0x1402B3470 MP 0x14037F070
{"_meth_82b4", 0x82B4}, // SP 0x1402B3490 MP 0x14037F090 // looks similar to moveto/rotateto, wtf
{"rotateroll", 0x82B4}, // SP 0x1402B3490 MP 0x14037F090 // looks similar to moveto/rotateto, wtf
{"addpitch", 0x82B5}, // SP 0x1402B3410 MP 0x14037F010
{"addyaw", 0x82B6}, // SP 0x1402B3430 MP 0x14037F030
{"addoll", 0x82B7}, // SP 0x1402B3450 MP 0x14037F050
{"_meth_82b8", 0x82B8}, // SP 0x1402B34B0 MP 0x14037F0B0
{"addroll", 0x82B7}, // SP 0x1402B3450 MP 0x14037F050
{"vibrate", 0x82B8}, // SP 0x1402B34B0 MP 0x14037F0B0
{"rotatevelocity", 0x82B9}, // SP 0x1402B3700 MP 0x14037F3C0
{"solid", 0x82BA}, // SP 0x1402B45E0 MP 0x1403808A0
{"notsolid", 0x82BB}, // SP 0x1402B4690 MP 0x140380950
{"setcandamage", 0x82BC}, // SP 0x1402B3880 MP 0x14037F590
{"setcanradiusdamage", 0x82BD}, // SP 0x1402B38E0 MP 0x14037F5F0
{"physicslaunchclient", 0x82BE}, // SP 0x1402B3960 MP 0x14037F670
{"_meth_82bf", 0x82BF}, // SP 0x000000000 MP 0x1403351A0
{"_meth_82c0", 0x82C0}, // SP 0x000000000 MP 0x1403351B0
{"setcardicon", 0x82BF}, // SP 0x000000000 MP 0x1403351A0
{"setcardnameplate", 0x82C0}, // SP 0x000000000 MP 0x1403351B0
{"setcarddisplayslot", 0x82C1}, // SP 0x000000000 MP 0x1403351C0
{"regweaponforfxremoval", 0x82C2}, // SP 0x000000000 MP 0x1403352B0
{"laststandrevive", 0x82C3}, // SP 0x000000000 MP 0x140331E00
@ -1509,15 +1509,15 @@ namespace scripting
{"visionsetthermalforplayer", 0x82D4}, // SP 0x140263710 MP 0x14032FD20
{"visionsetpainforplayer", 0x82D5}, // SP 0x140263730 MP 0x14032FD40
{"setblurforplayer", 0x82D6}, // SP 0x140264890 MP 0x140330B80
{"_meth_82d7", 0x82D7}, // SP 0x140264C80 MP 0x140331310
{"_meth_82d8", 0x82D8}, // SP 0x140264C80 MP 0x140331330
{"_meth_82d9", 0x82D9}, // SP 0x1402ABE90 MP 0x000000000
{"getbuildnumber", 0x82DA}, // SP 0x1402663A0 MP 0x14032A910
{"_meth_82db", 0x82DB}, // SP 0x140266AF0 MP 0x14032AE90
{"_meth_82dc", 0x82DC}, // SP 0x140266CD0 MP 0x14032B120
{"getplayerweaponmodel", 0x82D7}, // SP 0x140264C80 MP 0x140331310
{"getplayerknifemodel", 0x82D8}, // SP 0x140264C80 MP 0x140331330
{"updateplayermodelwithweapons", 0x82D9}, // SP 0x1402ABE90 MP 0x000000000
{"notifyonplayercommand", 0x82DA}, // SP 0x1402663A0 MP 0x14032A910
{"canmantle", 0x82DB}, // SP 0x140266AF0 MP 0x14032AE90
{"forcemantle", 0x82DC}, // SP 0x140266CD0 MP 0x14032B120
{"ismantling", 0x82DD}, // SP 0x140266FE0 MP 0x14032B500
{"playfx", 0x82DE}, // SP 0x140267330 MP 0x14032B9F0
{"playerrecoilscaleon", 0x82DF}, // SP 0x140267530 MP 0x14032BD00
{"player_recoilscaleon", 0x82DF}, // SP 0x140267530 MP 0x14032BD00
{"player_recoilscaleoff", 0x82E0}, // SP 0x140267600 MP 0x14032BDD0
{"weaponlockstart", 0x82E1}, // SP 0x1402676E0 MP 0x14032C000
{"weaponlockfinalize", 0x82E2}, // SP 0x140260240 MP 0x14032C240

View File

@ -69,19 +69,40 @@ namespace scripting
return reinterpret_cast<script_function*>(method_table)[index - 0x8000];
}
unsigned int parse_token_id(const std::string& name)
{
if (name.starts_with("_ID"))
{
return static_cast<unsigned int>(std::strtol(name.substr(3).data(), nullptr, 10));
}
std::string find_token(unsigned int id)
if (name.starts_with("_id_"))
{
return static_cast<unsigned int>(std::strtol(name.substr(4).data(), nullptr, 16));
}
return 0;
}
}
std::vector<std::string> find_token(unsigned int id)
{
std::vector<std::string> results;
results.push_back(utils::string::va("_id_%X", id));
results.push_back(utils::string::va("_ID%i", id));
for (const auto& token : token_map)
{
if (token.second == id)
{
return token.first;
results.push_back(token.first);
break;
}
}
return utils::string::va("_ID%i", id);
return results;
}
unsigned int find_token_id(const std::string& name)
@ -93,7 +114,13 @@ namespace scripting
return result->second;
}
return 0;
const auto parsed_id = parse_token_id(name);
if (parsed_id)
{
return parsed_id;
}
return game::SL_GetCanonicalString(name.data());
}
script_function find_function(const std::string& name, const bool prefer_global)

View File

@ -9,7 +9,7 @@ namespace scripting
using script_function = void(*)(game::scr_entref_t);
std::string find_token(unsigned int id);
std::vector<std::string> find_token(unsigned int id);
unsigned int find_token_id(const std::string& name);
script_function find_function(const std::string& name, const bool prefer_global);

View File

@ -9,6 +9,7 @@
#include "../../../component/command.hpp"
#include "../../../component/logfile.hpp"
#include "../../../component/scripting.hpp"
#include "../../../component/fastfiles.hpp"
#include <utils/string.hpp>
#include <utils/io.hpp>
@ -17,7 +18,6 @@ namespace scripting::lua
{
namespace
{
vector normalize_vector(const vector& vec)
{
const auto length = sqrt(
@ -155,12 +155,52 @@ namespace scripting::lua
{
return normalize_vector(a);
};
vector_type["normalize"] = [](const vector& a)
{
return normalize_vector(a);
};
vector_type["toangles"] = [](const vector& a)
{
return call("vectortoangles", {a}).as<vector>();
};
vector_type["toyaw"] = [](const vector& a)
{
return call("vectortoyaw", {a}).as<vector>();
};
vector_type["tolerp"] = [](const vector& a)
{
return call("vectortolerp", {a}).as<vector>();
};
vector_type["toup"] = [](const vector& a)
{
return call("anglestoup", {a}).as<vector>();
};
vector_type["toright"] = [](const vector& a)
{
return call("anglestoright", {a}).as<vector>();
};
vector_type["toforward"] = [](const vector& a)
{
return call("anglestoforward", {a}).as<vector>();
};
}
void setup_entity_type(sol::state& state, event_handler& handler, scheduler& scheduler)
{
state["level"] = entity{*game::levelEntityId};
if (game::environment::is_sp())
{
state["player"] = call("getentbynum", {0}).as<entity>();
}
auto entity_type = state.new_usertype<entity>("entity");
for (const auto& func : method_map)
@ -462,6 +502,76 @@ namespace scripting::lua
return detour;
};
game_type["assetlist"] = [](const game&, const sol::this_state s, const std::string& type_string)
{
auto table = sol::table::create(s.lua_state());
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["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["getentbyref"] = [](const game&, const sol::this_state s,
const unsigned int entnum, const unsigned int classnum)
{
const auto id = ::game::Scr_GetEntityId(entnum, classnum);
if (id)
{
return convert(s, scripting::entity{id});
}
else
{
return sol::lua_value{s, sol::lua_nil};
}
};
}
}

View File

@ -4,6 +4,7 @@
#include "../execution.hpp"
#include "../../../component/logfile.hpp"
#include "../../../component/filesystem.hpp"
#include <utils/io.hpp>
@ -11,6 +12,8 @@ namespace scripting::lua::engine
{
namespace
{
bool running = false;
auto& get_scripts()
{
static std::vector<std::unique_ptr<context>> scripts{};
@ -38,21 +41,27 @@ namespace scripting::lua::engine
void stop()
{
running = false;
logfile::clear_callbacks();
get_scripts().clear();
}
void start()
{
// No SP until there is a concept
stop();
running = true;
for (const auto& path : filesystem::get_search_paths())
{
load_scripts(path + "/scripts/");
if (game::environment::is_sp())
{
return;
load_scripts(path + "/scripts/sp/");
}
else
{
load_scripts(path + "/scripts/mp/");
}
}
stop();
load_scripts("h1-mod/scripts/");
load_scripts("data/scripts/");
}
void notify(const event& e)
@ -70,4 +79,9 @@ namespace scripting::lua::engine
script->run_frame();
}
}
bool is_running()
{
return running;
}
}

View File

@ -8,4 +8,5 @@ namespace scripting::lua::engine
void stop();
void notify(const event& e);
void run_frame();
bool is_running();
}

View File

@ -165,56 +165,33 @@ namespace scripting::lua
auto table = sol::table::create(state);
auto metatable = sol::table::create(state);
const auto offset = 64000 * (parent_id & 3);
metatable[sol::meta_function::new_index] = [offset, parent_id](const sol::table t, const sol::this_state s,
metatable[sol::meta_function::new_index] = [parent_id](const sol::table t, const sol::this_state s,
const sol::lua_value& field, const sol::lua_value& value)
{
const auto id = field.is<std::string>()
? scripting::find_token_id(field.as<std::string>())
: field.as<int>();
if (!id)
const auto new_variable = convert({s, value});
if (field.is<unsigned int>())
{
return;
scripting::set_object_variable(parent_id, field.as<unsigned int>(), new_variable);
}
else if (field.is<std::string>())
{
scripting::set_object_variable(parent_id, field.as<std::string>(), new_variable);
}
const auto variable_id = game::GetVariable(parent_id, id);
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
const auto new_variable = convert({s, value}).get_raw();
game::AddRefToValue(new_variable.type, new_variable.u);
game::RemoveRefToValue(variable->type, variable->u.u);
variable->type = (char)new_variable.type;
variable->u.u = new_variable.u;
};
metatable[sol::meta_function::index] = [offset, parent_id](const sol::table t, const sol::this_state s,
metatable[sol::meta_function::index] = [parent_id](const sol::table t, const sol::this_state s,
const sol::lua_value& field)
{
const auto id = field.is<std::string>()
? scripting::find_token_id(field.as<std::string>())
: field.as<int>();
if (!id)
if (field.is<unsigned int>())
{
return sol::lua_value{s, sol::lua_nil};
return convert(s, scripting::get_object_variable(parent_id, field.as<unsigned int>()));
}
else if (field.is<std::string>())
{
return convert(s, scripting::get_object_variable(parent_id, field.as<std::string>()));
}
const auto variable_id = game::FindVariable(parent_id, id);
if (!variable_id)
{
return sol::lua_value{s, sol::lua_nil};
}
const auto variable = game::scr_VarGlob->childVariableValue[variable_id + offset];
game::VariableValue result{};
result.u = variable.u.u;
result.type = (game::scriptType_e)variable.type;
return convert(s, result);
};
table[sol::metatable_key] = metatable;

View File

@ -1175,7 +1175,12 @@ namespace game
const char* name;
char __pad0[0x118];
char textureCount;
char __pad1[7];
char constantCount;
char stateBitsCount;
char stateFlags;
char cameraRegion;
char materialType;
char assetFlags;
MaterialTechniqueSet* techniqueSet;
MaterialTextureDef* textureTable;
void* constantTable;
@ -1311,6 +1316,14 @@ namespace game
const char* buffer;
};
struct TTF
{
const char* name;
int len;
const char* buffer;
int fontFace;
};
struct GfxImageLoadDef
{
char levelCount;
@ -1382,6 +1395,7 @@ namespace game
StringTable* stringTable;
LuaFile* luaFile;
GfxImage* image;
TTF* ttf;
};
struct XAsset

View File

@ -33,18 +33,23 @@ namespace game
WEAK symbol<void()> Com_Frame_Try_Block_Function{0x1401CE8D0, 0x1400D8310};
WEAK symbol<CodPlayMode()> Com_GetCurrentCoDPlayMode{0, 0x1405039A0};
WEAK symbol<bool()> Com_InFrontEnd{0x1400E4B30, 0x140176A30};
WEAK symbol<bool()> Com_InFrontend{0x1400E4B30, 0x140176A30};
WEAK symbol<void(float, float, int)> Com_SetSlowMotion{0, 0x1400DB790};
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x1403509C0, 0x1400D78A0};
WEAK symbol<void()> Com_Quit_f{0x140352BE0, 0x1400DA830};
WEAK symbol<void(char const* finalMessage)> Com_Shutdown{0x140353B70, 0x1400DB8A0};
WEAK symbol<void()> Quit{0x140352D90, 0x1400DA830};
WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessage{0x1401389A0, 0x140220CC0};
WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessageBold{0x140138750, 0x140220620};
WEAK symbol<void(int localClientNum, /*mp::cg_s**/void* cg,
const char* dvar, const char* value)> CG_SetClientDvarFromServer{0, 0x140236120};
WEAK symbol<char*(const unsigned int weapon,
bool isAlternate, char* outputBuffer, int bufferLen)> CG_GetWeaponDisplayName{0x14016EC30, 0x1400B5840};
WEAK symbol<bool()> CL_IsCgameInitialized{0x14017EE30, 0x140245650};
WEAK symbol<void(int a1)> CL_VirtualLobbyShutdown{0, 0x140256D40};
WEAK symbol<void(int hash, const char* name, const char* buffer)> Dvar_SetCommand{0x1403C72B0, 0x1404FD0A0};
WEAK symbol<dvar_t*(const char* name)> Dvar_FindVar{0x1403C5D50, 0x1404FBB00};
@ -70,7 +75,7 @@ namespace game
WEAK symbol<void(const char* gameName)> FS_Startup{0x1403B85D0, 0x1404EDD30};
WEAK symbol<void(const char* path, const char* dir)> FS_AddLocalizedGameDirectory{0x1403B6030, 0x1404EBE20};
WEAK symbol<unsigned int(unsigned int, unsigned int)> GetVariable{0x14036FDD0, 0x1403F3730};
WEAK symbol<unsigned int(unsigned int, unsigned int)> GetVariable{0x14036FDD0, 0x14043DD70};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewVariable{0x14036FA00, 0x14043D990};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewArrayVariable{0x14036F880, 0x14043D810};
WEAK symbol<void()> GScr_LoadConsts{0x1402D13E0, 0x140393810};
@ -136,6 +141,7 @@ namespace game
WEAK symbol<void()> Scr_ClearOutParams{0x140374460, 0x140442510};
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x140372D50, 0x140440D80};
WEAK symbol<unsigned int(int classnum, unsigned int entnum)> Scr_GetEntityId{0x140372CA0, 0x140440CD0};
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x1402B9F60, 0x140385330};
WEAK symbol<ScreenPlacement* ()> ScrPlace_GetViewPlacement{0x1401981F0, 0x140288550};
@ -143,16 +149,22 @@ namespace game
DB_EnumXAssets_Internal{0x1401C9C10, 0x1402BA830};
WEAK symbol<const char*(const XAsset* asset)> DB_GetXAssetName{0x14019A390, 0x14028BE50};
WEAK symbol<int(XAssetType type)> DB_GetXAssetTypeSize{0x14019A3B0, 0x14028BE70};
WEAK symbol<XAssetHeader(XAssetType type, const char* name,
int createDefault)> DB_FindXAssetHeader{0x1401CA150, 0x1402BAC70};
WEAK symbol<void(int clientNum, const char* menu,
int a3, int a4, unsigned int a5)> LUI_OpenMenu{0x14039D5F0, 0x1404CD210};
WEAK symbol<bool(int clientNum, const char* name, hks::lua_State* s)> LUI_BeginEvent{0x1400D27F0, 0x140161A00};
WEAK symbol<void(hks::lua_State* s)> LUI_EndEvent{0x1400D3A80, 0x140162CD0};
WEAK symbol<void()> LUI_EnterCriticalSection{0x1400D3B70, 0x140162DC0};
WEAK symbol<void()> LUI_LeaveCriticalSection{0x1400D8DB0, 0x140168150};
WEAK symbol<bool(int clientNum, const char* menu)> Menu_IsMenuOpenAndVisible{0x1404709C0, 0x1404C7320};
WEAK symbol<scr_string_t(const char* str)> SL_FindString{0x14036D700, 0x14043B470};
WEAK symbol<scr_string_t(const char* str, unsigned int user)> SL_GetString{0x14036D9A0, 0x14043B840};
WEAK symbol<const char*(scr_string_t stringValue)> SL_ConvertToString{0x14036D420, 0x14043B170};
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x1402B9F60, 0x140385330};
WEAK symbol<unsigned int(const char* str)> SL_GetCanonicalString{0x14036A310, 0x140437EA0};
WEAK symbol<void(netadr_s* from)> SV_DirectConnect{0, 0x140480860};
WEAK symbol<void(int arg, char* buffer, int bufferLength)> SV_Cmd_ArgvBuffer{0x1403446C0, 0x140404CA0};
@ -266,5 +278,6 @@ namespace game
int internal_, int profilerTreatClosureAsFunc)> cclosure_Create{0x14008B5D0, 0x14011B540};
WEAK symbol<int(lua_State* s, int t)> hksi_luaL_ref{0x1400A64D0, 0x140136D30};
WEAK symbol<void(lua_State* s, int t, int ref)> hksi_luaL_unref{0x14009EF10, 0x14012F610};
WEAK symbol<void(lua_State* s, HksObject* lfp)> closePendingUpvalues{0x14008EA00, 0x14011E970};
}
}

View File

@ -37,17 +37,56 @@ namespace ui_scripting
return values;
}
bool notify(const std::string& name, const event_arguments& arguments)
{
const auto state = *game::hks::lua_state;
if (!state)
{
return false;
}
const auto _1 = gsl::finally(game::LUI_LeaveCriticalSection);
game::LUI_EnterCriticalSection();
try
{
const auto globals = table((*::game::hks::lua_state)->globals.v.table);
const auto engine = globals.get("Engine").as<table>();
const auto root = engine.get("GetLuiRoot").as<function>().call({})[0].as<userdata>();
const auto process_event = root.get("processEvent").as<function>();
table event{};
event.set("name", name);
for (const auto& arg : arguments)
{
event.set(arg.first, arg.second);
}
process_event.call({root, event});
return true;
}
catch (const std::exception& e)
{
printf("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;
state->m_apistack.top = state->m_apistack.base;
stack stack;
push_value(function);
for (auto i = arguments.begin(); i != arguments.end(); ++i)
{
push_value(*i);
}
const auto num_args = static_cast<int>(arguments.size());
stack.save(num_args + 1);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
@ -59,6 +98,7 @@ namespace ui_scripting
}
catch (const std::exception& e)
{
stack.fix();
throw std::runtime_error(std::string("Error executing script function: ") + e.what());
}
}
@ -66,9 +106,10 @@ namespace ui_scripting
script_value get_field(const userdata& self, const script_value& key)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
stack stack;
push_value(key);
stack.save(1);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
@ -85,16 +126,18 @@ namespace ui_scripting
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error getting userdata field: ") + e.what());
stack.fix();
throw std::runtime_error("Error getting userdata field: "s + e.what());
}
}
script_value get_field(const table& self, const script_value& key)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
stack stack;
push_value(key);
stack.save(1);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
@ -111,14 +154,17 @@ namespace ui_scripting
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error getting table field: ") + e.what());
stack.fix();
throw std::runtime_error("Error getting table field: "s + e.what());
}
}
void set_field(const userdata& self, const script_value& key, const script_value& value)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
stack stack;
stack.save(0);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
@ -133,14 +179,17 @@ namespace ui_scripting
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error setting userdata field: ") + e.what());
stack.fix();
throw std::runtime_error("Error setting userdata field: "s + e.what());
}
}
void set_field(const table& self, const script_value& key, const script_value& value)
{
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
stack stack;
stack.save(0);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
@ -155,7 +204,8 @@ namespace ui_scripting
}
catch (const std::exception& e)
{
throw std::runtime_error(std::string("Error setting table field: ") + e.what());
stack.fix();
throw std::runtime_error("Error setting table field: "s + e.what());
}
}
}

View File

@ -9,6 +9,8 @@ namespace ui_scripting
script_value get_return_value(int offset);
arguments get_return_values(int count);
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);

View File

@ -2,6 +2,7 @@
#include "context.hpp"
#include "error.hpp"
#include "value_conversion.hpp"
#include "../../scripting/execution.hpp"
#include "../script_value.hpp"
#include "../execution.hpp"
@ -10,6 +11,9 @@
#include "../../../component/updater.hpp"
#include "../../../component/fps.hpp"
#include "../../../component/localized_strings.hpp"
#include "../../../component/fastfiles.hpp"
#include "../../../component/scripting.hpp"
#include "../../../component/mods.hpp"
#include "component/game_console.hpp"
#include "component/scheduler.hpp"
@ -22,6 +26,31 @@ namespace ui_scripting::lua
{
namespace
{
const auto json_script = utils::nt::load_resource(LUA_JSON);
void setup_json(sol::state& state)
{
const auto json = state.safe_script(json_script, &sol::script_pass_on_error);
handle_error(json);
state["json"] = json;
}
void setup_io(sol::state& state)
{
state["io"]["fileexists"] = utils::io::file_exists;
state["io"]["writefile"] = utils::io::write_file;
state["io"]["movefile"] = utils::io::move_file;
state["io"]["filesize"] = utils::io::file_size;
state["io"]["createdirectory"] = utils::io::create_directory;
state["io"]["directoryexists"] = utils::io::directory_exists;
state["io"]["directoryisempty"] = utils::io::directory_is_empty;
state["io"]["listfiles"] = utils::io::list_files;
state["io"]["copyfolder"] = utils::io::copy_folder;
state["io"]["removefile"] = utils::io::remove_file;
state["io"]["removedirectory"] = utils::io::remove_directory;
state["io"]["readfile"] = static_cast<std::string(*)(const std::string&)>(utils::io::read_file);
}
void setup_types(sol::state& state, scheduler& scheduler)
{
struct game
@ -68,6 +97,78 @@ namespace ui_scripting::lua
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 sol::this_state s, const std::string& type_string)
{
auto table = sol::table::create(s.lua_state());
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["getweapondisplayname"] = [](const game&, const std::string& name)
{
const auto alternate = name.starts_with("alt_");
const auto weapon = ::game::G_GetWeaponForName(name.data());
char buffer[0x400] = {0};
::game::CG_GetWeaponDisplayName(weapon, alternate, buffer, 0x400);
return std::string(buffer);
};
game_type["getloadedmod"] = [](const game&)
{
return mods::mod_path;
};
auto userdata_type = state.new_usertype<userdata>("userdata_");
userdata_type["new"] = sol::property(
@ -193,6 +294,51 @@ namespace ui_scripting::lua
updater_table["getcurrentfile"] = updater::get_current_file;
state["updater"] = updater_table;
if (::game::environment::is_sp())
{
struct player
{
};
auto player_type = state.new_usertype<player>("player_");
state["player"] = player();
player_type["notify"] = [](const player&, const sol::this_state s, const std::string& name, sol::variadic_args va)
{
if (!::game::CL_IsCgameInitialized() || !::game::sp::g_entities[0].client)
{
throw std::runtime_error("Not in game");
}
const sol::state_view view{s};
const auto to_string = view["tostring"].get<sol::protected_function>();
std::vector<std::string> args{};
for (auto arg : va)
{
args.push_back(to_string.call(arg).get<std::string>());
}
::scheduler::once([s, name, args]()
{
try
{
std::vector<scripting::script_value> arguments{};
for (const auto& arg : args)
{
arguments.push_back(arg);
}
const auto player = scripting::call("getentbynum", {0}).as<scripting::entity>();
scripting::notify(player, name, arguments);
}
catch (...)
{
}
}, ::scheduler::pipeline::server);
};
}
}
}
@ -207,6 +353,8 @@ namespace ui_scripting::lua
sol::lib::math,
sol::lib::table);
setup_json(this->state_);
setup_io(this->state_);
setup_types(this->state_, this->scheduler_);
if (type == script_type::file)

View File

@ -3,6 +3,7 @@
#include "context.hpp"
#include "../../../component/ui_scripting.hpp"
#include "../../../component/filesystem.hpp"
#include <utils/io.hpp>
#include <utils/nt.hpp>
@ -52,8 +53,18 @@ namespace ui_scripting::lua::engine
load_code(lui_common);
load_code(lui_updater);
load_scripts("h1-mod/ui_scripts/");
load_scripts("data/ui_scripts/");
for (const auto& path : filesystem::get_search_paths())
{
load_scripts(path + "/ui_scripts/");
if (game::environment::is_sp())
{
load_scripts(path + "/ui_scripts/sp/");
}
else
{
load_scripts(path + "/ui_scripts/mp/");
}
}
}
void stop()

View File

@ -53,4 +53,5 @@ namespace ui_scripting
};
using arguments = std::vector<script_value>;
using event_arguments = std::unordered_map<std::string, script_value>;
}

View File

@ -273,4 +273,38 @@ namespace ui_scripting
{
return call_script_function(*this, arguments);
}
/***************************************************************
* Stack
**************************************************************/
stack::stack()
{
this->state = *game::hks::lua_state;
this->state->m_apistack.top = this->state->m_apistack.base;
}
void stack::save(int num_args)
{
this->num_args_ = num_args;
this->num_calls_ = state->m_numberOfCCalls;
this->base_bottom_ = state->m_apistack.base - state->m_apistack.bottom;
this->top_bottom_ = state->m_apistack.top - state->m_apistack.bottom;
this->callstack_ = state->m_callStack.m_current - state->m_callStack.m_records;
}
void stack::fix()
{
this->state->m_numberOfCCalls = this->num_calls_;
game::hks::closePendingUpvalues(this->state, &this->state->m_apistack.bottom[this->top_bottom_ - this->num_args_]);
this->state->m_callStack.m_current = &this->state->m_callStack.m_records[this->callstack_];
this->state->m_apistack.base = &this->state->m_apistack.bottom[this->base_bottom_];
this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ - static_cast<uint64_t>(this->num_args_ + 1)];
this->state->m_apistack.bottom[this->top_bottom_].t = this->state->m_apistack.top[-1].t;
this->state->m_apistack.bottom[this->top_bottom_].v.ptr = this->state->m_apistack.top[-1].v.ptr;
this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ + 1];
}
}

View File

@ -86,4 +86,28 @@ namespace ui_scripting
int ref{};
};
class stack final
{
public:
stack();
void save(int num_args);
void fix();
stack(stack&&) = delete;
stack(const stack&) = delete;
stack& operator=(stack&&) = delete;
stack& operator=(const stack&) = delete;
private:
game::hks::lua_State* state;
int num_args_;
int num_calls_;
uint64_t base_bottom_;
uint64_t top_bottom_;
uint64_t callstack_;
};
}

View File

@ -18,3 +18,5 @@
#define LUI_COMMON 309
#define LUI_UPDATER 310
#define LUA_JSON 311

View File

@ -121,6 +121,8 @@ ICON_IMAGE RCDATA "resources/icon.png"
LUI_COMMON RCDATA "resources/ui_scripts/common.lua"
LUI_UPDATER RCDATA "resources/ui_scripts/updater.lua"
LUA_JSON RCDATA "resources/json.lua"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,388 @@
--
-- json.lua
--
-- Copyright (c) 2020 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = { _version = "0.1.2" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\",
[ "\"" ] = "\"",
[ "\b" ] = "b",
[ "\f" ] = "f",
[ "\n" ] = "n",
[ "\r" ] = "r",
[ "\t" ] = "t",
}
local escape_char_map_inv = { [ "/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(1, 4), 16 )
local n2 = tonumber( s:sub(7, 10), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local res = ""
local j = i + 1
local k = j
while j <= #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1)
j = j + 1
local c = str:sub(j, j)
if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1)
or decode_error(str, j - 1, "invalid unicode escape in string")
res = res .. parse_unicode_escape(hex)
j = j + #hex
else
if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end
res = res .. escape_char_map_inv[c]
end
k = j + 1
elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1)
return res, j + 1
end
j = j + 1
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

File diff suppressed because one or more lines are too long

View File

@ -104,6 +104,11 @@ namespace utils::io
return std::filesystem::is_empty(directory);
}
bool remove_directory(const std::string& directory)
{
return std::filesystem::remove_all(directory);
}
std::vector<std::string> list_files(const std::string& directory)
{
std::vector<std::string> files;

View File

@ -16,6 +16,7 @@ namespace utils::io
bool create_directory(const std::string& directory);
bool directory_exists(const std::string& directory);
bool directory_is_empty(const std::string& directory);
bool remove_directory(const std::string& directory);
std::vector<std::string> list_files(const std::string& directory);
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
}