spicetools/hooks/avshook.cpp

294 lines
8.6 KiB
C++

#include "avshook.h"
#include <map>
#include <optional>
#include "avs/core.h"
#include "avs/ea3.h"
#include "avs/game.h"
#include "external/layeredfs/hook.h"
#include "util/detour.h"
#include "util/fileutils.h"
#include "util/logging.h"
#include "util/utils.h"
#include "external/layeredfs/hook.h"
#ifdef min
#undef min
#endif
static bool FAKE_FILE_OPEN = false;
static std::map<std::string, std::string> ROM_FILE_MAP;
static std::string *ROM_FILE_CONTENTS = nullptr;
namespace hooks::avs::config {
bool DISABLE_VFS_DRIVE_REDIRECTION = false;
bool LOG = false;
};
using namespace hooks::avs;
#define WRAP_DEBUG_FMT(format, ...) \
if (config::LOG) { \
log_misc("avshook", "{}: " format " = 0x{:x}", __FUNCTION__, __VA_ARGS__, static_cast<unsigned>(value)); \
}
#define AVS_HOOK(f) hook_function(::avs::core::IMPORT_NAMES.f, #f, &::avs::core::f, f)
template<typename T>
static void hook_function(const char *source_name, const char *target_name, T **source, T *target) {
if (!detour::trampoline_try(avs::core::DLL_NAME.c_str(), source_name, target, source)) {
log_warning("avshook", "could not hook {} ({})", target_name, source_name);
}
}
static inline bool is_fake_fd(avs::core::avs_file_t fd) {
return FAKE_FILE_OPEN && fd == 1337;
}
static bool is_dest_file(const char *name) {
static std::string path_dest = fmt::format("/dev/raw/{}.dest", avs::game::DEST[0]);
static std::string path_bin = fmt::format("/dev/raw/{}.bin", avs::game::DEST[0]);
return !_stricmp(name, path_dest.c_str()) || !_stricmp(name, path_bin.c_str());
}
static bool is_dest_file(const char *name, uint16_t mode) {
return mode == 1 && is_dest_file(name);
}
static bool is_dest_spec_ea3_config(const char *name) {
static std::string path = fmt::format("/prop/ea3-config-{}{}.xml", avs::game::DEST[0], avs::game::SPEC[0]);
return !_stricmp(name, path.c_str());
}
static bool is_spam_file(const char *file) {
static const char *spam_prefixes[] = {
"/mnt/bm2d/ngp",
"/afp",
"/dev/nvram/pm_eco.xml",
"/dev/nvram/pm_gamesys.xml",
"/dev/nvram/pm_clock.xml",
};
for (auto &spam : spam_prefixes) {
if (string_begins_with(file, spam)) {
return true;
}
}
return false;
}
static int avs_fs_fstat(avs::core::avs_file_t fd, struct avs::core::avs_stat *st) {
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
if (st) {
st->filesize = static_cast<uint32_t>(ROM_FILE_CONTENTS->length());
st->padding.st_dev = 0;
}
return 1;
}
return avs::core::avs_fs_fstat(fd, st);
}
static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
if (name == nullptr) {
return avs::core::avs_fs_lstat(name, st);
}
if (is_dest_file(name) || is_dest_spec_ea3_config(name)) {
if (st) {
st->filesize = 0;
st->padding.st_dev = 0;
}
return 1;
}
auto value = layeredfs::initialized
? layeredfs::hook_avs_fs_lstat(name, st) : avs::core::avs_fs_lstat(name, st);
if (!is_spam_file(name)) {
WRAP_DEBUG_FMT("name: {}", name);
}
return value;
}
static avs::core::avs_file_t avs_fs_open(const char *name, uint16_t mode, int flags) {
if (name == nullptr) {
return avs::core::avs_fs_open(name, mode, flags);
}
if (!FAKE_FILE_OPEN && (is_dest_file(name, mode) || ROM_FILE_MAP.contains(name))) {
FAKE_FILE_OPEN = true;
if (ROM_FILE_MAP.contains(name)) {
ROM_FILE_CONTENTS = &ROM_FILE_MAP.at(name);
}
log_info("avshook", "opening fake file '{}'", name);
return 1337;
}
auto value = layeredfs::initialized
? layeredfs::hook_avs_fs_open(name, mode, flags) : avs::core::avs_fs_open(name, mode, flags);
if (!is_spam_file(name)) {
WRAP_DEBUG_FMT("name: {} mode: {} flags: {}", name, mode, flags);
}
return value;
}
static void avs_fs_close(avs::core::avs_file_t fd) {
if (is_fake_fd(fd)) {
FAKE_FILE_OPEN = false;
ROM_FILE_CONTENTS = nullptr;
log_info("hooks::avs", "closing fake fd");
} else {
avs::core::avs_fs_close(fd);
}
}
static int avs_fs_copy(const char *sname, const char *dname) {
if (sname == nullptr || dname == nullptr) {
return avs::core::avs_fs_copy(sname, dname);
}
auto value = avs::core::avs_fs_copy(sname, dname);
WRAP_DEBUG_FMT("sname: {} dname {}", sname, dname);
return value;
}
static avs::core::avs_file_t avs_fs_opendir(const char *path) {
if (path == nullptr) {
return avs::core::avs_fs_opendir(path);
}
auto value = avs::core::avs_fs_opendir(path);
WRAP_DEBUG_FMT("path: {}", path);
return value;
}
static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *fstype, void *data) {
// avs redirection breaks ongaku paradise
if (mountpoint == nullptr ||
fsroot == nullptr ||
fstype == nullptr ||
avs::game::is_model("JC9")) {
return avs::core::avs_fs_mount(mountpoint, fsroot, fstype, data);
}
std::optional<std::string> new_fs_root = std::nullopt;
if (_stricmp(mountpoint, "/mnt/ea3-config.xml") == 0 && is_dest_spec_ea3_config(fsroot)) {
new_fs_root = fmt::format("/{}", avs::ea3::CFG_PATH);
}
// remap drive mounts to `dev/vfs/drive_x` where x is the drive letter
if (!config::DISABLE_VFS_DRIVE_REDIRECTION &&
(_strnicmp(fsroot, "d:", 2) == 0 ||
_strnicmp(fsroot, "e:", 2) == 0 ||
_strnicmp(fsroot, "f:", 2) == 0) &&
_stricmp(fstype, "fs") == 0)
{
// sub path is everything after the drive and colon characters
const char drive_letter[2] {
static_cast<char>(std::tolower(static_cast<unsigned char>(fsroot[0]))),
'\0',
};
const auto separator = fsroot[2] == '/' ? "" : "/";
const auto sub_path = &fsroot[2];
const std::filesystem::path mapped_path = fmt::format(
"dev/vfs/drive_{}{}{}",
drive_letter,
separator,
sub_path);
// create the mapped directory path
std::error_code err;
std::filesystem::create_directories(mapped_path, err);
if (err) {
log_warning("hooks::avs", "failed to create '{}': {}", mapped_path.string(), err.message());
} else {
// if this is the `e:\`, then create the special directories
if (drive_letter[0] == 'e' &&
(sub_path[0] == '/' || sub_path[0] == '\\') &&
sub_path[1] == '\0')
{
fileutils::dir_create_log("hooks::avs", mapped_path / "tmp");
fileutils::dir_create_log("hooks::avs", mapped_path / "up");
}
log_misc("hooks::avs", "source directory '{}' remapped to '{}'",
fsroot,
mapped_path.string());
}
new_fs_root = mapped_path.string();
}
auto fs_root_data = new_fs_root.has_value() ? new_fs_root->c_str() : fsroot;
auto value = avs::core::avs_fs_mount(mountpoint, fs_root_data, fstype, data);
WRAP_DEBUG_FMT("mountpoint: {}, fsroot: {}, fstype: {}", mountpoint, fs_root_data, fstype);
return value;
}
static size_t avs_fs_read(avs::core::avs_file_t fd, uint8_t *data, uint32_t data_size) {
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
const auto size = std::min(static_cast<size_t>(data_size), ROM_FILE_CONTENTS->length());
memcpy(data, ROM_FILE_CONTENTS->c_str(), size);
return size;
}
return avs::core::avs_fs_read(fd, data, data_size);
}
static int property_file_write(avs::core::property_ptr prop, const char* path) {
if (prop == nullptr || path == nullptr) {
return avs::core::property_file_write(prop, path);
}
// resort anthem dumps eacoin to /dev/nvram/eacoin AND /eacoin.xml
// it never reads /eacoin.xml, so it's probably a development leftover
if (avs::game::is_model("JDZ") && _stricmp(path, "/eacoin.xml") == 0) {
return 0;
}
auto value = avs::core::property_file_write(prop, path);
WRAP_DEBUG_FMT("path: {}", path);
return value;
}
void hooks::avs::init() {
log_info("hooks::avs", "initializing");
AVS_HOOK(avs_fs_fstat);
AVS_HOOK(avs_fs_lstat);
AVS_HOOK(avs_fs_open);
AVS_HOOK(avs_fs_close);
AVS_HOOK(avs_fs_copy);
AVS_HOOK(avs_fs_opendir);
AVS_HOOK(avs_fs_mount);
AVS_HOOK(avs_fs_read);
AVS_HOOK(property_file_write);
}
void hooks::avs::set_rom(const char *path, const char *contents) {
ROM_FILE_MAP.emplace(path, contents);
}