From 96e7e4949f759a2283fd41caacd6bb935547f9b5 Mon Sep 17 00:00:00 2001 From: bicarus-dev <202771338+bicarus-dev@users.noreply.github.com> Date: Fri, 26 Dec 2025 20:17:20 -0800 Subject: [PATCH] iidx, linux: cache lstat result for movie thumbnails (#474) ## Link to GitHub Issue, if one exists https://codeberg.org/nixac/spicetools/issues/4 ## Description of change Inspired by the Linux issue above in the fork, cache lstat calls for thumbnail. IIDX 32 and 33 make a call for this every frame which can cause significant overhead on Linux. This is being enabled for all versions and not just the Linux variant of the executables since it provides some marginal performance gain on Windows. ## Testing Tested IIDX 32/33, with the option on/off, and with AVS verbose logging on/off. Tested with ifs_layeredfs hook as well just to verify functionality. --- src/spice2x/hooks/avshook.cpp | 82 ++++++++++++++++++++++++++++++- src/spice2x/hooks/avshook.h | 1 + src/spice2x/launcher/launcher.cpp | 3 ++ src/spice2x/launcher/options.cpp | 8 +++ src/spice2x/launcher/options.h | 1 + 5 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/spice2x/hooks/avshook.cpp b/src/spice2x/hooks/avshook.cpp index 0fc1cdc..d58e618 100644 --- a/src/spice2x/hooks/avshook.cpp +++ b/src/spice2x/hooks/avshook.cpp @@ -1,7 +1,10 @@ #include "avshook.h" +#include +#include #include #include +#include #include "avs/core.h" #include "avs/ea3.h" @@ -20,8 +23,12 @@ static bool FAKE_FILE_OPEN = false; static std::map ROM_FILE_MAP; static std::string *ROM_FILE_CONTENTS = nullptr; +static std::mutex lstat_cache_mutex; +static robin_hood::unordered_map> lstat_cache; + namespace hooks::avs::config { bool DISABLE_VFS_DRIVE_REDIRECTION = false; + bool DISABLE_CACHE = false; bool LOG = false; }; @@ -79,6 +86,74 @@ static bool is_spam_file(const char *file) { return false; } +static bool is_ldj_cacheable_file(const char *file) { + static const char *spam_prefixes[] = { + // iidx33: profile background customization, spams on every frame during card entry + "/data/graphic/entry_card", + // iidx32+: movie thumbnails, spams on every frame during song select + "/data/graphic/thumbnail/", + "/data/graphic/movie_thumbnail/", + }; + + for (auto &spam : spam_prefixes) { + if (string_begins_with(file, spam)) { + return true; + } + } + return false; +} + +static int cached_avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) { + assert(name != nullptr); + assert(st != nullptr); + + const std::string filename(name); + int value = 0; + std::lock_guard lock(lstat_cache_mutex); + + // if this was already seen, return cached result + if (lstat_cache.contains(filename)) { + const auto &result = lstat_cache.at(filename); + value = result.first; + memcpy(st, &result.second, sizeof(*st)); + WRAP_DEBUG_FMT( + "(cached) file={}, a={}, m={}, c={}, size={}, u={}", + name, + st->st_atime, + st->st_mtime, + st->st_ctime, + st->filesize, + st->unk1); + return value; + } + + // if not, call the original avs function + // this returns 1 on success with valid values in st + // return 0 on failure, with filesize set to 0 + // maybe this could be used for future optimization + value = avs::core::avs_fs_lstat(name, st); + WRAP_DEBUG_FMT( + "file={} a={}, m={}, c={}, size={}, u={}", + name, + st->st_atime, + st->st_mtime, + st->st_ctime, + st->filesize, + st->unk1); + + // and cache it if there is room (prevent unconstrained growth) + if (lstat_cache.size() <= 4096) { + // c++ was a mistake + lstat_cache.emplace( + std::piecewise_construct, + std::forward_as_tuple(filename), + std::forward_as_tuple(value, *st)); + } + + // done + return value; +} + 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) { @@ -104,7 +179,12 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) { return 1; } - auto value = avs::core::avs_fs_lstat(name, st); + if (!hooks::avs::config::DISABLE_CACHE && (st != nullptr) && + avs::game::is_model("LDJ") && is_ldj_cacheable_file(name)) { + return cached_avs_fs_lstat(name, st); + } + + const auto value = avs::core::avs_fs_lstat(name, st); if (!is_spam_file(name)) { WRAP_DEBUG_FMT("name: {}", name); } diff --git a/src/spice2x/hooks/avshook.h b/src/spice2x/hooks/avshook.h index a0c83d3..de78a0c 100644 --- a/src/spice2x/hooks/avshook.h +++ b/src/spice2x/hooks/avshook.h @@ -9,6 +9,7 @@ namespace hooks::avs { namespace config { extern bool DISABLE_VFS_DRIVE_REDIRECTION; + extern bool DISABLE_CACHE; extern bool LOG; }; diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index 18d831e..e81f529 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -752,6 +752,9 @@ int main_implementation(int argc, char *argv[]) { if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) { hooks::avs::config::DISABLE_VFS_DRIVE_REDIRECTION = true; } + if (options[launcher::Options::DisableAvsCache].is_active()) { + hooks::avs::config::DISABLE_CACHE = true; + } if (options[launcher::Options::ScreenResizeConfigPath].is_active()) { cfg::SCREEN_RESIZE_CFG_PATH_OVERRIDE = options[launcher::Options::ScreenResizeConfigPath].value_text(); diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index f3e22e8..778e216 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -1766,6 +1766,14 @@ static const std::vector OPTION_DEFINITIONS = { .type = OptionType::Bool, .category = "Development", }, + { + // DisableAvsCache + .title = "Disable AVS Cache", + .name = "avscachedisable", + .desc = "Disable optimization used for some games to cache data file info (only for IIDX32+ for now)", + .type = OptionType::Bool, + .category = "Development", + }, { .title = "Output PEB", .name = "pebprint", diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index 26d130b..6bed3fd 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -196,6 +196,7 @@ namespace launcher { DisableSignalHandling, DisableDebugHooks, DisableAvsVfsDriveMountRedirection, + DisableAvsCache, OutputPEB, DumpSystemInfo, QKSArgs,