From 18659448d77658efbc8fb606fe7632cd350ca4b5 Mon Sep 17 00:00:00 2001 From: ineed bots Date: Thu, 30 Nov 2023 19:57:07 -0600 Subject: [PATCH] Add cod4x file io gsc funcs --- src/component/fileio.cpp | 405 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 src/component/fileio.cpp diff --git a/src/component/fileio.cpp b/src/component/fileio.cpp new file mode 100644 index 0000000..29dc8e9 --- /dev/null +++ b/src/component/fileio.cpp @@ -0,0 +1,405 @@ +#include +#include "loader/component_loader.hpp" +#include "gsc.hpp" +#include "scheduler.hpp" +#include +#include + +namespace fileio +{ + namespace + { + static constexpr size_t max_fhs = 10; + + enum class scr_fh_type_e + { + UNUSED, + READ, + WRITE, + APPEND + }; + + struct scr_fh_t + { + scr_fh_type_e type; + std::unique_ptr file_buff; + int file_length; + int seek; + std::filesystem::path full_path; + std::string base_path; + }; + + std::array scr_fhs = {}; + + bool validate_scr_path(const std::string& fpath) + { + if (fpath.empty()) + { + return false; + } + + constexpr static std::array bad_strings { R"(..)", R"(../)", R"(..\)" }; + for (auto i = 0u; i < bad_strings.size(); i++) + { + if (fpath.find(bad_strings[i]) != std::string::npos) + { + return false; + } + } + + return true; + } + + std::string build_base_path(const std::string& path) + { + if (!validate_scr_path(path)) + { + game::Scr_Error(utils::string::va("Invalid path: %s", path.c_str()), game::SCRIPTINSTANCE_SERVER, false); + } + + return "scriptdata/" + path; + } + + std::filesystem::path build_full_path(const std::string& path) + { + static game::dvar_s* fs_localAppData = nullptr; + static game::dvar_s* fs_gamedir = nullptr; + + if (!fs_localAppData) + { + fs_localAppData = game::Dvar_FindVar("fs_localAppData"); + } + + if (!fs_gamedir) + { + fs_gamedir = game::Dvar_FindVar("fs_gamedir"); + } + + return std::filesystem::path(fs_localAppData->current.string) / (*fs_gamedir->current.string ? fs_gamedir->current.string : "raw") / path; + } + + void free_scr_fh(scr_fh_t& scr_fh) + { +#ifdef DEBUG + printf("free_scr_fh: closing %s\n", scr_fh.base_path.c_str()); +#endif + + scr_fh = {}; + scr_fh.type = scr_fh_type_e::UNUSED; + } + + void close_all_scr_fh() + { + for (auto& fh : scr_fhs) + { + if (fh.type == scr_fh_type_e::UNUSED) + { + continue; + } + + free_scr_fh(fh); + } + } + + void fwrite_to_file(bool append_newline) + { + auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1; + + if (fh < 0 || fh >= max_fhs) + { + game::Scr_Error("fs_fwrite: invalid filehandle", game::SCRIPTINSTANCE_SERVER, false); + } + + if (scr_fhs[fh].type != scr_fh_type_e::WRITE && scr_fhs[fh].type != scr_fh_type_e::APPEND) + { + game::Scr_Error("File not opened for writing", game::SCRIPTINSTANCE_SERVER, false); + } + + std::string to_write = game::Scr_GetString(1, game::SCRIPTINSTANCE_SERVER); + if (append_newline) + { + to_write += '\n'; + } + + if (!utils::io::write_file(scr_fhs[fh].full_path.string(), to_write, true)) + { + game::Scr_Error("File write failed", game::SCRIPTINSTANCE_SERVER, false); + } + + game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1); + } + + void add_file_io() + { + scheduler::on_pre_scr_init_system([]([[maybe_unused]] game::scriptInstance_t inst) + { + if (inst != game::SCRIPTINSTANCE_SERVER) return; + + close_all_scr_fh(); + }); + + gsc::function::add("fs_testfile", []() + { + auto fpath = build_base_path(game::Scr_GetString(0, game::SCRIPTINSTANCE_SERVER)); + + auto fd = 0; + auto file_length = game::FS_FOpenFileRead(fpath.c_str(), &fd); + + if (!fd || file_length < 0) + { + game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 0); + return; + } + + game::FS_FCloseFile(fd); + game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1); + }); + + gsc::function::add("fs_fopen", []() + { + auto fpath = build_base_path(game::Scr_GetString(0, game::SCRIPTINSTANCE_SERVER)); + + // check for dupes + for (const auto& scr_fd : scr_fhs) + { + if (scr_fd.type != scr_fh_type_e::UNUSED && scr_fd.base_path == fpath) + { + game::Scr_Error("File already opened", game::SCRIPTINSTANCE_SERVER, false); + } + } + + // check for avail slot + auto i = 0; + for (; i < max_fhs; i++) + { + if (scr_fhs[i].type == scr_fh_type_e::UNUSED) + { + break; + } + } + + if (i >= max_fhs) + { + game::Scr_Error("Too many files opened", game::SCRIPTINSTANCE_SERVER, false); + } + + // check mode + auto mode = game::Scr_GetString(1, game::SCRIPTINSTANCE_SERVER); + if (mode == "read"s) + { + auto fd = 0; + auto file_length = game::FS_FOpenFileRead(fpath.c_str(), &fd); + + if (!fd || file_length < 0) + { + game::Scr_Error("Failed to open file", game::SCRIPTINSTANCE_SERVER, false); + } + + scr_fhs[i].file_buff = std::make_unique(file_length + 1); + auto bytes_read = game::FS_Read(scr_fhs[i].file_buff.get(), file_length, fd); + scr_fhs[i].file_buff[file_length] = '\0'; + game::FS_FCloseFile(fd); + + assert(bytes_read == file_length); + + if (bytes_read < 0) + { + scr_fhs[i].file_buff = {}; + game::Scr_Error("Failed to read file", game::SCRIPTINSTANCE_SERVER, false); + } + + scr_fhs[i].type = scr_fh_type_e::READ; + scr_fhs[i].file_length = bytes_read; + scr_fhs[i].seek = 0; + scr_fhs[i].base_path = fpath; + } + else if (mode == "write"s || mode == "append"s) + { + auto full_path = build_full_path(fpath); + + if (!utils::io::write_file(full_path.string(), "", (mode == "append"s))) + { + game::Scr_Error("Failed to open the file for writing", game::SCRIPTINSTANCE_SERVER, false); + } + + scr_fhs[i].type = scr_fh_type_e::WRITE; + scr_fhs[i].base_path = fpath; + scr_fhs[i].full_path = full_path; + } + else + { + game::Scr_Error(utils::string::va("Invalid mode: %s", mode), game::SCRIPTINSTANCE_SERVER, false); + } + +#ifdef DEBUG + printf("gscr_fs_fopen: opening %s, mode %s\n", fpath.c_str(), mode); +#endif + }); + + gsc::function::add("fs_write", []() + { + fwrite_to_file(false); + }); + + gsc::function::add("fs_writeline", []() + { + fwrite_to_file(true); + }); + + gsc::function::add("fs_readline", []() + { + auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1; + + if (fh < 0 || fh >= max_fhs) + { + game::Scr_Error("Invalid filehandle", game::SCRIPTINSTANCE_SERVER, false); + } + + if (scr_fhs[fh].type != scr_fh_type_e::READ) + { + game::Scr_Error("File not opened for reading", game::SCRIPTINSTANCE_SERVER, false); + } + + // file is completed being read + if (scr_fhs[fh].seek >= scr_fhs[fh].file_length) + { + game::Scr_AddUndefined(game::SCRIPTINSTANCE_SERVER); + return; + } + + // count how many bytes until the newline + auto bytes_to_read = 0; + auto found_nl = false; + + for (auto i = scr_fhs[fh].seek; i < scr_fhs[fh].file_length; bytes_to_read++, i++) + { + if (scr_fhs[fh].file_buff[i] == '\n') + { + bytes_to_read++; + found_nl = true; + break; + } + } + + if (bytes_to_read > 8191) + { + found_nl = false; + bytes_to_read = 8191; + + game::Com_PrintWarning(game::CON_CHANNEL_SCRIPT, "Line was too long in file %s, truncating\n", scr_fhs[fh].base_path.c_str()); + } + + auto scr_str = std::string(&scr_fhs[fh].file_buff[scr_fhs[fh].seek], bytes_to_read); + scr_fhs[fh].seek += bytes_to_read; + + // remove all '\r' + scr_str.erase(std::remove(scr_str.begin(), scr_str.end(), '\r'), scr_str.end()); + + // chop the newline char off + if (found_nl) + { + scr_str.pop_back(); + } + + game::Scr_AddString(game::SCRIPTINSTANCE_SERVER, scr_str.c_str()); + }); + + gsc::function::add("fs_read", []() + { + auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1; + + if (fh < 0 || fh >= max_fhs) + { + game::Scr_Error("Invalid filehandle", game::SCRIPTINSTANCE_SERVER, false); + } + + if (scr_fhs[fh].type != scr_fh_type_e::READ) + { + game::Scr_Error("File not opened for reading", game::SCRIPTINSTANCE_SERVER, false); + } + + // file is completed being read + if (scr_fhs[fh].seek >= scr_fhs[fh].file_length) + { + game::Scr_AddUndefined(game::SCRIPTINSTANCE_SERVER); + return; + } + + auto bytes_to_read = scr_fhs[fh].file_length - scr_fhs[fh].seek; + if (game::Scr_GetNumParam(game::SCRIPTINSTANCE_SERVER) >= 2) + { + bytes_to_read = std::clamp(game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 1), 0, bytes_to_read); + + if (bytes_to_read <= 0) + { + game::Scr_Error("Trying to read <1 bytes", game::SCRIPTINSTANCE_SERVER, false); + } + } + + if (bytes_to_read > 8191) + { + bytes_to_read = 8191; + + game::Com_PrintWarning(game::CON_CHANNEL_SCRIPT, "Line was too long in file %s, truncating\n", scr_fhs[fh].base_path.c_str()); + } + + auto scr_str = std::string(&scr_fhs[fh].file_buff[scr_fhs[fh].seek], bytes_to_read); + scr_fhs[fh].seek += bytes_to_read; + + game::Scr_AddString(game::SCRIPTINSTANCE_SERVER, scr_str.c_str()); + }); + + gsc::function::add("fs_fcloseall", []() + { + close_all_scr_fh(); + game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1); + }); + + gsc::function::add("fs_fclose", []() + { + auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1; + + if (fh < 0 || fh >= max_fhs) + { + game::Scr_Error("Invalid filehandle", game::SCRIPTINSTANCE_SERVER, false); + } + + if (scr_fhs[fh].type == scr_fh_type_e::UNUSED) + { + game::Scr_Error("File not opened", game::SCRIPTINSTANCE_SERVER, false); + } + + free_scr_fh(scr_fhs[fh]); + + game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1); + }); + + gsc::function::add("fs_remove", []() + { + auto fpath = build_base_path(game::Scr_GetString(0, game::SCRIPTINSTANCE_SERVER)); + auto full_path = build_full_path(fpath); + + if (!utils::io::remove_file(full_path.string())) + { + game::Scr_Error(utils::string::va("Failed to delete file: %s", fpath.c_str()), game::SCRIPTINSTANCE_SERVER, false); + } + + game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1); + }); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + add_file_io(); + } + + private: + }; +} + +REGISTER_COMPONENT(fileio::component) +