From 3f49f60cecd02fa49118cebe0f48d3f125fd3ead Mon Sep 17 00:00:00 2001 From: Roman Tarasov Date: Tue, 18 Aug 2026 21:57:00 +0300 Subject: [PATCH] Add cached emulator updates --- README.md | 11 +- config.ini.example | 13 + include/lang/ar_sa.inc | 2 + include/lang/cs_cz.inc | 2 + include/lang/da_dk.inc | 2 + include/lang/de_de.inc | 2 + include/lang/el_gr.inc | 2 + include/lang/en_gb.inc | 2 + include/lang/en_us.inc | 2 + include/lang/es_es.inc | 2 + include/lang/es_mx.inc | 2 + include/lang/fi_fi.inc | 2 + include/lang/fr_ca.inc | 2 + include/lang/fr_fr.inc | 2 + include/lang/hu_hu.inc | 2 + include/lang/id_id.inc | 2 + include/lang/it_it.inc | 2 + include/lang/ja_jp.inc | 2 + include/lang/ko_kr.inc | 2 + include/lang/nl_nl.inc | 2 + include/lang/no_no.inc | 2 + include/lang/pl_pl.inc | 2 + include/lang/pt_br.inc | 2 + include/lang/pt_pt.inc | 2 + include/lang/ro_ro.inc | 2 + include/lang/ru_ru.inc | 2 + include/lang/sv_se.inc | 2 + include/lang/th_th.inc | 2 + include/lang/tr_tr.inc | 2 + include/lang/vi_vn.inc | 2 + include/lang/zh_cn.inc | 2 + include/lang/zh_tw.inc | 2 + include/sm_fakelib.h | 5 + include/sm_filesystem.h | 5 + include/sm_l10n.h | 1 + include/sm_paths.h | 2 + include/sm_scan.h | 6 + include/sm_types.h | 2 + src/sm_config_mount.c | 69 ++- src/sm_fakelib.c | 859 +++++++++++++++++++++++++++++++++++-- src/sm_filesystem.c | 74 +++- src/sm_scan.c | 9 +- src/sm_scanner.c | 23 +- src/sm_shellcore_service.c | 4 + 44 files changed, 1077 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index d3b3dc2..01b8470 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ Supported keys (all optional): - `legacy_mount_pfsc=1|0` (outer `.ffpfsc`; `1` selects the version 1.6 mount profile; default: `1`) - `legacy_gddr5_cache=1|0` (request the PFS compressed-offset cache for nested images in legacy mode; default: `0`; optimized mode always requests it) - `backport_fakelib=1|0` (`1` mounts sandbox `fakelib` overlays for running games; default: `1`) +- `update_emulators=1|0` (`1` updates all emulators with matching files in a game's own fakelib; default: `1`) +- `emulators_path=` (folder containing emulator update files; default: `/data/shadowmount/emus`) - `global_fakelib=1|0` (`1` enables the global fakelib overlay when the folder exists; default: `1`) - `global_fakelib_path=` (global fakelib folder; default: `/data/shadowmount/fakelib`) - `global_fakelib_priority=game|global` (overlay priority when both global and game fakelib exist; default: `game`) @@ -183,12 +185,17 @@ Backport overlay behavior: - The `backports` folder is ignored during normal game scanning. - A backport is applied automatically to the matching mounted game from any configured scan path. - If multiple scan paths provide the same title backport, the game's own scan path wins; otherwise scan path order is used. -- If `/mnt/sandbox/_XXX/app0/fakelib2` exists while the game is running, ShadowMount+ mounts it into that game's sandbox `common/lib`; otherwise it falls back to `app0/fakelib`. +- ShadowMount+ checks the selected backport for `fakelib2` and then `fakelib`; if neither exists, it uses only `fakelib` from the original game source. The selected directory is mounted into the running game's sandbox `common/lib`. +- If `update_emulators=1` and at least one file from `emulators_path` matches a file in the selected game fakelib, ShadowMount+ prepares `/data/shadowmount/cache//fakelib` before mounting the title runtime. It copies the selected source fakelib into a temporary cache and replaces every matching emulator file. +- Cache entries are rebuilt when the source path, file set, size, or timestamps of the source fakelib or matching emulator update files change. Their seven-day lifetime is refreshed whenever the game launches. +- Cache cleanup is independent of game preparation: it runs once when the scanner loop starts and then once every 24 hours while no game mount is active. It removes invalid entries, orphaned temporary directories, and caches unused for more than seven days; mounted caches are always preserved. +- The cache root and every generated cache directory, file, and manifest are normalized to mode `0777`. +- After the game starts, the prepared cached fakelib is mounted into `common/lib` via `unionfs`. The backport notification adds `Emulators updated` when at least one emulator update file was copied into the active cache. - If `global_fakelib=1` and `global_fakelib_path` exists as a directory, ShadowMount+ also mounts that folder into the same sandbox `common/lib`. - When both global and per-game fakelib exist, the default gives the game's own `fakelib` priority by mounting `/data/shadowmount/fakelib` first and then the game-specific fakelib. - `global_fakelib_priority=global` reverses that priority. - Use repeatable `global_fakelib_exclude=` entries to skip the global fakelib for specific games without disabling per-game fakelib. -- `backport_fakelib=0` disables the sandbox `fakelib` watcher, including global fakelib. +- `backport_fakelib=0` disables the sandbox `fakelib` watcher, including global fakelib and emulator updates. - For `backport_fakelib` to work correctly, the standalone `BackPork` payload must be disabled. Running both at the same time will conflict. Kstuff game lifecycle behavior: diff --git a/config.ini.example b/config.ini.example index 9276c69..bb8c24c 100644 --- a/config.ini.example +++ b/config.ini.example @@ -131,6 +131,19 @@ # Default: 1 # backport_fakelib=1 +# Updates for multiple emulators in the game fakelib (cache is created only when +# at least one regular file name matches): +# 1/true/yes/on -> cache the game's fakelib before launch and replace matching +# cached files from emulators_path +# 0/false/no/off -> disable emulator updates +# Default: 1 +# update_emulators=1 +# +# Folder containing emulator update files (absolute path): +# emulators_path=/data/shadowmount/emus +# Cache: /data/shadowmount/cache//fakelib +# Unused caches expire seven days after the most recent game launch. + # Global fakelib overlay: # 1/true/yes/on -> also mount global fakelib for every tracked game when present # 0/false/no/off -> do not use the global fakelib folder diff --git a/include/lang/ar_sa.inc b/include/lang/ar_sa.inc index bd68ad6..9407bfa 100644 --- a/include/lang/ar_sa.inc +++ b/include/lang/ar_sa.inc @@ -11,6 +11,8 @@ static const char *const g_ar_sa[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "فشل تركيب طبقة التوافق مع الإصدارات الأقدم: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "طُبّق دعم الإصدار الأقدم على اللعبة: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "طُبّق دعم الإصدار الأقدم على اللعبة: %s\nتم تحديث المحاكيات", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "تم رفض تحميل الصورة:\n%s\nحجم الكتلة صغير جداً (%llu < %u).\nتم حفظ قيمة حجم القطاع الجديدة: %u.\nحاول التحميل مرة أخرى.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/cs_cz.inc b/include/lang/cs_cz.inc index ae3ff14..4308ed5 100644 --- a/include/lang/cs_cz.inc +++ b/include/lang/cs_cz.inc @@ -11,6 +11,8 @@ static const char *const g_cs_cz[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay selhal: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport hry použit: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport hry použit: %s\nEmulátory aktualizovány", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Připojení obrazu odmítnuto:\n%s\nVelikost clusteru je příliš malá (%llu < %u).\nUloženo přepsání image_sector: %u.\nZkuste připojit znovu.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/da_dk.inc b/include/lang/da_dk.inc index aa83721..a5fda2b 100644 --- a/include/lang/da_dk.inc +++ b/include/lang/da_dk.inc @@ -11,6 +11,8 @@ static const char *const g_da_dk[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay mislykkedes: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Spil backportet: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Spil backportet: %s\nEmulatorer opdateret", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montering af image afvist:\n%s\nKlyngestørrelsen er for lille (%llu < %u).\nimage_sector-tilsidesættelse gemt: %u.\nPrøv at montere igen.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/de_de.inc b/include/lang/de_de.inc index 67ebab2..88093d7 100644 --- a/include/lang/de_de.inc +++ b/include/lang/de_de.inc @@ -11,6 +11,8 @@ static const char *const g_de_de[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-Overlay fehlgeschlagen: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Spiel backportiert: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Spiel backportiert: %s\nEmulatoren aktualisiert", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Einbindung des Images abgelehnt:\n%s\nDie Clustergröße ist zu klein (%llu < %u).\nimage_sector-Überschreibung gespeichert: %u.\nVersuche es erneut.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/el_gr.inc b/include/lang/el_gr.inc index 85ec40d..2c1adbd 100644 --- a/include/lang/el_gr.inc +++ b/include/lang/el_gr.inc @@ -11,6 +11,8 @@ static const char *const g_el_gr[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Αποτυχία backport overlay: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Εφαρμόστηκε backport στο παιχνίδι: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Εφαρμόστηκε backport στο παιχνίδι: %s\nΟι εξομοιωτές ενημερώθηκαν", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Η προσάρτηση του ειδώλου απορρίφθηκε:\n%s\nΤο μέγεθος συστοιχίας είναι πολύ μικρό (%llu < %u).\nΑποθηκεύτηκε η τιμή image_sector: %u.\nΔοκιμάστε ξανά την προσάρτηση.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/en_gb.inc b/include/lang/en_gb.inc index a23b5f8..e55105f 100644 --- a/include/lang/en_gb.inc +++ b/include/lang/en_gb.inc @@ -11,6 +11,8 @@ static const char *const g_en_gb[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay failed: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport applied to game: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport applied to game: %s\nEmulators updated", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/en_us.inc b/include/lang/en_us.inc index de575e3..b6e56d5 100644 --- a/include/lang/en_us.inc +++ b/include/lang/en_us.inc @@ -7,6 +7,8 @@ static const char *const g_en_us[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay failed: %s\n%s\n0x%08X", [SM_L10N_GAME_BACKPORTED] = "Backport applied to game: %s", + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = + "Backport applied to game: %s\nEmulators updated", [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\n" "Saved image_sector override: %u.\nTry mounting again.", diff --git a/include/lang/es_es.inc b/include/lang/es_es.inc index d334736..aaee1cf 100644 --- a/include/lang/es_es.inc +++ b/include/lang/es_es.inc @@ -11,6 +11,8 @@ static const char *const g_es_es[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Error en overlay de backport: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Juego con backport aplicado: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Juego con backport aplicado: %s\nEmuladores actualizados", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaje de imagen rechazado:\n%s\nEl tamaño de clúster es demasiado pequeño (%llu < %u).\nSe guardó el valor de image_sector: %u.\nIntente montar de nuevo.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/es_mx.inc b/include/lang/es_mx.inc index a65d38e..81e61f1 100644 --- a/include/lang/es_mx.inc +++ b/include/lang/es_mx.inc @@ -11,6 +11,8 @@ static const char *const g_es_mx[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Falló el overlay de backport: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport aplicado al juego: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicado al juego: %s\nEmuladores actualizados", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaje de imagen rechazado:\n%s\nEl tamaño de clúster es demasiado pequeño (%llu < %u).\nSe guardó el valor de image_sector: %u.\nIntenta montar de nuevo.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/fi_fi.inc b/include/lang/fi_fi.inc index 181c7b0..3c9a205 100644 --- a/include/lang/fi_fi.inc +++ b/include/lang/fi_fi.inc @@ -11,6 +11,8 @@ static const char *const g_fi_fi[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay epäonnistui: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Peliin käytettiin backportia: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Peliin käytettiin backportia: %s\nEmulaattorit päivitetty", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Imagen liittäminen hylätty:\n%s\nKlusterikoko on liian pieni (%llu < %u).\nimage_sector-ohitus tallennettu: %u.\nYritä liittää uudelleen.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/fr_ca.inc b/include/lang/fr_ca.inc index 02dc03d..3560e00 100644 --- a/include/lang/fr_ca.inc +++ b/include/lang/fr_ca.inc @@ -11,6 +11,8 @@ static const char *const g_fr_ca[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Échec du montage de la surcouche de backport : %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport appliqué au jeu : %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport appliqué au jeu : %s\nÉmulateurs mis à jour", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montage de l’image refusé :\n%s\nLa taille des grappes est trop petite (%llu < %u).\nValeur image_sector enregistrée : %u.\nRéessayez de monter l’image.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/fr_fr.inc b/include/lang/fr_fr.inc index dfacbd5..7ef6934 100644 --- a/include/lang/fr_fr.inc +++ b/include/lang/fr_fr.inc @@ -11,6 +11,8 @@ static const char *const g_fr_fr[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Échec du montage de la surcouche de backport : %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport appliqué au jeu : %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport appliqué au jeu : %s\nÉmulateurs mis à jour", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montage de l’image refusé :\n%s\nLa taille des clusters est trop petite (%llu < %u).\nValeur image_sector enregistrée : %u.\nRéessayez de monter l’image.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/hu_hu.inc b/include/lang/hu_hu.inc index fea969d..30cf3c5 100644 --- a/include/lang/hu_hu.inc +++ b/include/lang/hu_hu.inc @@ -11,6 +11,8 @@ static const char *const g_hu_hu[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay sikertelen: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Játék backportolva: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Játék backportolva: %s\nEmulátorok frissítve", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "A lemezkép csatolása elutasítva:\n%s\nA klaszter mérete túl kicsi (%llu < %u).\nAz image_sector felülírása mentve: %u.\nPróbáld újra csatolni.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/id_id.inc b/include/lang/id_id.inc index 44053ab..c4bafe6 100644 --- a/include/lang/id_id.inc +++ b/include/lang/id_id.inc @@ -11,6 +11,8 @@ static const char *const g_id_id[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay gagal: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport game diterapkan: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport game diterapkan: %s\nSemua emulator diperbarui", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Pemasangan image ditolak:\n%s\nUkuran cluster terlalu kecil (%llu < %u).\nNilai image_sector disimpan: %u.\nCoba pasang lagi.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/it_it.inc b/include/lang/it_it.inc index 1f01ae0..2899fdd 100644 --- a/include/lang/it_it.inc +++ b/include/lang/it_it.inc @@ -11,6 +11,8 @@ static const char *const g_it_it[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Overlay backport non riuscito: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport applicato al gioco: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport applicato al gioco: %s\nEmulatori aggiornati", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaggio immagine rifiutato:\n%s\nLa dimensione cluster è troppo piccola (%llu < %u).\nOverride image_sector salvato: %u.\nProva a montare di nuovo.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/ja_jp.inc b/include/lang/ja_jp.inc index c06c546..ec3424d 100644 --- a/include/lang/ja_jp.inc +++ b/include/lang/ja_jp.inc @@ -11,6 +11,8 @@ static const char *const g_ja_jp[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "バックポートオーバーレイのマウントに失敗しました: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "ゲームにバックポートを適用しました: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "ゲームにバックポートを適用しました: %s\n複数のエミュレーターを更新しました", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "イメージのマウントが拒否されました:\n%s\nクラスタサイズが小さすぎます (%llu < %u)。\nimage_sector override を保存しました: %u。\nもう一度マウントしてください。", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/ko_kr.inc b/include/lang/ko_kr.inc index 4cc5801..a2bfcf4 100644 --- a/include/lang/ko_kr.inc +++ b/include/lang/ko_kr.inc @@ -11,6 +11,8 @@ static const char *const g_ko_kr[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "백포트 오버레이 마운트 실패: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "게임에 백포트 적용됨: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "게임에 백포트 적용됨: %s\n에뮬레이터들이 업데이트됨", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "이미지 마운트 거부됨:\n%s\n클러스터 크기가 너무 작습니다 (%llu < %u).\nimage_sector override 저장됨: %u.\n다시 마운트해 보세요.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/nl_nl.inc b/include/lang/nl_nl.inc index d27b92e..1f52619 100644 --- a/include/lang/nl_nl.inc +++ b/include/lang/nl_nl.inc @@ -11,6 +11,8 @@ static const char *const g_nl_nl[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay mislukt: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport op game toegepast: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport op game toegepast: %s\nEmulators bijgewerkt", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Koppelen van image geweigerd:\n%s\nDe clustergrootte is te klein (%llu < %u).\nimage_sector-waarde opgeslagen: %u.\nProbeer opnieuw te koppelen.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/no_no.inc b/include/lang/no_no.inc index 5b3d310..f257fd0 100644 --- a/include/lang/no_no.inc +++ b/include/lang/no_no.inc @@ -11,6 +11,8 @@ static const char *const g_no_no[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay mislyktes: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport brukt på spillet: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport brukt på spillet: %s\nEmulatorer oppdatert", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montering av image avvist:\n%s\nKlyngestørrelsen er for liten (%llu < %u).\nimage_sector-overstyring lagret: %u.\nPrøv å montere igjen.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/pl_pl.inc b/include/lang/pl_pl.inc index 4c55009..c54e0f9 100644 --- a/include/lang/pl_pl.inc +++ b/include/lang/pl_pl.inc @@ -11,6 +11,8 @@ static const char *const g_pl_pl[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Nakładka backportu nie powiodła się: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Zastosowano backport gry: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Zastosowano backport gry: %s\nEmulatory zaktualizowane", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Odrzucono montowanie obrazu:\n%s\nRozmiar klastra jest za mały (%llu < %u).\nZastosowano nadpisanie image_sector: %u.\nSpróbuj zamontować ponownie.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/pt_br.inc b/include/lang/pt_br.inc index 579b49d..596eb9a 100644 --- a/include/lang/pt_br.inc +++ b/include/lang/pt_br.inc @@ -11,6 +11,8 @@ static const char *const g_pt_br[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Falha no overlay de backport: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport aplicado ao jogo: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicado ao jogo: %s\nEmuladores atualizados", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é pequeno demais (%llu < %u).\nNovo valor para o parâmetro image_sector salvo: %u.\nTente montar novamente.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/pt_pt.inc b/include/lang/pt_pt.inc index c2c4ced..2848b88 100644 --- a/include/lang/pt_pt.inc +++ b/include/lang/pt_pt.inc @@ -11,6 +11,8 @@ static const char *const g_pt_pt[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Falha no overlay de backport: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Jogo com backport aplicado: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Jogo com backport aplicado: %s\nEmuladores atualizados", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é demasiado pequeno (%llu < %u).\nSubstituição image_sector guardada: %u.\nTente montar novamente.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/ro_ro.inc b/include/lang/ro_ro.inc index 0971e07..93827a1 100644 --- a/include/lang/ro_ro.inc +++ b/include/lang/ro_ro.inc @@ -11,6 +11,8 @@ static const char *const g_ro_ro[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Overlay backport eșuat: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport aplicat jocului: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicat jocului: %s\nEmulatoare actualizate", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montarea imaginii respinsă:\n%s\nDimensiunea clusterului este prea mică (%llu < %u).\nOverride image_sector salvat: %u.\nÎncercați montarea din nou.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/ru_ru.inc b/include/lang/ru_ru.inc index 242c736..6a1d7bb 100644 --- a/include/lang/ru_ru.inc +++ b/include/lang/ru_ru.inc @@ -11,6 +11,8 @@ static const char *const g_ru_ru[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Не удалось смонтировать оверлей бэкпорта: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "К игре %s применён бэкпорт", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "К игре %s применён бэкпорт\nЭмуляторы обновлены", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Не удалось смонтировать образ:\n%s\nРазмер кластера слишком мал (%llu < %u).\nСохранено значение image_sector: %u.\nПопробуйте смонтировать снова.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/sv_se.inc b/include/lang/sv_se.inc index 9aa4343..d49b56a 100644 --- a/include/lang/sv_se.inc +++ b/include/lang/sv_se.inc @@ -11,6 +11,8 @@ static const char *const g_sv_se[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay misslyckades: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Backport tillämpad på spelet: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport tillämpad på spelet: %s\nEmulatorer uppdaterade", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Image-montering nekades:\n%s\nKlusterstorleken är för liten (%llu < %u).\nimage_sector-åsidosättning sparad: %u.\nFörsök montera igen.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/th_th.inc b/include/lang/th_th.inc index 7f4dadb..baa3040 100644 --- a/include/lang/th_th.inc +++ b/include/lang/th_th.inc @@ -11,6 +11,8 @@ static const char *const g_th_th[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "backport overlay ล้มเหลว: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "ใช้ backport กับเกมแล้ว: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "ใช้ backport กับเกมแล้ว: %s\nอัปเดตอีมูเลเตอร์หลายตัวแล้ว", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "ปฏิเสธการเมานต์อิมเมจ:\n%s\nขนาดคลัสเตอร์เล็กเกินไป (%llu < %u)\nบันทึกค่า image_sector แล้ว: %u\nลองเมานต์อีกครั้ง", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/tr_tr.inc b/include/lang/tr_tr.inc index 568afb6..30338c6 100644 --- a/include/lang/tr_tr.inc +++ b/include/lang/tr_tr.inc @@ -11,6 +11,8 @@ static const char *const g_tr_tr[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay başarısız: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Oyuna backport uygulandı: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Oyuna backport uygulandı: %s\nEmülatörler güncellendi", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "İmaj bağlama işlemi reddedildi:\n%s\nKüme boyutu çok küçük (%llu < %u).\nimage_sector değeri kaydedildi: %u.\nBağlamayı yeniden deneyin.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/vi_vn.inc b/include/lang/vi_vn.inc index 76b9179..a58c156 100644 --- a/include/lang/vi_vn.inc +++ b/include/lang/vi_vn.inc @@ -11,6 +11,8 @@ static const char *const g_vi_vn[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay thất bại: %s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "Đã áp dụng backport cho game: %s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Đã áp dụng backport cho game: %s\nĐã cập nhật các trình giả lập", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Từ chối gắn ảnh đĩa:\n%s\nKích thước cụm quá nhỏ (%llu < %u).\nĐã lưu giá trị image_sector: %u.\nHãy thử gắn lại.", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/zh_cn.inc b/include/lang/zh_cn.inc index c570c65..64b14a9 100644 --- a/include/lang/zh_cn.inc +++ b/include/lang/zh_cn.inc @@ -11,6 +11,8 @@ static const char *const g_zh_cn[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "向下兼容覆盖层挂载失败:%s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "已为游戏应用向下兼容补丁:%s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "已为游戏应用向下兼容补丁:%s\n多个模拟器已更新", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "镜像挂载被拒绝:\n%s\n簇大小太小 (%llu < %u)。\n已保存 image_sector 覆盖值: %u。\n请再次尝试挂载。", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/lang/zh_tw.inc b/include/lang/zh_tw.inc index b54a6e6..6585fa7 100644 --- a/include/lang/zh_tw.inc +++ b/include/lang/zh_tw.inc @@ -11,6 +11,8 @@ static const char *const g_zh_tw[SM_L10N_COUNT] = { [SM_L10N_BACKPORT_OVERLAY_FAILED] = "向下相容覆蓋層掛載失敗:%s\n%s\n0x%08X", /* English: Backport applied to game: %s */ [SM_L10N_GAME_BACKPORTED] = "已為遊戲套用向下相容修補程式:%s", + /* English: Backport applied to game: %s\nEmulators updated */ + [SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "已為遊戲套用向下相容修補程式:%s\n多個模擬器已更新", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */ [SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "映像掛載被拒絕:\n%s\n叢集大小太小 (%llu < %u)。\n已儲存 image_sector 覆寫值: %u。\n請再次嘗試掛載。", /* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */ diff --git a/include/sm_fakelib.h b/include/sm_fakelib.h index de8df68..013405d 100644 --- a/include/sm_fakelib.h +++ b/include/sm_fakelib.h @@ -7,6 +7,11 @@ // Return true when per-game fakelib backport automation is enabled. bool sm_fakelib_game_feature_enabled(void); +// Prepare or refresh the on-disk fakelib cache before title runtime mounting. +void sm_fakelib_prepare_title_cache(const char *title_id, + const char *game_path); +// Remove invalid and unused fakelib caches during the scanner's rare cycle. +void sm_fakelib_cleanup_caches(void); // Track a supported game launch and mount its fakelib overlay immediately. void sm_fakelib_game_on_exec(pid_t pid, const char *title_id); // Remove a tracked fakelib overlay for a stopped game. diff --git a/include/sm_filesystem.h b/include/sm_filesystem.h index 744a5de..66acc27 100644 --- a/include/sm_filesystem.h +++ b/include/sm_filesystem.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "sm_limits.h" @@ -66,7 +67,11 @@ void cleanup_mount_links_for_source_unmount(const char *source_root); void cleanup_usb_mount_links_for_suspend(void); // Recursively copy a directory tree. int copy_dir(const char *src, const char *dst); +// Recursively copy a directory tree and set mode as each entry is created. +int copy_dir_with_mode(const char *src, const char *dst, mode_t mode); // Copy a single file. int copy_file(const char *src, const char *dst); +// Copy a single file and set its mode immediately after creation. +int copy_file_with_mode(const char *src, const char *dst, mode_t mode); #endif diff --git a/include/sm_l10n.h b/include/sm_l10n.h index e7a3b90..af5e48d 100644 --- a/include/sm_l10n.h +++ b/include/sm_l10n.h @@ -12,6 +12,7 @@ typedef enum { SM_L10N_SCAN_DEPTH_ENABLED, SM_L10N_BACKPORT_OVERLAY_FAILED, SM_L10N_GAME_BACKPORTED, + SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED, SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED, SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG, SM_L10N_REGISTER_FAILED_UNAVAILABLE, diff --git a/include/sm_paths.h b/include/sm_paths.h index 4886c2a..0887841 100644 --- a/include/sm_paths.h +++ b/include/sm_paths.h @@ -6,6 +6,8 @@ #define DEFAULT_BACKPORTS_DIR_NAME "backports" #define DEFAULT_GLOBAL_FAKELIB_PATH "/data/shadowmount/fakelib" +#define DEFAULT_EMULATORS_PATH "/data/shadowmount/emus" +#define FAKELIB_CACHE_PATH "/data/shadowmount/cache" #define LOG_DIR "/data/shadowmount" #define LOG_FILE "/data/shadowmount/debug.log" #define LOG_FILE_PREV "/data/shadowmount/debug.log.1" diff --git a/include/sm_scan.h b/include/sm_scan.h index eb0b208..e9b1483 100644 --- a/include/sm_scan.h +++ b/include/sm_scan.h @@ -3,6 +3,8 @@ #include +#include "sm_limits.h" + typedef struct scan_candidate scan_candidate_t; // Unmount and clean up mounts whose backing sources disappeared. @@ -21,6 +23,10 @@ int collect_scan_candidates_for_scan_root(const char *scan_root, int max_candidates, int *total_found_out, bool *unstable_found_out); +// Resolve the source directory of a title backport without mounting it. +bool resolve_backport_path_for_title(const char *title_id, + const char *owning_scan_root, + char backport_path[MAX_PATH]); // Ensure a stable backport overlay is mounted when one is present. // Absence is harmless; an unusable or failed required overlay returns false. bool mount_backport_overlay_for_title(const char *source_path, diff --git a/include/sm_types.h b/include/sm_types.h index fdf8c43..37fba0b 100644 --- a/include/sm_types.h +++ b/include/sm_types.h @@ -126,12 +126,14 @@ typedef struct runtime_config { bool backport_fakelib_enabled; bool global_fakelib_enabled; bool global_fakelib_mount_first; + bool update_emulators_enabled; bool kstuff_game_auto_toggle; bool kstuff_crash_detection_enabled; bool legacy_recursive_scan_forced; char api_bind_address[MAX_API_BIND_ADDRESS]; uint32_t api_port; char global_fakelib_path[MAX_PATH]; + char emulators_path[MAX_PATH]; uint32_t global_fakelib_exclude_title_count; char global_fakelib_exclude_title_ids[MAX_FAKELIB_EXCLUDE_RULES][MAX_TITLE_ID]; uint32_t scan_depth; diff --git a/src/sm_config_mount.c b/src/sm_config_mount.c index 078847b..9600f94 100644 --- a/src/sm_config_mount.c +++ b/src/sm_config_mount.c @@ -81,6 +81,8 @@ static bool add_global_fakelib_exclude_rule(runtime_config_state_t *state, const char *value); static bool normalize_image_filename_value(const char *value, char out[MAX_PATH]); +static bool normalize_absolute_path_value(const char *value, + char out[MAX_PATH]); static bool set_image_sector_rule(runtime_config_state_t *state, const char *value); static bool parse_image_sector_rule_value(const char *value, @@ -248,6 +250,7 @@ static void init_runtime_config_defaults(runtime_config_state_t *state) { state->cfg.backport_fakelib_enabled = true; state->cfg.global_fakelib_enabled = true; state->cfg.global_fakelib_mount_first = true; + state->cfg.update_emulators_enabled = true; state->cfg.kstuff_game_auto_toggle = true; state->cfg.kstuff_crash_detection_enabled = true; state->cfg.legacy_recursive_scan_forced = false; @@ -256,6 +259,8 @@ static void init_runtime_config_defaults(runtime_config_state_t *state) { state->cfg.api_port = SM_API_DEFAULT_PORT; (void)strlcpy(state->cfg.global_fakelib_path, DEFAULT_GLOBAL_FAKELIB_PATH, sizeof(state->cfg.global_fakelib_path)); + (void)strlcpy(state->cfg.emulators_path, DEFAULT_EMULATORS_PATH, + sizeof(state->cfg.emulators_path)); state->cfg.scan_depth = DEFAULT_SCAN_DEPTH; state->cfg.scan_interval_us = DEFAULT_SCAN_INTERVAL_US; state->cfg.stability_wait_seconds = DEFAULT_STABILITY_WAIT_SECONDS; @@ -575,6 +580,29 @@ static bool normalize_image_filename_value(const char *value, return true; } +static bool normalize_absolute_path_value(const char *value, + char out[MAX_PATH]) { + if (!value || !out) + return false; + + char local[MAX_PATH]; + if (strlcpy(local, value, sizeof(local)) >= sizeof(local)) + return false; + + char *trimmed = trim_ascii(local); + size_t len = strlen(trimmed); + if (len == 0 || len >= MAX_PATH || trimmed[0] != '/') + return false; + + while (len > 1 && trimmed[len - 1] == '/') { + trimmed[len - 1] = '\0'; + len--; + } + + (void)strlcpy(out, trimmed, MAX_PATH); + return true; +} + static bool parse_bool_ini(const char *value, bool *out) { if (!value || !out) return false; @@ -1329,24 +1357,13 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st } if (strcasecmp(key, "global_fakelib_path") == 0) { - char local[MAX_PATH]; - if (strlcpy(local, value, sizeof(local)) >= sizeof(local)) { + char path[MAX_PATH]; + if (!normalize_absolute_path_value(value, path)) { log_debug(" [CFG] invalid global fakelib path at line %d: %s=%s", line_no, key, value); continue; } - char *trimmed = trim_ascii(local); - size_t len = strlen(trimmed); - if (len == 0 || len >= MAX_PATH || trimmed[0] != '/') { - log_debug(" [CFG] invalid global fakelib path at line %d: %s=%s", - line_no, key, value); - continue; - } - while (len > 1 && trimmed[len - 1] == '/') { - trimmed[len - 1] = '\0'; - len--; - } - (void)strlcpy(state->cfg.global_fakelib_path, trimmed, + (void)strlcpy(state->cfg.global_fakelib_path, path, sizeof(state->cfg.global_fakelib_path)); continue; } @@ -1394,6 +1411,27 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st continue; } + if (strcasecmp(key, "update_emulators") == 0) { + if (!parse_bool_ini(value, &bval)) { + log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value); + continue; + } + state->cfg.update_emulators_enabled = bval; + continue; + } + + if (strcasecmp(key, "emulators_path") == 0) { + char path[MAX_PATH]; + if (!normalize_absolute_path_value(value, path)) { + log_debug(" [CFG] invalid emulator path at line %d: %s=%s", + line_no, key, value); + continue; + } + (void)strlcpy(state->cfg.emulators_path, path, + sizeof(state->cfg.emulators_path)); + continue; + } + if (strcasecmp(key, "fan_target_temperature") == 0) { if (strcasecmp(value, "system") == 0 || strcasecmp(value, "auto") == 0) { @@ -1619,6 +1657,7 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st "legacy_recursive_scan_forced=%d backport_fakelib=%d " "global_fakelib=%d global_fakelib_priority=%s " "global_fakelib_path=%s global_fakelib_exclude=%u " + "update_emulators=%d emulators_path=%s " "kstuff_game_auto_toggle=%d kstuff_crash_detection=%d " "fan_target_temperature=%u (0=system) " "kstuff_pause_delay_image_s=%u kstuff_pause_delay_direct_s=%u " @@ -1645,6 +1684,8 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st state->cfg.global_fakelib_mount_first ? "game" : "global", state->cfg.global_fakelib_path, state->cfg.global_fakelib_exclude_title_count, + state->cfg.update_emulators_enabled ? 1 : 0, + state->cfg.emulators_path, state->cfg.kstuff_game_auto_toggle ? 1 : 0, state->cfg.kstuff_crash_detection_enabled ? 1 : 0, state->cfg.fan_target_temperature_c, diff --git a/src/sm_fakelib.c b/src/sm_fakelib.c index cd3151b..49a9007 100644 --- a/src/sm_fakelib.c +++ b/src/sm_fakelib.c @@ -2,11 +2,21 @@ #include "sm_fakelib.h" #include "sm_config_mount.h" +#include "sm_filesystem.h" +#include "sm_gameinfo.h" #include "sm_log.h" #include "sm_mount_diag.h" +#include "sm_paths.h" +#include "sm_scan.h" #include "sm_types.h" #include +#include +#include + +#define FAKELIB_CACHE_VERSION 2u +#define FAKELIB_CACHE_MAX_AGE_SECONDS (7u * 24u * 60u * 60u) +#define FAKELIB_CACHE_MAGIC 0x534D4643u typedef struct { char source_path[MAX_PATH]; @@ -21,8 +31,29 @@ typedef struct { size_t layer_count; } fakelib_session_t; +typedef struct { + uint64_t xor_hash; + uint64_t sum_hash; + uint64_t entry_count; +} fakelib_cache_signature_t; + +typedef struct { + uint32_t magic; + uint32_t version; + uint64_t emulator_file_count; + fakelib_cache_signature_t source_signature; + fakelib_cache_signature_t emulator_files_signature; + char source_path[MAX_PATH]; + char emulators_path[MAX_PATH]; +} fakelib_cache_manifest_t; + static fakelib_session_t g_fakelib_mount; static pthread_mutex_t g_fakelib_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t g_fakelib_cache_mutex = PTHREAD_MUTEX_INITIALIZER; + +static bool fakelib_session_active(void) { + return g_fakelib_mount.layer_count > 0; +} bool sm_fakelib_game_feature_enabled(void) { return runtime_config()->backport_fakelib_enabled; @@ -83,10 +114,8 @@ static bool track_fakelib_overlay(const char *title_id, return true; } -static bool resolve_sandbox_context(const char *title_id, - char game_source_path[MAX_PATH], - char mount_path[MAX_PATH]) { - game_source_path[0] = '\0'; +static bool resolve_sandbox_mount_path(const char *title_id, + char mount_path[MAX_PATH]) { mount_path[0] = '\0'; char sandbox_id[MAX_TITLE_ID]; @@ -131,19 +160,7 @@ static bool resolve_sandbox_context(const char *title_id, if (!found) return false; - char source_path[MAX_PATH]; - snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib2", - sandbox_id); struct stat st; - if (stat(source_path, &st) == 0 && S_ISDIR(st.st_mode)) { - (void)strlcpy(game_source_path, source_path, MAX_PATH); - } else { - snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib", - sandbox_id); - if (stat(source_path, &st) == 0 && S_ISDIR(st.st_mode)) - (void)strlcpy(game_source_path, source_path, MAX_PATH); - } - char sandbox_root[MAX_PATH]; snprintf(sandbox_root, sizeof(sandbox_root), "/mnt/sandbox/%s", sandbox_id); d = opendir(sandbox_root); @@ -170,6 +187,48 @@ static bool resolve_sandbox_context(const char *title_id, return found; } +static bool resolve_game_fakelib_source_for_path( + const char *title_id, const char *game_path, + char source_path[MAX_PATH]) { + source_path[0] = '\0'; + + char backport_path[MAX_PATH] = {0}; + bool has_backport = + resolve_backport_path_for_title(title_id, NULL, backport_path); + bool has_game = game_path && game_path[0] != '\0'; + const struct { + const char *root; + const char *directory; + } candidates[] = { + {has_backport ? backport_path : NULL, "fakelib2"}, + {has_backport ? backport_path : NULL, "fakelib"}, + {has_game ? game_path : NULL, "fakelib"}, + }; + struct stat st; + for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { + if (!candidates[i].root) + continue; + int written = snprintf(source_path, MAX_PATH, "%s/%s", + candidates[i].root, candidates[i].directory); + if (written > 0 && (size_t)written < MAX_PATH && + stat(source_path, &st) == 0 && S_ISDIR(st.st_mode)) { + return true; + } + } + + source_path[0] = '\0'; + return false; +} + +static bool resolve_game_fakelib_source(const char *title_id, + char source_path[MAX_PATH]) { + char game_path[MAX_PATH]; + if (!read_mount_link(title_id, game_path, sizeof(game_path))) + game_path[0] = '\0'; + return resolve_game_fakelib_source_for_path(title_id, game_path, + source_path); +} + static bool resolve_global_fakelib_source(const char *title_id, char source_path[MAX_PATH]) { source_path[0] = '\0'; @@ -197,8 +256,727 @@ static bool resolve_global_fakelib_source(const char *title_id, return true; } +static uint64_t cache_hash_bytes(uint64_t hash, const void *data, size_t size) { + const uint8_t *bytes = (const uint8_t *)data; + for (size_t i = 0; i < size; ++i) { + hash ^= bytes[i]; + hash *= 1099511628211ULL; + } + return hash; +} + +static void cache_signature_add(fakelib_cache_signature_t *signature, + const char *relative_path, + const struct stat *st) { + uint64_t entry_hash = 1469598103934665603ULL; + entry_hash = cache_hash_bytes(entry_hash, relative_path, + strlen(relative_path)); + entry_hash = cache_hash_bytes(entry_hash, &st->st_size, + sizeof(st->st_size)); + entry_hash = cache_hash_bytes(entry_hash, &st->st_mode, + sizeof(st->st_mode)); + entry_hash = cache_hash_bytes(entry_hash, &st->st_mtim.tv_sec, + sizeof(st->st_mtim.tv_sec)); + entry_hash = cache_hash_bytes(entry_hash, &st->st_mtim.tv_nsec, + sizeof(st->st_mtim.tv_nsec)); + entry_hash = cache_hash_bytes(entry_hash, &st->st_ctim.tv_sec, + sizeof(st->st_ctim.tv_sec)); + entry_hash = cache_hash_bytes(entry_hash, &st->st_ctim.tv_nsec, + sizeof(st->st_ctim.tv_nsec)); + signature->xor_hash ^= entry_hash; + signature->sum_hash += entry_hash; + signature->entry_count++; +} + +static bool compute_tree_signature(const char *root, const char *relative_root, + fakelib_cache_signature_t *signature) { + char directory_path[MAX_PATH]; + int written = relative_root[0] == '\0' + ? snprintf(directory_path, sizeof(directory_path), "%s", + root) + : snprintf(directory_path, sizeof(directory_path), "%s/%s", + root, relative_root); + if (written <= 0 || (size_t)written >= sizeof(directory_path)) + return false; + + DIR *d = opendir(directory_path); + if (!d) + return false; + + bool ok = true; + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + if (entry->d_name[0] == '.' && + (entry->d_name[1] == '\0' || + (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) { + continue; + } + + char relative_path[MAX_PATH]; + char full_path[MAX_PATH]; + int relative_len = + relative_root[0] == '\0' + ? snprintf(relative_path, sizeof(relative_path), "%s", + entry->d_name) + : snprintf(relative_path, sizeof(relative_path), "%s/%s", + relative_root, entry->d_name); + int full_len = snprintf(full_path, sizeof(full_path), "%s/%s", root, + relative_path); + if (relative_len <= 0 || + (size_t)relative_len >= sizeof(relative_path) || full_len <= 0 || + (size_t)full_len >= sizeof(full_path)) { + ok = false; + break; + } + + struct stat lst; + struct stat st; + if (lstat(full_path, &lst) != 0) { + ok = false; + break; + } + if (S_ISLNK(lst.st_mode)) { + if (stat(full_path, &st) != 0 || S_ISDIR(st.st_mode)) { + ok = false; + break; + } + } else { + st = lst; + } + cache_signature_add(signature, relative_path, &st); + if (S_ISDIR(st.st_mode) && + !compute_tree_signature(root, relative_path, signature)) { + ok = false; + break; + } + } + if (closedir(d) != 0) + ok = false; + return ok; +} + +static bool compute_source_signature( + const char *source_path, fakelib_cache_signature_t *signature) { + memset(signature, 0, sizeof(*signature)); + return compute_tree_signature(source_path, "", signature); +} + +static bool compute_emulator_files_signature( + const char *emulators_path, const char *source_path, + fakelib_cache_signature_t *signature, size_t *matching_count_out) { + memset(signature, 0, sizeof(*signature)); + *matching_count_out = 0; + + DIR *d = opendir(emulators_path); + if (!d) + return errno == ENOENT; + + bool ok = true; + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + if (entry->d_name[0] == '.' && + (entry->d_name[1] == '\0' || + (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) { + continue; + } + + char emulator_file[MAX_PATH]; + char source_file[MAX_PATH]; + int emulator_len = snprintf(emulator_file, sizeof(emulator_file), "%s/%s", + emulators_path, entry->d_name); + int source_len = snprintf(source_file, sizeof(source_file), "%s/%s", + source_path, entry->d_name); + if (emulator_len <= 0 || + (size_t)emulator_len >= sizeof(emulator_file) || source_len <= 0 || + (size_t)source_len >= sizeof(source_file)) { + ok = false; + break; + } + + struct stat emulator_st; + struct stat source_st; + if (stat(emulator_file, &emulator_st) != 0 || + !S_ISREG(emulator_st.st_mode)) { + continue; + } + if (stat(source_file, &source_st) != 0 || !S_ISREG(source_st.st_mode)) + continue; + if (emulator_st.st_dev == source_st.st_dev && + emulator_st.st_ino == source_st.st_ino) { + continue; + } + cache_signature_add(signature, entry->d_name, &emulator_st); + (*matching_count_out)++; + } + if (closedir(d) != 0) + ok = false; + return ok; +} + +static bool cache_signatures_equal(const fakelib_cache_signature_t *a, + const fakelib_cache_signature_t *b) { + return a->xor_hash == b->xor_hash && a->sum_hash == b->sum_hash && + a->entry_count == b->entry_count; +} + +static bool remove_cache_tree(const char *path) { + struct stat root_st; + if (lstat(path, &root_st) != 0) + return errno == ENOENT; + if (!S_ISDIR(root_st.st_mode)) + return unlink(path) == 0 || errno == ENOENT; + + DIR *d = opendir(path); + if (!d) + return false; + + bool removed = true; + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + if (entry->d_name[0] == '.' && + (entry->d_name[1] == '\0' || + (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) { + continue; + } + + char child_path[MAX_PATH]; + int written = snprintf(child_path, sizeof(child_path), "%s/%s", path, + entry->d_name); + if (written <= 0 || (size_t)written >= sizeof(child_path)) { + removed = false; + break; + } + struct stat st; + if (lstat(child_path, &st) != 0) { + removed = false; + break; + } + if (S_ISDIR(st.st_mode)) { + if (!remove_cache_tree(child_path)) { + removed = false; + break; + } + } else if (unlink(child_path) != 0) { + removed = false; + break; + } + } + if (closedir(d) != 0) + removed = false; + if (removed && rmdir(path) != 0 && errno != ENOENT) + removed = false; + return removed; +} + +static bool build_cache_path(const char *title_id, const char *suffix, + char path[MAX_PATH]) { + int written = snprintf(path, MAX_PATH, "%s/%s%s", FAKELIB_CACHE_PATH, + title_id, suffix ? suffix : ""); + return written > 0 && (size_t)written < MAX_PATH; +} + +static bool cache_root_is_mounted(const char *cache_root) { + char cache_fakelib[MAX_PATH]; + int written = snprintf(cache_fakelib, sizeof(cache_fakelib), "%s/fakelib", + cache_root); + if (written <= 0 || (size_t)written >= sizeof(cache_fakelib)) + return false; + + struct statfs *mounts = NULL; + int mount_count = getmntinfo(&mounts, MNT_NOWAIT); + if (mount_count <= 0 || !mounts) + return true; + + for (int i = 0; i < mount_count; ++i) { + const char *source = mounts[i].f_mntfromname; + if (strncmp(source, ":", 8) == 0) + source += 8; + if (strcmp(source, cache_fakelib) == 0) + return true; + } + return false; +} + +static void remove_title_cache(const char *title_id) { + char cache_root[MAX_PATH]; + if (!build_cache_path(title_id, "", cache_root)) + return; + struct stat st; + if (lstat(cache_root, &st) != 0) + return; + if (!S_ISDIR(st.st_mode)) { + (void)unlink(cache_root); + return; + } + + char manifest_path[MAX_PATH]; + int written = snprintf(manifest_path, sizeof(manifest_path), "%s/manifest", + cache_root); + if (written > 0 && (size_t)written < sizeof(manifest_path)) + (void)unlink(manifest_path); + if (!cache_root_is_mounted(cache_root)) + (void)remove_cache_tree(cache_root); +} + +static bool read_cache_manifest(const char *cache_root, + fakelib_cache_manifest_t *manifest, + time_t *last_used_out) { + struct stat cache_st; + if (lstat(cache_root, &cache_st) != 0 || !S_ISDIR(cache_st.st_mode)) + return false; + + char manifest_path[MAX_PATH]; + int written = snprintf(manifest_path, sizeof(manifest_path), "%s/manifest", + cache_root); + if (written <= 0 || (size_t)written >= sizeof(manifest_path)) + return false; + + struct stat manifest_st; + if (lstat(manifest_path, &manifest_st) != 0 || + !S_ISREG(manifest_st.st_mode)) { + return false; + } + + FILE *file = fopen(manifest_path, "rb"); + if (!file) + return false; + bool ok = fread(manifest, 1, sizeof(*manifest), file) == sizeof(*manifest); + if (fclose(file) != 0) + ok = false; + ok = ok && manifest->magic == FAKELIB_CACHE_MAGIC && + manifest->version == FAKELIB_CACHE_VERSION && + memchr(manifest->source_path, '\0', sizeof(manifest->source_path)) && + memchr(manifest->emulators_path, '\0', + sizeof(manifest->emulators_path)); + if (ok && last_used_out) + *last_used_out = manifest_st.st_mtime; + return ok; +} + +static bool touch_cache_manifest(const char *cache_root) { + char manifest_path[MAX_PATH]; + int written = snprintf(manifest_path, sizeof(manifest_path), "%s/manifest", + cache_root); + return written > 0 && (size_t)written < sizeof(manifest_path) && + utimes(manifest_path, NULL) == 0; +} + +static bool write_cache_manifest(const char *cache_root, + const fakelib_cache_manifest_t *manifest) { + char manifest_path[MAX_PATH]; + int written = snprintf(manifest_path, sizeof(manifest_path), "%s/manifest", + cache_root); + if (written <= 0 || (size_t)written >= sizeof(manifest_path)) + return false; + + FILE *file = fopen(manifest_path, "wb"); + if (!file) + return false; + bool ok = fchmod(fileno(file), 0777) == 0 && + fwrite(manifest, 1, sizeof(*manifest), file) == sizeof(*manifest) && + fflush(file) == 0 && fsync(fileno(file)) == 0; + if (fclose(file) != 0) + ok = false; + if (!ok) + (void)unlink(manifest_path); + return ok; +} + +static bool cache_manifest_matches_context( + const fakelib_cache_manifest_t *manifest, const char *source_path, + const char *emulators_path) { + return strcmp(manifest->source_path, source_path) == 0 && + strcmp(manifest->emulators_path, emulators_path) == 0 && + manifest->emulator_file_count > 0; +} + +static bool cache_manifest_is_current( + const fakelib_cache_manifest_t *manifest, const char *source_path, + const char *emulators_path, + const fakelib_cache_signature_t *source_signature, + const fakelib_cache_signature_t *emulator_files_signature, + size_t emulator_file_count) { + return cache_manifest_matches_context(manifest, source_path, + emulators_path) && + manifest->emulator_file_count == emulator_file_count && + cache_signatures_equal(&manifest->source_signature, + source_signature) && + cache_signatures_equal(&manifest->emulator_files_signature, + emulator_files_signature); +} + +static bool copy_emulator_files_to_cache(const char *emulators_path, + const char *source_path, + const char *cache_fakelib_path, + size_t *copied_count_out) { + *copied_count_out = 0; + DIR *d = opendir(emulators_path); + if (!d) + return errno == ENOENT; + + bool ok = true; + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + if (entry->d_name[0] == '.' && + (entry->d_name[1] == '\0' || + (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) { + continue; + } + + char emulator_file[MAX_PATH]; + char source_file[MAX_PATH]; + char cache_file[MAX_PATH]; + int emulator_len = snprintf(emulator_file, sizeof(emulator_file), "%s/%s", + emulators_path, entry->d_name); + int source_len = snprintf(source_file, sizeof(source_file), "%s/%s", + source_path, entry->d_name); + int cache_len = snprintf(cache_file, sizeof(cache_file), "%s/%s", + cache_fakelib_path, entry->d_name); + if (emulator_len <= 0 || + (size_t)emulator_len >= sizeof(emulator_file) || source_len <= 0 || + (size_t)source_len >= sizeof(source_file) || cache_len <= 0 || + (size_t)cache_len >= sizeof(cache_file)) { + ok = false; + break; + } + + struct stat emulator_st; + struct stat source_st; + if (stat(emulator_file, &emulator_st) != 0 || + !S_ISREG(emulator_st.st_mode)) { + continue; + } + if (stat(source_file, &source_st) != 0 || !S_ISREG(source_st.st_mode)) + continue; + if (emulator_st.st_dev == source_st.st_dev && + emulator_st.st_ino == source_st.st_ino) { + continue; + } + if (copy_file_with_mode(emulator_file, cache_file, 0777) != 0) { + ok = false; + break; + } + (*copied_count_out)++; + } + if (closedir(d) != 0) + ok = false; + return ok; +} + +static bool ensure_fakelib_cache_root(void) { + if (mkdir("/data/shadowmount", 0777) != 0 && errno != EEXIST) + return false; + if (mkdir(FAKELIB_CACHE_PATH, 0777) != 0 && errno != EEXIST) + return false; + + struct stat st; + return lstat(FAKELIB_CACHE_PATH, &st) == 0 && S_ISDIR(st.st_mode); +} + +static bool parse_temporary_cache_title_id( + const char *entry_name, char title_id[MAX_TITLE_ID]) { + const char suffix[] = ".tmp"; + size_t length = strlen(entry_name); + size_t suffix_length = sizeof(suffix) - 1u; + if (length <= suffix_length || length - suffix_length >= MAX_TITLE_ID || + strcmp(entry_name + length - suffix_length, suffix) != 0) { + return false; + } + size_t title_length = length - suffix_length; + memcpy(title_id, entry_name, title_length); + title_id[title_length] = '\0'; + return is_supported_game_title_id(title_id); +} + +static void cleanup_expired_fakelib_caches(uint64_t now_sec) { + DIR *d = opendir(FAKELIB_CACHE_PATH); + if (!d) + return; + + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + char temporary_title_id[MAX_TITLE_ID]; + if (parse_temporary_cache_title_id(entry->d_name, + temporary_title_id)) { + char temporary_root[MAX_PATH]; + if (build_cache_path(temporary_title_id, ".tmp", temporary_root)) { + if (remove_cache_tree(temporary_root)) { + log_debug(" [EMU] orphaned temporary cache removed: %s", + temporary_root); + } else { + log_debug(" [EMU] temporary cache cleanup failed: %s", + temporary_root); + } + } + continue; + } + if (!is_supported_game_title_id(entry->d_name)) { + continue; + } + + char cache_root[MAX_PATH]; + if (!build_cache_path(entry->d_name, "", cache_root)) + continue; + if (cache_root_is_mounted(cache_root)) + continue; + + fakelib_cache_manifest_t manifest; + time_t last_used = 0; + if (!read_cache_manifest(cache_root, &manifest, &last_used)) { + (void)remove_cache_tree(cache_root); + continue; + } + if (manifest.emulator_file_count > 0 && last_used > 0 && + ((uint64_t)last_used > now_sec || + now_sec - (uint64_t)last_used <= FAKELIB_CACHE_MAX_AGE_SECONDS)) { + continue; + } + if (remove_cache_tree(cache_root)) { + log_debug(" [EMU] stale fakelib cache removed: %s", cache_root); + } else { + log_debug(" [EMU] stale fakelib cache cleanup failed: %s", + cache_root); + } + } + closedir(d); +} + +void sm_fakelib_cleanup_caches(void) { + time_t now = time(NULL); + if (now < 0) + return; + + pthread_mutex_lock(&g_fakelib_cache_mutex); + cleanup_expired_fakelib_caches((uint64_t)now); + pthread_mutex_unlock(&g_fakelib_cache_mutex); +} + +static bool rebuild_fakelib_cache( + const char *title_id, const char *source_path, const char *emulators_path, + const fakelib_cache_signature_t *source_signature, + const fakelib_cache_signature_t *emulator_files_signature, + size_t emulator_file_count) { + char cache_root[MAX_PATH] = {0}; + char temp_root[MAX_PATH]; + if (!build_cache_path(title_id, "", cache_root) || + !build_cache_path(title_id, ".tmp", temp_root)) { + return false; + } + if (chmod(FAKELIB_CACHE_PATH, 0777) != 0) { + log_debug(" [EMU] cache root mode update failed: %s (%s)", + FAKELIB_CACHE_PATH, strerror(errno)); + return false; + } + + if (!remove_cache_tree(temp_root)) { + log_debug(" [EMU] temporary cache cleanup failed for %s: %s", title_id, + temp_root); + return false; + } + if (mkdir(temp_root, 0777) != 0) { + log_debug(" [EMU] temporary cache creation failed for %s: %s (%s)", + title_id, temp_root, strerror(errno)); + return false; + } + if (chmod(temp_root, 0777) != 0) { + log_debug(" [EMU] temporary cache mode update failed for %s: %s (%s)", + title_id, temp_root, strerror(errno)); + (void)remove_cache_tree(temp_root); + return false; + } + + char temp_fakelib[MAX_PATH]; + int written = snprintf(temp_fakelib, sizeof(temp_fakelib), "%s/fakelib", + temp_root); + bool built = written > 0 && (size_t)written < sizeof(temp_fakelib) && + copy_dir_with_mode(source_path, temp_fakelib, 0777) == 0; + size_t copied_emulator_files = 0; + if (built) { + built = copy_emulator_files_to_cache(emulators_path, source_path, + temp_fakelib, + &copied_emulator_files) && + copied_emulator_files == emulator_file_count; + } + + fakelib_cache_signature_t current_source_signature; + fakelib_cache_signature_t current_emulator_files_signature; + size_t current_emulator_file_count = 0; + if (built) { + built = compute_source_signature(source_path, ¤t_source_signature) && + compute_emulator_files_signature( + emulators_path, source_path, ¤t_emulator_files_signature, + ¤t_emulator_file_count) && + current_emulator_file_count == emulator_file_count && + cache_signatures_equal(source_signature, + ¤t_source_signature) && + cache_signatures_equal(emulator_files_signature, + ¤t_emulator_files_signature); + } + + fakelib_cache_manifest_t manifest; + if (built) { + memset(&manifest, 0, sizeof(manifest)); + manifest.magic = FAKELIB_CACHE_MAGIC; + manifest.version = FAKELIB_CACHE_VERSION; + manifest.emulator_file_count = emulator_file_count; + manifest.source_signature = *source_signature; + manifest.emulator_files_signature = *emulator_files_signature; + (void)strlcpy(manifest.source_path, source_path, + sizeof(manifest.source_path)); + (void)strlcpy(manifest.emulators_path, emulators_path, + sizeof(manifest.emulators_path)); + built = write_cache_manifest(temp_root, &manifest); + } + + if (!built) { + log_debug(" [EMU] fakelib cache build failed for %s", title_id); + (void)remove_cache_tree(temp_root); + return false; + } + if (cache_root_is_mounted(cache_root)) { + log_debug(" [EMU] fakelib cache is still mounted for %s", title_id); + (void)remove_cache_tree(temp_root); + return false; + } + if (!remove_cache_tree(cache_root) || rename(temp_root, cache_root) != 0) { + log_debug(" [EMU] fakelib cache publish failed for %s: %s", title_id, + strerror(errno)); + (void)remove_cache_tree(temp_root); + return false; + } + + log_debug(" [EMU] fakelib cache updated for %s: source=%s emulator_files=%u", + title_id, source_path, (unsigned)emulator_file_count); + return true; +} + +static void prepare_title_cache(const char *title_id, const char *game_path) { + if (!is_supported_game_title_id(title_id)) + return; + const runtime_config_t *cfg = runtime_config(); + if (!cfg->backport_fakelib_enabled || !cfg->update_emulators_enabled) + return; + char emulators_path[MAX_PATH]; + (void)strlcpy(emulators_path, cfg->emulators_path, sizeof(emulators_path)); + + char source_path[MAX_PATH]; + if (!resolve_game_fakelib_source_for_path(title_id, game_path, + source_path)) + return; + if (!ensure_fakelib_cache_root()) { + log_debug(" [EMU] fakelib cache root unavailable for %s: %s (%s)", + title_id, FAKELIB_CACHE_PATH, strerror(errno)); + return; + } + + fakelib_cache_signature_t source_signature; + fakelib_cache_signature_t emulator_files_signature; + size_t emulator_file_count = 0; + if (!compute_emulator_files_signature( + emulators_path, source_path, &emulator_files_signature, + &emulator_file_count)) { + log_debug(" [EMU] fakelib cache fingerprint failed for %s", title_id); + remove_title_cache(title_id); + return; + } + if (emulator_file_count == 0) { + remove_title_cache(title_id); + return; + } + if (!compute_source_signature(source_path, &source_signature)) { + log_debug(" [EMU] fakelib cache fingerprint failed for %s", title_id); + remove_title_cache(title_id); + return; + } + + char cache_root[MAX_PATH] = {0}; + bool current = false; + if (build_cache_path(title_id, "", cache_root)) { + fakelib_cache_manifest_t manifest; + char cache_fakelib[MAX_PATH]; + int written = snprintf(cache_fakelib, sizeof(cache_fakelib), + "%s/fakelib", cache_root); + struct stat st; + current = written > 0 && (size_t)written < sizeof(cache_fakelib) && + lstat(cache_fakelib, &st) == 0 && S_ISDIR(st.st_mode) && + read_cache_manifest(cache_root, &manifest, NULL) && + cache_manifest_is_current( + &manifest, source_path, emulators_path, + &source_signature, &emulator_files_signature, + emulator_file_count); + } + if (!current) { + if (!rebuild_fakelib_cache( + title_id, source_path, emulators_path, &source_signature, + &emulator_files_signature, emulator_file_count)) { + remove_title_cache(title_id); + } + } else { + log_debug(" [EMU] fakelib cache current for %s: emulator_files=%u", title_id, + (unsigned)emulator_file_count); + } +} + +void sm_fakelib_prepare_title_cache(const char *title_id, + const char *game_path) { + pthread_mutex_lock(&g_fakelib_cache_mutex); + prepare_title_cache(title_id, game_path); + pthread_mutex_unlock(&g_fakelib_cache_mutex); +} + +static bool resolve_cached_fakelib_locked( + const char *title_id, const char *source_path, char cache_path[MAX_PATH], + size_t *emulator_file_count_out) { + *emulator_file_count_out = 0; + if (!is_supported_game_title_id(title_id)) + return false; + const runtime_config_t *cfg = runtime_config(); + if (!cfg->update_emulators_enabled) + return false; + char emulators_path[MAX_PATH]; + (void)strlcpy(emulators_path, cfg->emulators_path, sizeof(emulators_path)); + + char cache_root[MAX_PATH]; + if (!build_cache_path(title_id, "", cache_root)) + return false; + fakelib_cache_manifest_t manifest; + if (!read_cache_manifest(cache_root, &manifest, NULL)) + return false; + + if (!cache_manifest_matches_context(&manifest, source_path, + emulators_path) || + manifest.emulator_file_count > SIZE_MAX) { + return false; + } + + int written = snprintf(cache_path, MAX_PATH, "%s/fakelib", cache_root); + struct stat st; + if (written <= 0 || (size_t)written >= MAX_PATH || + lstat(cache_path, &st) != 0 || !S_ISDIR(st.st_mode)) { + cache_path[0] = '\0'; + return false; + } + if (!touch_cache_manifest(cache_root)) { + log_debug(" [EMU] fakelib cache lifetime refresh failed for %s: %s", + title_id, strerror(errno)); + } + *emulator_file_count_out = (size_t)manifest.emulator_file_count; + return true; +} + +static bool resolve_cached_fakelib(const char *title_id, + const char *source_path, + char cache_path[MAX_PATH], + size_t *emulator_file_count_out) { + pthread_mutex_lock(&g_fakelib_cache_mutex); + bool resolved = resolve_cached_fakelib_locked( + title_id, source_path, cache_path, emulator_file_count_out); + pthread_mutex_unlock(&g_fakelib_cache_mutex); + return resolved; +} + static bool cleanup_fakelib_mount(void) { - if (g_fakelib_mount.layer_count == 0) { + if (!fakelib_session_active()) { memset(&g_fakelib_mount, 0, sizeof(g_fakelib_mount)); return true; } @@ -223,14 +1001,14 @@ void sm_fakelib_game_on_exec(pid_t pid, const char *title_id) { return; } - if (g_fakelib_mount.layer_count > 0 && g_fakelib_mount.pid == pid) { + if (fakelib_session_active() && g_fakelib_mount.pid == pid) { log_debug(" [FAKELIB] already tracking pid=%ld for %s", (long)pid, title_id); pthread_mutex_unlock(&g_fakelib_mutex); return; } - if (g_fakelib_mount.layer_count > 0 && g_fakelib_mount.pid != pid) { + if (fakelib_session_active() && g_fakelib_mount.pid != pid) { log_debug(" [FAKELIB] handoff active mount pid=%ld -> pid=%ld (%s)", (long)g_fakelib_mount.pid, (long)pid, title_id); if (!cleanup_fakelib_mount()) { @@ -244,28 +1022,43 @@ void sm_fakelib_game_on_exec(pid_t pid, const char *title_id) { char game_source_path[MAX_PATH]; char global_source_path[MAX_PATH]; char mount_path[MAX_PATH]; - if (!resolve_sandbox_context(title_id, game_source_path, mount_path)) { - pthread_mutex_unlock(&g_fakelib_mutex); - return; - } - + bool has_game = resolve_game_fakelib_source(title_id, game_source_path); bool has_global = resolve_global_fakelib_source(title_id, global_source_path); - bool has_game = (game_source_path[0] != '\0'); if (!has_global && !has_game) { pthread_mutex_unlock(&g_fakelib_mutex); return; } + bool same_source = + has_global && has_game && + strcmp(global_source_path, game_source_path) == 0; + size_t emulator_file_count = 0; + if (has_game) { + char cache_path[MAX_PATH]; + if (resolve_cached_fakelib(title_id, game_source_path, cache_path, + &emulator_file_count)) { + (void)strlcpy(game_source_path, cache_path, sizeof(game_source_path)); + log_debug(" [EMU] using fakelib cache for %s: %s", title_id, + game_source_path); + } + } + same_source = same_source || + (has_global && has_game && + strcmp(global_source_path, game_source_path) == 0); + memset(&g_fakelib_mount, 0, sizeof(g_fakelib_mount)); g_fakelib_mount.pid = pid; + + if (!resolve_sandbox_mount_path(title_id, mount_path)) { + memset(&g_fakelib_mount, 0, sizeof(g_fakelib_mount)); + pthread_mutex_unlock(&g_fakelib_mutex); + return; + } (void)strlcpy(g_fakelib_mount.mount_path, mount_path, sizeof(g_fakelib_mount.mount_path)); bool global_first = runtime_config()->global_fakelib_mount_first; - bool same_source = - has_global && has_game && - strcmp(global_source_path, game_source_path) == 0; if (global_first && has_global && !same_source) { if (!track_fakelib_overlay(title_id, global_source_path, mount_path, "global")) { @@ -293,14 +1086,18 @@ void sm_fakelib_game_on_exec(pid_t pid, const char *title_id) { } } - if (has_game) - notify_system_info_l10n(SM_L10N_GAME_BACKPORTED, title_id); + if (has_game) { + notify_system_info_l10n(emulator_file_count > 0 + ? SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED + : SM_L10N_GAME_BACKPORTED, + title_id); + } pthread_mutex_unlock(&g_fakelib_mutex); } void sm_fakelib_game_on_exit(pid_t pid) { pthread_mutex_lock(&g_fakelib_mutex); - if (g_fakelib_mount.layer_count == 0 || g_fakelib_mount.pid != pid) { + if (!fakelib_session_active() || g_fakelib_mount.pid != pid) { pthread_mutex_unlock(&g_fakelib_mutex); return; } diff --git a/src/sm_filesystem.c b/src/sm_filesystem.c index 3d075c1..d29e549 100644 --- a/src/sm_filesystem.c +++ b/src/sm_filesystem.c @@ -959,7 +959,8 @@ void cleanup_duplicate_title_mounts(void) { } // --- Copy Helpers for Install Action --- -static int copy_param_json_rewrite(const char *src, const char *dst) { +static int copy_param_json_rewrite(const char *src, const char *dst, + bool set_mode, mode_t mode) { FILE *fs = fopen(src, "rb"); if (!fs) return -1; @@ -1008,8 +1009,8 @@ static int copy_param_json_rewrite(const char *src, const char *dst) { return -1; } - int ret = 0; - if (len > 0 && fwrite(buf, 1, len, fd) != len) + int ret = set_mode && fchmod(fileno(fd), mode) != 0 ? -1 : 0; + if (ret == 0 && len > 0 && fwrite(buf, 1, len, fd) != len) ret = -1; if (fclose(fd) != 0) ret = -1; @@ -1023,12 +1024,13 @@ static int copy_param_json_rewrite(const char *src, const char *dst) { return ret; } -int copy_file(const char *src, const char *dst) { +static int copy_file_buffered(const char *src, const char *dst, bool set_mode, + mode_t mode, char *buffer, + size_t buffer_size) { if (strstr(src, "/sce_sys/param.json")) { - return copy_param_json_rewrite(src, dst); + return copy_param_json_rewrite(src, dst, set_mode, mode); } - char buf[64 * 1024]; FILE *fs = fopen(src, "rb"); if (!fs) return -1; @@ -1037,14 +1039,16 @@ int copy_file(const char *src, const char *dst) { fclose(fs); return -1; } - int ret = 0; + int ret = set_mode && fchmod(fileno(fd), mode) != 0 ? -1 : 0; while (true) { - size_t n = fread(buf, 1, sizeof(buf), fs); - if (n > 0 && fwrite(buf, 1, n, fd) != n) { + if (ret != 0) + break; + size_t n = fread(buffer, 1, buffer_size, fs); + if (n > 0 && fwrite(buffer, 1, n, fd) != n) { ret = -1; break; } - if (n < sizeof(buf)) { + if (n < buffer_size) { if (ferror(fs)) ret = -1; break; @@ -1061,8 +1065,22 @@ int copy_file(const char *src, const char *dst) { return ret; } -int copy_dir(const char *src, const char *dst) { - if (mkdir(dst, 0777) != 0 && errno != EEXIST) +static int copy_file_impl(const char *src, const char *dst, bool set_mode, + mode_t mode) { + const size_t buffer_size = 64u * 1024u; + char *buffer = malloc(buffer_size); + if (!buffer) + return -1; + int ret = copy_file_buffered(src, dst, set_mode, mode, buffer, buffer_size); + free(buffer); + return ret; +} + +static int copy_dir_impl(const char *src, const char *dst, bool set_mode, + mode_t mode, char *buffer, size_t buffer_size) { + if (mkdir(dst, set_mode ? mode : 0777) != 0 && errno != EEXIST) + return -1; + if (set_mode && chmod(dst, mode) != 0) return -1; DIR *d = opendir(src); if (!d) @@ -1100,12 +1118,13 @@ int copy_dir(const char *src, const char *dst) { st = lst; } if (S_ISDIR(st.st_mode)) { - if (copy_dir(ss, dd) != 0) { + if (copy_dir_impl(ss, dd, set_mode, mode, buffer, buffer_size) != 0) { ret = -1; break; } } else { - if (copy_file(ss, dd) != 0) { + if (copy_file_buffered(ss, dd, set_mode, mode, buffer, buffer_size) != + 0) { ret = -1; break; } @@ -1116,6 +1135,33 @@ int copy_dir(const char *src, const char *dst) { return ret; } +static int copy_dir_with_options(const char *src, const char *dst, + bool set_mode, mode_t mode) { + const size_t buffer_size = 64u * 1024u; + char *buffer = malloc(buffer_size); + if (!buffer) + return -1; + int ret = copy_dir_impl(src, dst, set_mode, mode, buffer, buffer_size); + free(buffer); + return ret; +} + +int copy_file(const char *src, const char *dst) { + return copy_file_impl(src, dst, false, 0); +} + +int copy_file_with_mode(const char *src, const char *dst, mode_t mode) { + return copy_file_impl(src, dst, true, mode); +} + +int copy_dir(const char *src, const char *dst) { + return copy_dir_with_options(src, dst, false, 0); +} + +int copy_dir_with_mode(const char *src, const char *dst, mode_t mode) { + return copy_dir_with_options(src, dst, true, mode); +} + int remount_system_ex(void) { struct iovec iov[] = { IOVEC_ENTRY("from"), IOVEC_ENTRY("/dev/ssd0.system_ex"), diff --git a/src/sm_scan.c b/src/sm_scan.c index 4360db2..685a4d6 100644 --- a/src/sm_scan.c +++ b/src/sm_scan.c @@ -747,9 +747,9 @@ static bool backport_path_is_dir(const char *backport_path) { S_ISDIR(st.st_mode); } -static bool resolve_backport_path(const char *title_id, - const char *owning_scan_path, - char backport_path[MAX_PATH]) { +bool resolve_backport_path_for_title(const char *title_id, + const char *owning_scan_path, + char backport_path[MAX_PATH]) { if (!title_id || title_id[0] == '\0') return false; @@ -787,7 +787,8 @@ bool mount_backport_overlay_for_title(const char *source_path, return false; char backport_path[MAX_PATH]; - if (!resolve_backport_path(title_id, owning_scan_root, backport_path)) { + if (!resolve_backport_path_for_title(title_id, owning_scan_root, + backport_path)) { return true; } if (is_backport_mount_blocked(backport_path)) diff --git a/src/sm_scanner.c b/src/sm_scanner.c index e761992..2a538d9 100644 --- a/src/sm_scanner.c +++ b/src/sm_scanner.c @@ -38,6 +38,8 @@ #define SCANNER_CONFIG_PROBE_INTERVAL_US 10000000ull #define SCANNER_MANUAL_RELOAD_DEBOUNCE_US 250000ull #define SCANNER_MANUAL_PROBE_INTERVAL_US 10000000ull +#define SCANNER_FAKELIB_CACHE_CLEANUP_INTERVAL_US \ + (24ull * 60ull * 60ull * 1000000ull) typedef enum { SCANNER_WATCH_SCAN_ROOT = 0, @@ -234,6 +236,9 @@ static bool fakelib_runtime_config_changed(const runtime_config_t *old_cfg, old_cfg->global_fakelib_enabled != new_cfg->global_fakelib_enabled || old_cfg->global_fakelib_mount_first != new_cfg->global_fakelib_mount_first || + old_cfg->update_emulators_enabled != + new_cfg->update_emulators_enabled || + strcmp(old_cfg->emulators_path, new_cfg->emulators_path) != 0 || strcmp(old_cfg->global_fakelib_path, new_cfg->global_fakelib_path) != 0 || old_cfg->global_fakelib_exclude_title_count != @@ -1161,6 +1166,7 @@ static int find_due_dirty_scan_root(uint64_t now_us) { static uint64_t compute_next_scan_deadline_us(uint64_t now_us, uint64_t full_resync_due_us, + uint64_t cache_cleanup_due_us, bool include_scan_work) { uint64_t next_deadline = full_resync_due_us; uint64_t install_wake_us = sm_install_next_wake_us(now_us); @@ -1192,6 +1198,10 @@ static uint64_t compute_next_scan_deadline_us(uint64_t now_us, } if (include_scan_work) { + if (cache_cleanup_due_us != 0 && + (next_deadline == 0 || cache_cleanup_due_us < next_deadline)) { + next_deadline = cache_cleanup_due_us; + } for (int i = 0; i < get_scan_path_count(); i++) { const scanner_root_state_t *state = &g_scanner_root_states[i]; if (!state->dirty) @@ -1527,6 +1537,9 @@ void sm_scanner_run_loop(void) { } uint64_t next_full_resync_us = monotonic_time_us() + scanner_full_resync_interval_us(); + uint64_t next_fakelib_cache_cleanup_us = monotonic_time_us(); + if (next_fakelib_cache_cleanup_us == 0) + next_fakelib_cache_cleanup_us = 1; bool was_sleeping = false; bool scan_work_blocked = false; @@ -1719,6 +1732,13 @@ void sm_scanner_run_loop(void) { continue; } + if (!game_mount_busy && now_us >= next_fakelib_cache_cleanup_us) { + sm_fakelib_cleanup_caches(); + next_fakelib_cache_cleanup_us = + monotonic_time_us() + SCANNER_FAKELIB_CACHE_CLEANUP_INTERVAL_US; + continue; + } + if (!game_mount_busy && next_full_resync_us != 0 && now_us >= next_full_resync_us) { bool unstable_found = false; @@ -1819,7 +1839,8 @@ void sm_scanner_run_loop(void) { } uint64_t deadline_us = compute_next_scan_deadline_us( - now_us, next_full_resync_us, !game_mount_busy); + now_us, next_full_resync_us, next_fakelib_cache_cleanup_us, + !game_mount_busy); struct timespec timeout; const struct timespec *timeout_ptr = build_wait_timeout(&timeout, now_us, deadline_us); diff --git a/src/sm_shellcore_service.c b/src/sm_shellcore_service.c index f713d2d..03a6ef4 100644 --- a/src/sm_shellcore_service.c +++ b/src/sm_shellcore_service.c @@ -6,6 +6,7 @@ #include #include "sm_filesystem.h" +#include "sm_fakelib.h" #include "sm_game_lifecycle.h" #include "sm_gameinfo.h" #include "sm_image.h" @@ -390,6 +391,7 @@ static bool prepare_title_runtime(const char *title_id, const char *source_path) { if (!prepare_image_source(title_id, source_path)) return false; + sm_fakelib_prepare_title_cache(title_id, source_path); runtime_mount_state_lock(); bool title_mounted = !runtime_sleep_mode_active() && mount_title_nullfs(title_id, source_path); @@ -425,6 +427,8 @@ static int mount_managed_title_runtime(const char *title_id, if (claim_status != 0) return claim_status; if (!claimed && is_data_mounted(title_id)) { + if (launch_request) + sm_fakelib_prepare_title_cache(title_id, source_path); finish_prepared_title(title_id, false); return 0; }