diff --git a/src/client/component/io.cpp b/src/client/component/io.cpp new file mode 100644 index 00000000..8d13d752 --- /dev/null +++ b/src/client/component/io.cpp @@ -0,0 +1,131 @@ +#include +#include "loader/component_loader.hpp" + +#include "console.hpp" +#include "gsc.hpp" +#include "logfile.hpp" +#include "scheduler.hpp" + +#include "game/dvars.hpp" +#include "game/game.hpp" + +#include "game/scripting/execution.hpp" + +#include +#include +#include + +namespace io +{ + namespace + { + void check_path(const std::filesystem::path& path) + { + if (path.generic_string().find("..") != std::string::npos) + { + throw std::runtime_error("io: directory traversal is not allowed"); + } + } + + std::string convert_path(const std::filesystem::path& path) + { + check_path(path); + static const auto fs_game = game::Dvar_FindVar("fs_game"); + + if (fs_game->current.string && fs_game->current.string != ""s) + { + const std::filesystem::path fs_game_path(fs_game->current.string); + return (fs_game_path / path).generic_string(); + } + + throw std::runtime_error("io: fs_game is not properly defined"); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + gsc::function::add("fileexists", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + game::Scr_AddBool(utils::io::file_exists(path)); + }); + + gsc::function::add("writefile", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + const auto data = gsc::get_argument(1).as(); + const auto params = game::Scr_GetNumParam(); + + auto append = false; + if (params > 2) + { + append = gsc::get_argument(2).as(); + } + + game::Scr_AddBool(utils::io::write_file(path, data, append)); + }); + + gsc::function::add("readfile", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + game::Scr_AddString(utils::io::read_file(path).data()); + }); + + gsc::function::add("filesize", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + game::Scr_AddInt(static_cast(utils::io::file_size(path))); + }); + + gsc::function::add("createdirectory", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + game::Scr_AddBool(utils::io::create_directory(path)); + }); + + gsc::function::add("directoryexists", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + game::Scr_AddBool(utils::io::directory_exists(path)); + }); + + gsc::function::add("directoryisempty", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + game::Scr_AddBool(utils::io::directory_is_empty(path)); + }); + + gsc::function::add("listfiles", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + const auto files = utils::io::list_files(path); + + scripting::array array{}; + for (const auto& file : files) + { + array.push(file); + } + + scripting::push_value(array); + }); + + gsc::function::add("copyfolder", []() + { + const auto source = convert_path(gsc::get_argument(0).as()); + const auto target = convert_path(gsc::get_argument(1).as()); + utils::io::copy_folder(source, target); + }); + + gsc::function::add("removefile", []() + { + const auto path = convert_path(gsc::get_argument(0).as()); + game::Scr_AddBool(utils::io::remove_file(path)); + }); + } + }; +} + +REGISTER_COMPONENT(io::component) diff --git a/src/client/game/scripting/execution.hpp b/src/client/game/scripting/execution.hpp index 9ec6f62f..fb223bd5 100644 --- a/src/client/game/scripting/execution.hpp +++ b/src/client/game/scripting/execution.hpp @@ -6,6 +6,8 @@ namespace scripting { + void push_value(const script_value& value); + script_value call_function(const std::string& name, const std::vector& arguments); script_value call_function(const std::string& name, const entity& entity, const std::vector& arguments);