Add mod stats funcs
This commit is contained in:
parent
c7f6864aea
commit
0225505661
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -46,3 +46,6 @@
|
||||
[submodule "deps/gsc-tool-h2"]
|
||||
path = deps/gsc-tool-h2
|
||||
url = https://github.com/fedddddd/gsc-tool-h2.git
|
||||
[submodule "deps/json"]
|
||||
path = deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
|
@ -39,24 +39,22 @@ LUI.addmenubutton("main_campaign", {
|
||||
})
|
||||
|
||||
function getmodname(path)
|
||||
local name = path
|
||||
game:addlocalizedstring(name, name)
|
||||
local desc = Engine.Localize("LUA_MENU_MOD_DESC_DEFAULT", name)
|
||||
local infofile = path .. "/info.json"
|
||||
local modinfo = mods.getinfo(path)
|
||||
|
||||
if (io.fileexists(infofile)) then
|
||||
pcall(function()
|
||||
local data = json.decode(io.readfile(infofile))
|
||||
game:addlocalizedstring(data.description, data.description)
|
||||
game:addlocalizedstring(data.author, data.author)
|
||||
game:addlocalizedstring(data.version, data.version)
|
||||
desc = Engine.Localize("@LUA_MENU_MOD_DESC",
|
||||
data.description, data.author, data.version)
|
||||
name = data.name
|
||||
end)
|
||||
if (not modinfo.isvalid) then
|
||||
game:addlocalizedstring(path, path)
|
||||
local desc = Engine.Localize("LUA_MENU_MOD_DESC_DEFAULT", path)
|
||||
|
||||
return path, desc
|
||||
else
|
||||
game:addlocalizedstring(modinfo.name, modinfo.name)
|
||||
game:addlocalizedstring(modinfo.description, modinfo.description)
|
||||
game:addlocalizedstring(modinfo.author, modinfo.author)
|
||||
game:addlocalizedstring(modinfo.version, modinfo.version)
|
||||
local desc = Engine.Localize("@LUA_MENU_MOD_DESC",
|
||||
modinfo.description, modinfo.author, modinfo.version)
|
||||
return modinfo.name, desc
|
||||
end
|
||||
|
||||
return name, desc
|
||||
end
|
||||
|
||||
LUI.MenuBuilder.registerType("mods_menu", function(a1)
|
||||
@ -69,13 +67,13 @@ LUI.MenuBuilder.registerType("mods_menu", function(a1)
|
||||
uppercase_title = true
|
||||
})
|
||||
|
||||
menu:AddButton("@LUA_MENU_WORKSHOP", function()
|
||||
--[[menu:AddButton("@LUA_MENU_WORKSHOP", function()
|
||||
if (LUI.MenuBuilder.m_types_build["mods_workshop_menu"]) then
|
||||
LUI.FlowManager.RequestAddMenu(nil, "mods_workshop_menu")
|
||||
end
|
||||
end, nil, true, nil, {
|
||||
desc_text = Engine.Localize("@LUA_MENU_WORKSHOP_DESC")
|
||||
})
|
||||
})--]]
|
||||
|
||||
local modfolder = game:getloadedmod()
|
||||
if (modfolder ~= "") then
|
||||
@ -91,21 +89,21 @@ LUI.MenuBuilder.registerType("mods_menu", function(a1)
|
||||
|
||||
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])
|
||||
local contentpresent = false
|
||||
|
||||
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
|
||||
local mods = mods.getlist()
|
||||
for i = 1, #mods do
|
||||
contentpresent = true
|
||||
|
||||
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
|
||||
|
||||
@ -116,7 +114,10 @@ LUI.MenuBuilder.registerType("mods_menu", function(a1)
|
||||
|
||||
LUI.Options.InitScrollingList(menu.list, nil)
|
||||
menu:CreateBottomDivider()
|
||||
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
|
||||
|
||||
if (contentpresent) then
|
||||
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
|
||||
end
|
||||
|
||||
return menu
|
||||
end)
|
||||
|
1
deps/json
vendored
Submodule
1
deps/json
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit b2306145e1789368e6f261680e8dc007e91cc986
|
17
deps/premake/json.lua
vendored
Normal file
17
deps/premake/json.lua
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
json = {
|
||||
source = path.join(dependencies.basePath, "json")
|
||||
}
|
||||
|
||||
function json.import()
|
||||
json.includes()
|
||||
end
|
||||
|
||||
function json.includes()
|
||||
includedirs {path.join(json.source, "single_include/*")}
|
||||
end
|
||||
|
||||
function json.project()
|
||||
|
||||
end
|
||||
|
||||
table.insert(dependencies, json)
|
@ -97,6 +97,23 @@ namespace filesystem
|
||||
static auto current_path = std::filesystem::current_path().string();
|
||||
return current_path.data();
|
||||
}
|
||||
|
||||
bool is_parent_path(const std::filesystem::path& parent, const std::filesystem::path& child)
|
||||
{
|
||||
std::filesystem::path iter = child;
|
||||
|
||||
while (iter != iter.parent_path())
|
||||
{
|
||||
if (iter == parent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
iter = iter.parent_path();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string read_file(const std::string& path)
|
||||
@ -230,6 +247,50 @@ namespace filesystem
|
||||
return paths;
|
||||
}
|
||||
|
||||
void check_path(const std::filesystem::path& path)
|
||||
{
|
||||
if (path.generic_string().find("..") != std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("directory traversal is not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_safe_path(const std::filesystem::path& path)
|
||||
{
|
||||
check_path(path);
|
||||
const auto absolute = std::filesystem::weakly_canonical(path);
|
||||
|
||||
static std::vector<std::filesystem::path> allowed_directories =
|
||||
{
|
||||
{std::filesystem::weakly_canonical("mods")},
|
||||
{std::filesystem::weakly_canonical("h2-mod")},
|
||||
{std::filesystem::weakly_canonical("players2/default")},
|
||||
};
|
||||
|
||||
auto is_allowed = false;
|
||||
for (const auto& dir : allowed_directories)
|
||||
{
|
||||
if (is_parent_path(dir, absolute))
|
||||
{
|
||||
is_allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_allowed)
|
||||
{
|
||||
throw std::runtime_error(std::format("Disallowed access to directory \"{}\"", path.generic_string()));
|
||||
}
|
||||
|
||||
return path.generic_string();
|
||||
}
|
||||
|
||||
bool safe_write_file(const std::string& file, const std::string& data, bool append)
|
||||
{
|
||||
const auto path = filesystem::get_safe_path(file);
|
||||
return utils::io::write_file(path, data, append);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
|
@ -14,4 +14,7 @@ namespace filesystem
|
||||
|
||||
std::vector<std::string> get_search_paths();
|
||||
std::vector<std::string> get_search_paths_rev();
|
||||
|
||||
std::string get_safe_path(const std::filesystem::path& path);
|
||||
bool safe_write_file(const std::string& file, const std::string& data, bool append = false);
|
||||
}
|
||||
|
@ -16,6 +16,9 @@
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#define MOD_FOLDER "mods"
|
||||
#define MOD_STATS_FOLDER "players2/modstats"
|
||||
|
||||
namespace mods
|
||||
{
|
||||
namespace
|
||||
@ -123,6 +126,97 @@ namespace mods
|
||||
mod_info.zone_info.zones.emplace_back(values[1], alloc_flags);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> get_mod_basename()
|
||||
{
|
||||
const auto mod = get_mod();
|
||||
if (!mod.has_value())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& value = mod.value();
|
||||
const auto last_index = value.find_last_of('/');
|
||||
const auto basename = value.substr(last_index + 1);
|
||||
return {basename};
|
||||
}
|
||||
|
||||
nlohmann::json default_mod_stats()
|
||||
{
|
||||
nlohmann::json json;
|
||||
json["maps"] = {};
|
||||
return json;
|
||||
}
|
||||
|
||||
nlohmann::json verify_mod_stats(nlohmann::json& json)
|
||||
{
|
||||
if (!json.is_object())
|
||||
{
|
||||
json = {};
|
||||
}
|
||||
|
||||
if (!json.contains("maps") || !json["maps"].is_object())
|
||||
{
|
||||
json["maps"] = {};
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
nlohmann::json parse_mod_stats()
|
||||
{
|
||||
const auto name = get_mod_basename();
|
||||
if (!name.has_value())
|
||||
{
|
||||
return default_mod_stats();
|
||||
}
|
||||
|
||||
const auto& name_value = name.value();
|
||||
const auto stat_file = MOD_STATS_FOLDER "/" + name_value + ".json";
|
||||
if (!utils::io::file_exists(stat_file))
|
||||
{
|
||||
return default_mod_stats();
|
||||
}
|
||||
|
||||
const auto data = utils::io::read_file(stat_file);
|
||||
try
|
||||
{
|
||||
auto json = nlohmann::json::parse(data);
|
||||
return verify_mod_stats(json);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("Failed to parse json mod stat file \"%s.json\": %s",
|
||||
name_value.data(), e.what());
|
||||
}
|
||||
|
||||
return default_mod_stats();
|
||||
}
|
||||
|
||||
void initialize_stats()
|
||||
{
|
||||
get_current_stats() = parse_mod_stats();
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json& get_current_stats()
|
||||
{
|
||||
static nlohmann::json stats;
|
||||
stats = verify_mod_stats(stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
void write_mod_stats()
|
||||
{
|
||||
const auto name = get_mod_basename();
|
||||
if (!name.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& name_value = name.value();
|
||||
const auto stat_file = MOD_STATS_FOLDER "/" + name_value + ".json";
|
||||
utils::io::write_file(stat_file, get_current_stats().dump(), false);
|
||||
}
|
||||
|
||||
bool mod_requires_restart(const std::string& path)
|
||||
@ -139,6 +233,8 @@ namespace mods
|
||||
filesystem::unregister_path(mod_info.path.value());
|
||||
}
|
||||
|
||||
write_mod_stats();
|
||||
initialize_stats();
|
||||
mod_info.path = path;
|
||||
filesystem::register_path(path);
|
||||
parse_mod_zones();
|
||||
@ -165,14 +261,63 @@ namespace mods
|
||||
return mod_info.path;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_mod_list()
|
||||
{
|
||||
if (!utils::io::directory_exists(MOD_FOLDER))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> mod_list;
|
||||
|
||||
const auto files = utils::io::list_files(MOD_FOLDER);
|
||||
for (const auto& file : files)
|
||||
{
|
||||
if (!utils::io::directory_exists(file) || utils::io::directory_is_empty(file))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mod_list.push_back(file);
|
||||
}
|
||||
|
||||
return mod_list;
|
||||
}
|
||||
|
||||
std::optional<nlohmann::json> get_mod_info(const std::string& name)
|
||||
{
|
||||
const auto info_file = name + "/info.json";
|
||||
if (!utils::io::directory_exists(name) || !utils::io::file_exists(info_file))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> info;
|
||||
const auto data = utils::io::read_file(info_file);
|
||||
try
|
||||
{
|
||||
return {nlohmann::json::parse(data)};
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!utils::io::directory_exists("mods"))
|
||||
if (!utils::io::directory_exists(MOD_FOLDER))
|
||||
{
|
||||
utils::io::create_directory("mods");
|
||||
utils::io::create_directory(MOD_FOLDER);
|
||||
}
|
||||
|
||||
if (!utils::io::directory_exists(MOD_STATS_FOLDER))
|
||||
{
|
||||
utils::io::create_directory(MOD_STATS_FOLDER);
|
||||
}
|
||||
|
||||
db_release_xassets_hook.create(0x140416A80, db_release_xassets_stub);
|
||||
|
@ -12,4 +12,10 @@ namespace mods
|
||||
void set_mod(const std::string& path);
|
||||
std::optional<std::string> get_mod();
|
||||
std::vector<mod_zone> get_mod_zones();
|
||||
|
||||
std::vector<std::string> get_mod_list();
|
||||
std::optional<nlohmann::json> get_mod_info(const std::string& mod);
|
||||
|
||||
nlohmann::json& get_current_stats();
|
||||
void write_mod_stats();
|
||||
}
|
@ -142,21 +142,106 @@ namespace ui_scripting
|
||||
}
|
||||
}
|
||||
|
||||
template <typename R>
|
||||
std::function<R(const std::string& str)>
|
||||
safe_io_func(const std::function<R(const std::string& str)>& func)
|
||||
{
|
||||
return [func](const std::string& path)
|
||||
{
|
||||
const auto safe_path = filesystem::get_safe_path(path);
|
||||
return func(safe_path);
|
||||
};
|
||||
}
|
||||
|
||||
script_value json_to_lua(const nlohmann::json& json)
|
||||
{
|
||||
if (json.is_object())
|
||||
{
|
||||
table object;
|
||||
for (const auto& [key, value] : json.items())
|
||||
{
|
||||
object[key] = json_to_lua(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (json.is_array())
|
||||
{
|
||||
table array;
|
||||
auto index = 1;
|
||||
for (const auto& value : json.array())
|
||||
{
|
||||
array[index++] = json_to_lua(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (json.is_boolean())
|
||||
{
|
||||
return json.get<bool>();
|
||||
}
|
||||
|
||||
if (json.is_number_integer())
|
||||
{
|
||||
return json.get<int>();
|
||||
}
|
||||
|
||||
if (json.is_number_float())
|
||||
{
|
||||
return json.get<float>();
|
||||
}
|
||||
|
||||
if (json.is_string())
|
||||
{
|
||||
return json.get<std::string>();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
nlohmann::json lua_to_json(const script_value& value)
|
||||
{
|
||||
if (value.is<bool>())
|
||||
{
|
||||
return value.as<bool>();
|
||||
}
|
||||
|
||||
if (value.is<int>())
|
||||
{
|
||||
return value.as<int>();
|
||||
}
|
||||
|
||||
if (value.is<float>())
|
||||
{
|
||||
return value.as<float>();
|
||||
}
|
||||
|
||||
if (value.is<std::string>())
|
||||
{
|
||||
return value.as<std::string>();
|
||||
}
|
||||
|
||||
if (value.get_raw().t == game::hks::TNIL)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
throw std::runtime_error("lua value must be of primitive type (boolean, integer, float, string)");
|
||||
}
|
||||
|
||||
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"]["removedirectory"] = utils::io::remove_directory;
|
||||
lua["io"]["readfile"] = static_cast<std::string(*)(const std::string&)>(utils::io::read_file);
|
||||
lua["io"]["fileexists"] = safe_io_func<bool>(utils::io::file_exists);
|
||||
lua["io"]["writefile"] = filesystem::safe_write_file;
|
||||
lua["io"]["filesize"] = safe_io_func<size_t>(utils::io::file_size);
|
||||
lua["io"]["createdirectory"] = safe_io_func<bool>(utils::io::create_directory);
|
||||
lua["io"]["directoryexists"] = safe_io_func<bool>(utils::io::directory_exists);
|
||||
lua["io"]["directoryisempty"] = safe_io_func<bool>(utils::io::directory_is_empty);
|
||||
lua["io"]["listfiles"] = safe_io_func<std::vector<std::string>>(utils::io::list_files);
|
||||
lua["io"]["removefile"] = safe_io_func<bool>(utils::io::remove_file);
|
||||
lua["io"]["removedirectory"] = safe_io_func<bool>(utils::io::remove_directory);
|
||||
lua["io"]["readfile"] = safe_io_func<std::string>(
|
||||
static_cast<std::string(*)(const std::string&)>(utils::io::read_file));
|
||||
|
||||
using game = table;
|
||||
auto game_type = game();
|
||||
@ -320,6 +405,85 @@ namespace ui_scripting
|
||||
|
||||
updater_table["getlasterror"] = updater::get_last_error;
|
||||
updater_table["getcurrentfile"] = updater::get_current_file;
|
||||
|
||||
auto mods_table = table();
|
||||
lua["mods"] = mods_table;
|
||||
|
||||
mods_table["getloaded"] = []() -> script_value
|
||||
{
|
||||
const auto& mod = mods::get_mod();
|
||||
if (mod.has_value())
|
||||
{
|
||||
return mod.value();
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
mods_table["getlist"] = mods::get_mod_list;
|
||||
mods_table["getinfo"] = [](const std::string& mod)
|
||||
{
|
||||
table info_table{};
|
||||
const auto info = mods::get_mod_info(mod);
|
||||
const auto has_value = info.has_value();
|
||||
info_table["isvalid"] = has_value;
|
||||
|
||||
if (!has_value)
|
||||
{
|
||||
return info_table;
|
||||
}
|
||||
|
||||
const auto& map = info.value();
|
||||
for (const auto& [key, value] : map.items())
|
||||
{
|
||||
info_table[key] = json_to_lua(value);
|
||||
}
|
||||
|
||||
return info_table;
|
||||
};
|
||||
|
||||
auto mods_stats_table = table();
|
||||
mods_table["stats"] = mods_stats_table;
|
||||
|
||||
mods_stats_table["set"] = [](const std::string& key, const script_value& value)
|
||||
{
|
||||
const auto json_value = lua_to_json(value);
|
||||
mods::get_current_stats()[key] = json_value;
|
||||
mods::write_mod_stats();
|
||||
};
|
||||
|
||||
mods_stats_table["get"] = [](const std::string& key)
|
||||
{
|
||||
return json_to_lua(mods::get_current_stats());
|
||||
};
|
||||
|
||||
mods_stats_table["mapset"] = [](const std::string& mapname,
|
||||
const std::string& key, const script_value& value)
|
||||
{
|
||||
const auto json_value = lua_to_json(value);
|
||||
auto& stats = mods::get_current_stats();
|
||||
stats["maps"][mapname][key] = json_value;
|
||||
mods::write_mod_stats();
|
||||
};
|
||||
|
||||
mods_stats_table["mapget"] = [](const std::string& mapname,
|
||||
const std::string& key)
|
||||
{
|
||||
auto& stats = mods::get_current_stats();
|
||||
return json_to_lua(stats["maps"][mapname][key]);
|
||||
};
|
||||
|
||||
mods_stats_table["save"] = mods::write_mod_stats;
|
||||
mods_stats_table["getall"] = []()
|
||||
{
|
||||
return json_to_lua(mods::get_current_stats());
|
||||
};
|
||||
|
||||
mods_stats_table["setfromjson"] = [](const std::string& data)
|
||||
{
|
||||
const auto json = nlohmann::json::parse(data);
|
||||
mods::get_current_stats() = json;
|
||||
};
|
||||
}
|
||||
|
||||
void start()
|
||||
@ -490,6 +654,11 @@ namespace ui_scripting
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int removed_function_stub(game::hks::lua_State* /*state*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
|
@ -79,6 +79,13 @@ namespace ui_scripting
|
||||
* Constructors
|
||||
**************************************************************/
|
||||
|
||||
script_value::script_value()
|
||||
{
|
||||
game::hks::HksObject nil{};
|
||||
nil.t = game::hks::TNIL;
|
||||
this->value_ = nil;
|
||||
}
|
||||
|
||||
script_value::script_value(const game::hks::HksObject& value)
|
||||
: value_(value)
|
||||
{
|
||||
|
@ -91,7 +91,7 @@ namespace ui_scripting
|
||||
class script_value
|
||||
{
|
||||
public:
|
||||
script_value() = default;
|
||||
script_value();
|
||||
script_value(const game::hks::HksObject& value);
|
||||
|
||||
script_value(int value);
|
||||
@ -136,6 +136,19 @@ namespace ui_scripting
|
||||
{
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
script_value(const std::optional<F> optional)
|
||||
{
|
||||
if (optional.has_value())
|
||||
{
|
||||
script_value::script_value(optional.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
script_value::script_value();
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const script_value& other) const;
|
||||
|
||||
arguments operator()() const;
|
||||
@ -227,6 +240,11 @@ namespace ui_scripting
|
||||
return args;
|
||||
}
|
||||
|
||||
operator script_value() const
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
operator T() const
|
||||
{
|
||||
|
@ -87,6 +87,11 @@
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4459)
|
||||
#include <json.hpp>
|
||||
#pragma warning(pop)
|
||||
|
||||
#include <asmjit/core/jitruntime.h>
|
||||
#include <asmjit/x86/x86assembler.h>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user