mirror of
https://github.com/JezuzLizard/T4SP-Server-Plugin.git
synced 2025-04-19 13:12:53 +00:00
434 lines
11 KiB
C++
434 lines
11 KiB
C++
#include <stdinc.hpp>
|
|
#include "loader/component_loader.hpp"
|
|
#include "gsc.hpp"
|
|
#include "scheduler.hpp"
|
|
#include <utils/memory.hpp>
|
|
#include <utils/string.hpp>
|
|
|
|
namespace fileio
|
|
{
|
|
namespace
|
|
{
|
|
static constexpr size_t max_fhs = 10;
|
|
static constexpr size_t max_gsc_string = 0x10000 - 1;
|
|
|
|
enum class scr_fh_type_e
|
|
{
|
|
UNUSED,
|
|
READ,
|
|
WRITE,
|
|
APPEND
|
|
};
|
|
|
|
struct scr_fh_t
|
|
{
|
|
scr_fh_type_e type;
|
|
std::unique_ptr<char[]> file_buff;
|
|
int file_length;
|
|
int seek;
|
|
std::filesystem::path full_path;
|
|
std::string base_path;
|
|
};
|
|
|
|
std::array<scr_fh_t, max_fhs> scr_fhs = {};
|
|
|
|
bool validate_scr_path(const std::string& fpath)
|
|
{
|
|
auto toks = utils::string::split(fpath, '/');
|
|
|
|
for (const auto& tok : toks)
|
|
{
|
|
if (tok == "." || tok == "..")
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (tok.find(":") != std::string::npos)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string build_base_path(const std::string& path_)
|
|
{
|
|
auto path = path_;
|
|
std::replace(path.begin(), path.end(), '\\', '/');
|
|
|
|
if (!validate_scr_path(path))
|
|
{
|
|
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, utils::string::va("Invalid path: %s", path_.c_str()));
|
|
}
|
|
|
|
return path.starts_with("scriptdata/") ? path : "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_game");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
int scr_get_fh()
|
|
{
|
|
auto fh = game::Scr_GetInt(game::SCRIPTINSTANCE_SERVER, 0) - 1;
|
|
|
|
if (fh < 0 || fh >= max_fhs)
|
|
{
|
|
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "fs_fwrite: invalid filehandle");
|
|
}
|
|
|
|
return fh;
|
|
}
|
|
|
|
void fwrite_to_file(bool append_newline)
|
|
{
|
|
auto fh = scr_get_fh();
|
|
|
|
if (scr_fhs[fh].type != scr_fh_type_e::WRITE && scr_fhs[fh].type != scr_fh_type_e::APPEND)
|
|
{
|
|
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened for writing");
|
|
}
|
|
|
|
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::Com_PrintWarning(game::CON_CHANNEL_SCRIPT, "Failed to write file: %s\n", scr_fhs[fh].base_path.c_str());
|
|
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 0);
|
|
return;
|
|
}
|
|
|
|
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_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File already opened");
|
|
}
|
|
}
|
|
|
|
// 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_ParamError(0, game::SCRIPTINSTANCE_SERVER, "Too many files opened");
|
|
}
|
|
|
|
// 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::Com_PrintWarning(game::CON_CHANNEL_SCRIPT, "Failed to open file for reading: %s\n", fpath.c_str());
|
|
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 0);
|
|
return;
|
|
}
|
|
|
|
scr_fhs[i].file_buff = std::make_unique<char[]>(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::Com_PrintWarning(game::CON_CHANNEL_SCRIPT, "Failed to read file: %s\n", fpath.c_str());
|
|
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 0);
|
|
return;
|
|
}
|
|
|
|
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::Com_PrintWarning(game::CON_CHANNEL_SCRIPT, "Failed to open file for writing: %s\n", fpath.c_str());
|
|
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 0);
|
|
return;
|
|
}
|
|
|
|
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_ParamError(1, game::SCRIPTINSTANCE_SERVER, utils::string::va("Invalid mode: %s", mode));
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
printf("gscr_fs_fopen: opening %s, mode %s\n", fpath.c_str(), mode);
|
|
#endif
|
|
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, i + 1);
|
|
});
|
|
|
|
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 = scr_get_fh();
|
|
|
|
if (scr_fhs[fh].type != scr_fh_type_e::READ)
|
|
{
|
|
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened for reading");
|
|
}
|
|
|
|
// 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 > max_gsc_string)
|
|
{
|
|
found_nl = false;
|
|
bytes_to_read = max_gsc_string;
|
|
|
|
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 = scr_get_fh();
|
|
|
|
if (scr_fhs[fh].type != scr_fh_type_e::READ)
|
|
{
|
|
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened for reading");
|
|
}
|
|
|
|
// 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_ParamError(1, game::SCRIPTINSTANCE_SERVER, "Trying to read <1 bytes");
|
|
}
|
|
}
|
|
|
|
if (bytes_to_read > max_gsc_string)
|
|
{
|
|
bytes_to_read = max_gsc_string;
|
|
|
|
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 = scr_get_fh();
|
|
|
|
if (scr_fhs[fh].type == scr_fh_type_e::UNUSED)
|
|
{
|
|
game::Scr_ParamError(0, game::SCRIPTINSTANCE_SERVER, "File not opened");
|
|
}
|
|
|
|
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::Com_PrintWarning(game::CON_CHANNEL_SCRIPT, "Failed to delete file: %s\n", fpath.c_str());
|
|
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 0);
|
|
return;
|
|
}
|
|
|
|
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 1);
|
|
});
|
|
|
|
gsc::function::add("fs_listfiles", []()
|
|
{
|
|
auto fpath = build_base_path(game::Scr_GetString(0, game::SCRIPTINSTANCE_SERVER));
|
|
|
|
int numfiles;
|
|
auto* files = game::FS_ListFiles(fpath.c_str(), "", game::FS_LIST_ALL, &numfiles);
|
|
|
|
game::Scr_MakeArray(game::SCRIPTINSTANCE_SERVER);
|
|
for (int i = 0; i < numfiles; i++)
|
|
{
|
|
game::Scr_AddString(game::SCRIPTINSTANCE_SERVER, files[i]);
|
|
game::Scr_AddArray(game::SCRIPTINSTANCE_SERVER);
|
|
}
|
|
|
|
game::FS_FreeFileList(files);
|
|
});
|
|
}
|
|
}
|
|
|
|
class component final : public component_interface
|
|
{
|
|
public:
|
|
void post_unpack() override
|
|
{
|
|
add_file_io();
|
|
}
|
|
|
|
private:
|
|
};
|
|
}
|
|
|
|
REGISTER_COMPONENT(fileio::component)
|
|
|