#include #include #include #include "game/game.hpp" #include "file_system.hpp" static utils::hook::detour sys_default_install_path_hook; static const game::native::dvar_t** fs_homepath; static const game::native::dvar_t** fs_debug; static FILE* file_for_handle(int f) { assert(!game::native::fsh[f].zipFile); assert(game::native::fsh[f].handleFiles.file.o); return game::native::fsh[f].handleFiles.file.o; } static unsigned int file_write(const void* ptr, unsigned int len, FILE* stream) { return std::fwrite(ptr, 1, len, stream); } static FILE* file_open_append_text(const char* filename) { errno = 0; auto* file = fopen(filename, "at"); if (file) { return file; } printf("Couldn't open file: %s %s\n", filename, strerror(errno)); return nullptr; } static FILE* file_open_write_binary(const char* filename) { errno = 0; auto* file = fopen(filename, "wb"); if (file) { return file; } printf("Couldn't open file: %s %s\n", filename, strerror(errno)); return nullptr; } static void replace_separators(char* path) { char* src, * dst; bool was_sep = false; for (src = path, dst = path; *src; ++src) { if (*src == '/' || *src == '\\') { if (!was_sep) { was_sep = true; *dst++ = '\\'; } } else { was_sep = false; *dst++ = *src; } } *dst = 0; } static void build_os_path_for_thread(const char* base, const char* game, const char* qpath, char* ospath, game::native::FsThread thread) { assert(base); assert(qpath); assert(ospath); if (!game) { game = ""; } else if (!game[0]) { game = game::native::fs_gamedir; } auto len_base = std::strlen(base); auto len_game = std::strlen(game); auto len_qpath = std::strlen(qpath); if (len_game + 1 + len_base + len_qpath + 1 >= 256) { if (thread) { *ospath = '\0'; return; } game::native::Com_Error(game::native::ERR_FATAL, "\x15" "FS_BuildOSPath: os path length exceeded\n"); } std::memcpy(ospath, base, len_base); ospath[len_base] = '/'; std::memcpy(&ospath[len_base + 1], game, len_game); ospath[len_base + 1 + len_game] = '/'; std::memcpy(ospath + len_base + 2 + len_game, qpath, len_qpath + 1); replace_separators(ospath); } static game::native::FsThread get_current_thread() { if (game::native::Sys_IsMainThread()) { return game::native::FS_THREAD_MAIN; } if (game::native::Sys_IsDatabaseThread()) { return game::native::FS_THREAD_DATABASE; } if (game::native::Sys_IsStreamThread()) { return game::native::FS_THREAD_STREAM; } if (game::native::Sys_IsRenderThread()) { return game::native::FS_THREAD_BACKEND; } if (game::native::Sys_IsServerThread()) { return game::native::FS_THREAD_SERVER; } return game::native::FS_THREAD_INVALID; } static int handle_for_file_current_thread() { return game::native::FS_HandleForFile(get_current_thread()); } static int open_file_append(const char* filename) { char ospath[MAX_PATH]{}; game::native::FS_CheckFileSystemStarted(); const auto* basepath = (*fs_homepath)->current.string; build_os_path_for_thread(basepath, game::native::fs_gamedir, filename, ospath, game::native::FS_THREAD_MAIN); if ((*fs_debug)->current.integer) { printf("FS_FOpenFileAppend: %s\n", ospath); } if (game::native::FS_CreatePath(ospath)) { return 0; } auto* f = file_open_append_text(ospath); if (!f) { return 0; } auto h = handle_for_file_current_thread(); game::native::fsh[h].zipFile = nullptr; strncpy_s(game::native::fsh[h].name, filename, _TRUNCATE); game::native::fsh[h].handleFiles.file.o = f; game::native::fsh[h].handleSync = 0; if (!game::native::fsh[h].handleFiles.file.o) { game::native::FS_FCloseFile(h); h = 0; } return h; } static int get_handle_and_open_file(const char* filename, const char* ospath, game::native::FsThread thread) { auto* fp = file_open_write_binary(ospath); if (!fp) { return 0; } const auto f = game::native::FS_HandleForFile(thread); game::native::fsh[f].zipFile = NULL; game::native::fsh[f].handleFiles.file.o = fp; strncpy_s(game::native::fsh[f].name, filename, _TRUNCATE); game::native::fsh[f].handleSync = 0; return f; } static int open_file_write_to_dir_for_thread(const char* filename, const char* dir, const char* osbasepath, game::native::FsThread thread) { char ospath[MAX_PATH]{}; game::native::FS_CheckFileSystemStarted(); const char* basepath = (*fs_homepath)->current.string; build_os_path_for_thread(basepath, dir, filename, ospath, game::native::FS_THREAD_MAIN); if ((*fs_debug)->current.integer) { printf("FS_FOpenFileWriteToDirForThread: %s\n", ospath); } if (game::native::FS_CreatePath(ospath)) { return 0; } return get_handle_and_open_file(filename, ospath, thread); } static int open_file_write(const char* filename) { return open_file_write_to_dir_for_thread(filename, game::native::fs_gamedir, "", game::native::FS_THREAD_MAIN); } static const char* sys_default_install_path_stub() { static auto current_path = std::filesystem::current_path().string(); return current_path.data(); } int file_system::open_file_by_mode(const char* qpath, int* f, game::native::fsMode_t mode) { auto r = 6969; auto sync = 0; switch (mode) { case game::native::FS_READ: *game::native::com_fileAccessed = TRUE; r = game::native::FS_FOpenFileReadForThread(qpath, f, game::native::FS_THREAD_MAIN); break; case game::native::FS_WRITE: *f = open_file_write(qpath); r = 0; if (!*f) { r = -1; } break; case game::native::FS_APPEND_SYNC: sync = 1; case game::native::FS_APPEND: *f = open_file_append(qpath); r = 0; if (!*f ) { r = -1; } break; default: game::native::Com_Error(game::native::ERR_FATAL, "\x15" "FSH_FOpenFile: bad mode"); break; } if (!f) { return r; } if (*f) { game::native::fsh[*f].fileSize = r; game::native::fsh[*f].streamed = 0; } game::native::fsh[*f].handleSync = sync; return r; } int file_system::write(const char* buffer, int len, int h) { game::native::FS_CheckFileSystemStarted(); if (!h) { return 0; } auto* f = file_for_handle(h); auto* buf = const_cast(buffer); auto remaining = len; auto tries = 0; while (remaining) { auto block = remaining; auto written = static_cast(file_write(buf, block, f)); if (!written) { if (tries) { return 0; } tries = 1; } if (written == -1) { return 0; } remaining -= written; buf += written; } if (game::native::fsh[h].handleSync) { std::fflush(f); } return len; } void file_system::post_load() { fs_homepath = reinterpret_cast( SELECT_VALUE(0x1C2B538, 0x59ADD18, 0x585E8F0)); fs_debug = reinterpret_cast( SELECT_VALUE(0x1C2B32C, 0x59A9A08, 0x585A5E4)); // Make open-iw5 work outside of the game directory sys_default_install_path_hook.create(SELECT_VALUE(0x487E50, 0x5C4A80, 0x535F80), &sys_default_install_path_stub); // fs_basegame utils::hook::set(SELECT_VALUE(0x629031, 0x5B0FD1, 0x526F5C), "userraw"); } REGISTER_MODULE(file_system)