Compare commits

...
18 Commits
Author SHA1 Message Date
Drakmor 3530d8d88e Merge pull request #107 from blackbearreloaded/main
Add support to auto load and register FAKE title IDs
2026-08-28 21:37:40 +03:00
BlackBearReloaded 4dafdde002 lifecycle: support FAKE title IDs 2026-08-27 22:35:00 -04:00
Roman Tarasov 8566c0294c Revert "Revert "Attempting to restore occupied mount points for nullfs""
This reverts commit 16873e01f2.
2026-06-28 16:53:42 +03:00
Roman Tarasov 56dd338abd Prefer fakelib2 over fakelib for game overlays
Check app0/fakelib2 before resolving the legacy per-game fakelib
source. Fall back to app0/fakelib when fakelib2 is missing or is
not a directory.
2026-06-28 16:49:30 +03:00
Drakmor 090723879c Update README.md 2026-06-07 02:43:46 +03:00
Roman Tarasov 16873e01f2 Revert "Attempting to restore occupied mount points for nullfs"
This reverts commit c41c3eeba8.
2026-06-03 21:30:59 +03:00
Roman Tarasov f20b97db65 funding 2026-06-03 17:53:44 +03:00
Roman Tarasov 9d6ab61369 Added description of compressed PFS format 2026-06-03 17:49:43 +03:00
Roman Tarasov 25182da557 Fix PFS mount 2026-06-03 16:09:11 +03:00
Drakmor 72cf4460a9 Merge pull request #55 from drakmor/alert-autofix-1
Potential fix for code scanning alert no. 1: Workflow does not contain permissions
2026-06-03 02:26:06 +03:00
DrakmorandCopilot Autofix powered by AI cc0f4e0684 Potential fix for code scanning alert no. 1: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-03 01:49:51 +03:00
Roman Tarasov 8cdcd112f5 Nested images 2026-06-03 01:34:23 +03:00
Roman Tarasov c41c3eeba8 Attempting to restore occupied mount points for nullfs 2026-06-03 01:19:38 +03:00
Drakmor 3d6a300742 Update README.md 2026-06-02 11:51:43 +03:00
Roman Tarasov 51d01011ff Fixed mounting of exfat 2026-06-02 10:14:47 +03:00
Roman Tarasov 9e803cff7f Added support for compressed PFS 2026-06-01 21:28:57 +03:00
Roman Tarasov dee175e2bd Added copying of trophy data 2026-06-01 01:12:30 +03:00
Roman Tarasov 860e9de3b1 Fix duplicate uninstall requests during scan cleanup 2026-06-01 00:05:13 +03:00
20 changed files with 551 additions and 82 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
custom:
- https://github.com/drakmor/shadowMountPlus#support-development
+3
View File
@@ -13,6 +13,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: false
permissions:
contents: read
jobs:
build:
outputs:
+102 -8
View File
@@ -2,7 +2,7 @@
**Repository:** https://github.com/drakmor/shadowMountPlus
**Discord:** https://discord.gg/hVnfFMHK
**Discord:** https://discord.gg/x2Ppvzwjhm
**Warning! Mounting images can cause shutdown problems and data corruption on internal drives! This depends on many factors, but is more common with older firmware versions. Please take this into account when testing.**
@@ -11,7 +11,7 @@
**ShadowMountPlus** is a fully automated, background "Auto-Mounter" payload for Jailbroken PlayStation 5 consoles. It streamlines the game mounting process by eliminating the need for manual configuration or external tools (such as DumpRunner or Itemzflow). ShadowMountPlus automatically detects, mounts, and installs game dumps from both **internal and external storage**.
**Compatibility:** Supports all Jailbroken PS5 firmwares running **[Kstuff-lite v1.05+](https://github.com/EchoStretch/kstuff-lite)**.
**Compatibility:** Supports all Jailbroken PS5 firmwares running **[Kstuff-lite v1.07+](https://github.com/EchoStretch/kstuff-lite)**.
## 💜 Support Development
@@ -32,6 +32,7 @@
| `.ffpkg` | `ufs` | `LVD` or `MD` (configurable) | Recommended |
| `.exfat` | `exfatfs` | `LVD` or `MD` (configurable) | Compatibility / external-drive-only titles |
| `.ffpfs` | `pfs` | `LVD` | Experimental |
| `.ffpfsc` | `pfs` container | `LVD` | Experimental container for nested images |
Notes:
- Backend, read-only mode, and sector size can be configured via `/data/shadowmount/config.ini`.
@@ -109,7 +110,7 @@ Per-image sector override behavior:
Scan path behavior:
- If at least one `scanpath=...` is present, only those custom paths are used.
- `/mnt/shadowmnt` is always added automatically, even with custom paths.
- `/mnt/shadowmnt/pfsc` and `/mnt/shadowmnt` are always added automatically, even with custom paths.
- With `scan_depth=1` (default), only first-level subfolders are checked.
- With `scan_depth=2`, one additional nested level is checked.
- If `recursive_scan=1` is set, ShadowMount+ forces `scan_depth=2`.
@@ -123,7 +124,7 @@ 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/<TITLE_ID>_XXX/app0/fakelib` exists while the game is running, ShadowMount+ mounts it into that game's sandbox `common/lib`.
- If `/mnt/sandbox/<TITLE_ID>_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`.
- 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.
@@ -160,12 +161,102 @@ Image mountpoints are created under:
`/mnt/shadowmnt/<image_name>_<hash>`
PFSC container mountpoints are created under:
`/mnt/shadowmnt/pfsc/<image_name>_<hash>`
Image layout requirement (`.ffpkg`, `.exfat`, `.ffpfs`):
- Game files must be placed at the image root.
- Do not add an extra top-level folder inside the image.
- Valid example: `/sce_sys/param.json` exists directly from image root.
- Invalid example: `/GAME_FOLDER/sce_sys/param.json` (extra nesting level).
PFSC container layout requirement (`.ffpfsc`):
- Do not place game files directly in the container root.
- Place supported nested image files inside the container; ShadowMountPlus mounts those nested images and scans them for the game.
- A nested `pfs_image.dat` file inside a PFSC container is treated as a PFS image.
- `.ffpfsc` uses the nested outer PFS profile (`img_type=0x02`); `.ffpfs` and `pfs_image.dat` files mounted from inside it use the nested inner profile (`img_type=0x82`). Signature verification and GDDR5 cache setup are kept in code but currently disabled.
## Compressed PFS containers (`.ffpfsc`)
Compressed PFS mode is intended only for nested images. During packing, data is
zero-padded to a `64 KB` sector boundary, so the compressed PFS should be used
as an outer container for another image rather than as a direct game-file
layout.
Recommended layouts:
- exFAT image inside compressed PFS.
- Uncompressed PFS image inside compressed PFS.
MkPFS uses zLib compression. Decompression is hardware-assisted, but throughput
is limited to roughly `150-250 MB/s`. This is about one third of the speed of an
external USB drive, FFPKG/exFAT images, or about one tenth of the internal drive
speed. Keep this in mind when choosing which games to pack. Games that read large
amounts of data or stream textures continuously may stutter.
Use the official [PSBrew/MkPFS](https://github.com/PSBrew/MkPFS) tool to pack
PFS images.
### Packing an uncompressed PFS image into compressed PFS
First create an uncompressed nested PFS image:
```bash
mkpfs pack folder --verify --no-compress --no-adjust-output-file-extension --version PS5 --inode-bits 32 \
'./PPSA07923/PPSA07923-app' \
'./pfs_image.dat'
```
Then pack the nested image (**pfs_image.dat**) into a compressed PFS container:
```bash
mkpfs pack file --verify --version PS5 --inode-bits 32 \
'./pfs_image.dat' \
'./PPSA12345.ffpfsc'
```
After successful packing, the temporary nested image can be removed:
```bash
rm './pfs_image.dat'
```
### Packing an exFAT image into compressed PFS
First create a normal exFAT image using one of the methods from
`Creating an exFAT image`. The nested exFAT image name must keep the `.exfat`
extension, for example `PPSA12345.exfat`.
Linux:
```bash
chmod +x mkexfat.sh
./mkexfat.sh ./PPSA12345-app ./PPSA12345.exfat
```
Windows:
```cmd
make_image.bat "C:\images\PPSA12345.exfat" "C:\payload\PPSA12345-app"
```
The exFAT image must contain the game files at the image root, without an extra
top-level folder.
Then pack the exFAT image into a compressed PFS container:
```bash
mkpfs pack file --verify --version PS5 --inode-bits 32 \
'./PPSA12345.exfat' \
'./PPSA12345.ffpfsc'
```
After successful packing, the temporary exFAT image can be removed:
```bash
rm './PPSA12345.exfat'
```
## Scan paths
Default scan locations:
@@ -180,6 +271,7 @@ Default scan locations:
- `/mnt/usb0` .. `/mnt/usb7`
- `/mnt/ext0`
- `/mnt/ext1`
- `/mnt/shadowmnt/pfsc` (mounted PFSC container scan)
- `/mnt/shadowmnt` (mounted image content scan)
You can override scan roots with `scanpath=...` entries in `/data/shadowmount/config.ini`.
@@ -193,7 +285,7 @@ checks:
Add one source per line:
- Path to a game folder, where `sce_sys/param.json` exists inside that folder.
- Path to a supported image file: `.ffpkg`, `.exfat`, or `.ffpfs`.
- Path to a supported image file: `.ffpkg`, `.exfat`, `.ffpfs`, or `.ffpfsc`.
- Empty lines and lines starting with `#` are ignored.
Example:
@@ -327,7 +419,7 @@ If a game is not mounted:
- Check `/data/shadowmount/debug.log` and system notifications from ShadowMount+.
- Verify scan roots:
- if `scanpath=...` is set, only these paths are scanned;
- `/mnt/shadowmnt` is always scanned.
- `/mnt/shadowmnt/pfsc` and `/mnt/shadowmnt` are always scanned.
- Verify scan depth:
- `scan_depth=1` scans only first-level subfolders;
- `scan_depth=2` scans one additional nested level;
@@ -335,9 +427,10 @@ If a game is not mounted:
- If logs show `source not stable yet`, adjust `stability_wait_seconds` (or wait for source copy/write to finish).
- Verify game structure:
- folder game: `<GAME_DIR>/sce_sys/param.json`;
- image game (`.ffpkg` / `.exfat` / `.ffpfs`): `sce_sys/param.json` must be at image root (no extra top-level folder).
- image game (`.ffpkg` / `.exfat` / `.ffpfs`): `sce_sys/param.json` must be at image root (no extra top-level folder);
- PFSC container (`.ffpfsc`): nested supported image files are scanned; direct game files inside the container are ignored.
- If you see `missing/invalid param.json` for an image, check via FTP that files are present under `/mnt/shadowmnt/<image_name>_<hash>/` and include `sce_sys/param.json`.
- If you see image mount failure, check image integrity and filesystem type (`.ffpkg`=UFS, `.exfat`=exFAT, `.ffpfs`=PFS).
- If you see image mount failure, check image integrity and filesystem type (`.ffpkg`=UFS, `.exfat`=exFAT, `.ffpfs`=PFS, `.ffpfsc`=PFS container).
- If you see duplicate titleId notification, keep only one source per `<TITLE_ID>`.
If a game is mounted but does not start:
@@ -359,5 +452,6 @@ If a game is mounted but does not start:
* Gezine
* earthonion
* LightningMods
* RenanGBarreto for his excellent https://github.com/PSBrew/MkPFS
* john-tornblom for SDK
* PS5 R&D Community
+2 -2
View File
@@ -67,7 +67,7 @@
# ufs_backend=lvd
# Sandbox fakelib backport watcher:
# 1/true/yes/on -> mount /mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib into common/lib
# 1/true/yes/on -> mount app0/fakelib2 when present, otherwise app0/fakelib, into common/lib
# 0/false/no/off -> disable fakelib watcher
# Default: 1
# backport_fakelib=1
@@ -120,7 +120,7 @@
# Optional custom scan roots (can be repeated):
# If at least one scanpath=... is present, only these paths are used.
# "/mnt/shadowmnt" is always added automatically.
# "/mnt/shadowmnt/pfsc" and "/mnt/shadowmnt" are always added automatically.
# Default: use built-in scan path list
# scanpath=/data/homebrew
+2
View File
@@ -18,6 +18,8 @@ const runtime_config_t *runtime_config(void);
int get_scan_path_count(void);
// Return a scan root by index, or NULL if out of range.
const char *get_scan_path(int index);
// Return scan depth for a root, including managed container-root expansion.
uint32_t get_scan_depth_for_root(const char *scan_path);
// Resolve a per-image read-only override from the file name.
bool get_image_mode_override(const char *filename, bool *mount_read_only_out);
// Resolve a per-image sector-size override from autotune.ini or config.ini.
+2
View File
@@ -29,5 +29,7 @@ void get_image_mount_point_for_source(const char *file_path,
char mount_point[MAX_PATH]);
// Return true when the filename has a supported image extension.
bool is_supported_image_file_name(const char *name);
// Return true when the source path is a supported image, including nested names.
bool is_supported_image_file_path(const char *full_path, const char *name);
#endif
+4 -1
View File
@@ -45,6 +45,8 @@
#define LVD_ATTACH_IMAGE_TYPE_SINGLE 0
#define LVD_ATTACH_IMAGE_TYPE_UFS_DOWNLOAD_DATA 7
#define LVD_ATTACH_IMAGE_TYPE_PFS_SAVE_DATA 5
#define PFS_NESTED_OUTER_IMG_TYPE 0x02u
#define PFS_NESTED_INNER_IMG_TYPE 0x82u
#define LVD_ATTACH_LAYER_COUNT 1
#define LVD_ATTACH_LAYER_ARRAY_SIZE 3
#define LVD_ENTRY_TYPE_FILE 1
@@ -69,10 +71,11 @@
#define DEVPFS_MKEYMODE_GD "GD"
#define DEVPFS_MKEYMODE_AC "AC"
#define PFS_MOUNT_BUDGET_ID DEVPFS_BUDGET_GAME
#define PFS_MOUNT_MKEYMODE DEVPFS_MKEYMODE_SD
#define PFS_MOUNT_MKEYMODE DEVPFS_MKEYMODE_AC
#define PFS_MOUNT_SIGVERIFY 0
#define PFS_MOUNT_PLAYGO 0
#define PFS_MOUNT_DISC 0
#define PFS_GDDR5_CACHE_IOCTL 0x80209168
// 4x64-bit PFS key encoded as 64 hex chars.
#define PFS_ZERO_EKPFS_KEY_HEX \
"0000000000000000000000000000000000000000000000000000000000000000"
+2
View File
@@ -12,6 +12,8 @@ const char *get_filename_component(const char *path);
bool path_exists(const char *path);
// Return true when a path lives under the image mount root.
bool is_under_image_mount_base(const char *path);
// Return true when a path is the PFSC container mount root or lives under it.
bool is_pfsc_image_mount_base_or_child(const char *path);
// Return true when path is equal to root or is under it.
bool path_matches_root_or_child(const char *path, const char *root);
// Return true when a path lives on USB-backed storage.
+1
View File
@@ -2,6 +2,7 @@
#define SM_PATHS_H
#define IMAGE_MOUNT_BASE "/mnt/shadowmnt"
#define PFSC_IMAGE_MOUNT_BASE "/mnt/shadowmnt/pfsc"
#define DEFAULT_BACKPORTS_DIR_NAME "backports"
#define DEFAULT_GLOBAL_FAKELIB_PATH "/data/shadowmount/fakelib"
+1
View File
@@ -138,6 +138,7 @@ typedef enum {
IMAGE_FS_UFS,
IMAGE_FS_EXFAT,
IMAGE_FS_PFS,
IMAGE_FS_PFSC_CONTAINER,
} image_fs_type_t;
typedef struct sm_error {
+16 -3
View File
@@ -224,11 +224,16 @@ static bool add_runtime_scan_path(runtime_config_state_t *state,
return true;
}
static void add_runtime_managed_scan_paths(runtime_config_state_t *state) {
(void)add_runtime_scan_path(state, PFSC_IMAGE_MOUNT_BASE);
(void)add_runtime_scan_path(state, IMAGE_MOUNT_BASE);
}
static void init_runtime_scan_paths_defaults(runtime_config_state_t *state) {
clear_runtime_scan_paths(state);
for (int i = 0; k_default_scan_paths[i] != NULL; i++)
(void)add_runtime_scan_path(state, k_default_scan_paths[i]);
(void)add_runtime_scan_path(state, IMAGE_MOUNT_BASE);
add_runtime_managed_scan_paths(state);
}
static void clear_kstuff_title_rules(runtime_config_state_t *state) {
@@ -353,6 +358,15 @@ const char *get_scan_path(int index) {
return state->scan_path_storage[index];
}
uint32_t get_scan_depth_for_root(const char *scan_path) {
uint32_t scan_depth = runtime_config()->scan_depth;
if (scan_depth < MIN_SCAN_DEPTH)
scan_depth = MIN_SCAN_DEPTH;
if (is_pfsc_image_mount_base_or_child(scan_path))
scan_depth++;
return scan_depth;
}
bool get_image_mode_override(const char *filename, bool *mount_read_only_out) {
ensure_runtime_config_ready();
if (!filename || !mount_read_only_out)
@@ -1446,9 +1460,8 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
if (has_custom_scanpaths && state->scan_path_count == 0) {
log_debug(" [CFG] no valid scanpath entries, using defaults");
init_runtime_scan_paths_defaults(state);
} else {
(void)add_runtime_scan_path(state, IMAGE_MOUNT_BASE);
}
add_runtime_managed_scan_paths(state);
if (legacy_recursive_scan_requested) {
state->cfg.scan_depth = 2u;
+8 -2
View File
@@ -125,11 +125,17 @@ static bool resolve_sandbox_context(const char *title_id,
return false;
char source_path[MAX_PATH];
snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib",
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))
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);
+56 -2
View File
@@ -594,6 +594,58 @@ static bool unmount_controlled_mount_stack(const char *path) {
return !get_top_mount(path, &mount_st);
}
static void unmount_mount_point_for_recovery(const char *path) {
if (!unmount_controlled_mount_stack(path)) {
log_debug(" [LINK] failed to reset mount stack for recovery: %s", path);
}
if (unmount(path, 0) == 0) {
log_debug(" [LINK] extra unmount for recovery: %s", path);
return;
}
if (errno == ENOENT || errno == EINVAL)
return;
if (unmount(path, MNT_FORCE) == 0) {
log_debug(" [LINK] extra forced unmount for recovery: %s", path);
return;
}
if (errno == ENOENT || errno == EINVAL)
return;
log_debug(" [LINK] extra unmount for recovery did not complete for %s: %s",
path, strerror(errno));
}
static bool recover_mount_directory_after_stat_failure(
const char *path, struct stat *st_out) {
log_debug(" [LINK] resetting mount stack after stat failure: %s", path);
unmount_mount_point_for_recovery(path);
if (stat(path, st_out) == 0)
return true;
int stat_errno = errno;
if (stat_errno != ENOENT) {
log_debug(" [LINK] mount directory stat failed after reset for %s: %s",
path, strerror(stat_errno));
return false;
}
if (mkdir(path, 0755) != 0 && errno != EEXIST) {
log_debug(" [LINK] failed to recreate mount directory %s: %s", path,
strerror(errno));
return false;
}
if (stat(path, st_out) == 0)
return true;
log_debug(" [LINK] mount directory stat failed after recreate for %s: %s",
path, strerror(errno));
return false;
}
static bool cleanup_duplicate_title_mounts_entry(const char *title_id,
const title_link_paths_t *paths,
void *ctx) {
@@ -845,9 +897,11 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
}
struct stat dst_st;
if (stat(dst, &dst_st) != 0) {
int stat_errno = errno;
log_debug(" [LINK] mount directory exists but stat failed for %s: %s",
dst, strerror(errno));
return false;
dst, strerror(stat_errno));
if (!recover_mount_directory_after_stat_failure(dst, &dst_st))
return false;
}
if (!S_ISDIR(dst_st.st_mode)) {
log_debug(" [LINK] mount target is not a directory: %s mode=0%o", dst,
+3 -1
View File
@@ -78,7 +78,9 @@ static bool consume_active_game_title(pid_t pid,
}
static bool is_supported_title_id(const char *title_id) {
return strncmp(title_id, "PPSA", 4) == 0 || strncmp(title_id, "CUSA", 4) == 0;
return strncmp(title_id, "PPSA", 4) == 0 ||
strncmp(title_id, "CUSA", 4) == 0 ||
strncmp(title_id, "FAKE", 4) == 0;
}
static uint64_t min_nonzero_u64(uint64_t a, uint64_t b) {
+236 -25
View File
@@ -14,12 +14,45 @@
#include "sm_path_utils.h"
#include "sm_paths.h"
static bool image_fs_type_is_pfs(image_fs_type_t fs_type) {
return fs_type == IMAGE_FS_PFS || fs_type == IMAGE_FS_PFSC_CONTAINER;
}
static bool pfs_path_is_nested_inner(const char *path, image_fs_type_t fs_type) {
return fs_type == IMAGE_FS_PFS && is_pfsc_image_mount_base_or_child(path);
}
static bool pfs_path_uses_nested_profile(const char *path,
image_fs_type_t fs_type) {
return fs_type == IMAGE_FS_PFSC_CONTAINER ||
pfs_path_is_nested_inner(path, fs_type);
}
static uint8_t get_nested_pfs_img_type(const char *path,
image_fs_type_t fs_type) {
if (pfs_path_is_nested_inner(path, fs_type))
return PFS_NESTED_INNER_IMG_TYPE;
return PFS_NESTED_OUTER_IMG_TYPE;
}
static uint16_t pfs_lvd_game_image_type_from_selector(uint8_t selector) {
static const uint16_t table[8] = {1, 2, 3, 4, 8, 9, 10, 11};
uint32_t idx = (((uint32_t)selector >> 7) & 1u) +
2u * ((uint32_t)selector & 0x1Fu);
idx ^= 1u;
if (idx >= 8u)
return LVD_ATTACH_IMAGE_TYPE_PFS_SAVE_DATA;
return table[idx];
}
static uint32_t get_lvd_sector_size_fallback(image_fs_type_t fs_type) {
const runtime_config_t *cfg = runtime_config();
switch (fs_type) {
case IMAGE_FS_UFS:
return cfg->lvd_sector_ufs;
case IMAGE_FS_PFS:
case IMAGE_FS_PFSC_CONTAINER:
return cfg->lvd_sector_pfs;
case IMAGE_FS_EXFAT:
default:
@@ -39,8 +72,15 @@ static uint32_t get_image_sector_size_override_or_default(
}
static uint32_t get_lvd_sector_size(const char *path, image_fs_type_t fs_type) {
bool nested_pfs_profile = pfs_path_uses_nested_profile(path, fs_type);
uint32_t default_sector =
nested_pfs_profile ? LVD_SECTOR_SIZE_PFS
: get_lvd_sector_size_fallback(fs_type);
uint32_t fallback = get_image_sector_size_override_or_default(
path, get_lvd_sector_size_fallback(fs_type));
path, default_sector);
if (nested_pfs_profile)
return fallback;
struct statfs sfs;
if (statfs(path, &sfs) != 0)
return fallback;
@@ -91,7 +131,7 @@ static unsigned int get_md_attach_options(bool mount_read_only) {
static uint16_t get_lvd_attach_raw_flags(image_fs_type_t fs_type,
bool mount_read_only) {
if (fs_type == IMAGE_FS_UFS) {
if (fs_type == IMAGE_FS_UFS) {
return mount_read_only ? LVD_ATTACH_RAW_FLAGS_DD_RO
: LVD_ATTACH_RAW_FLAGS_DD_RW;
}
@@ -123,10 +163,14 @@ static uint16_t normalize_lvd_raw_flags(uint16_t raw_flags) {
return (uint16_t)(8u * ((uint32_t)raw_flags & 1u) + 4u);
}
static uint16_t get_lvd_image_type(image_fs_type_t fs_type) {
static uint16_t get_lvd_image_type(const char *path, image_fs_type_t fs_type) {
if (fs_type == IMAGE_FS_UFS)
return LVD_ATTACH_IMAGE_TYPE_UFS_DOWNLOAD_DATA;
if (fs_type == IMAGE_FS_PFS)
if (pfs_path_uses_nested_profile(path, fs_type)) {
return pfs_lvd_game_image_type_from_selector(
get_nested_pfs_img_type(path, fs_type));
}
if (image_fs_type_is_pfs(fs_type))
return LVD_ATTACH_IMAGE_TYPE_PFS_SAVE_DATA;
return LVD_ATTACH_IMAGE_TYPE_SINGLE;
}
@@ -145,6 +189,9 @@ static uint16_t get_lvd_source_type(const char *path) {
// --- Image Path and Naming Helpers ---
static image_fs_type_t detect_image_fs_type(const char *name) {
if (!name || name[0] == '\0')
return IMAGE_FS_UNKNOWN;
const char *dot = strrchr(name, '.');
if (!dot)
return IMAGE_FS_UNKNOWN;
@@ -154,6 +201,30 @@ static image_fs_type_t detect_image_fs_type(const char *name) {
return IMAGE_FS_EXFAT;
if (strcasecmp(dot, ".ffpfs") == 0)
return IMAGE_FS_PFS;
if (strcasecmp(dot, ".ffpfsc") == 0)
return IMAGE_FS_PFSC_CONTAINER;
return IMAGE_FS_UNKNOWN;
}
static bool is_nested_pfs_image_file_name(const char *name) {
return name && strcasecmp(name, "pfs_image.dat") == 0;
}
static image_fs_type_t detect_image_fs_type_for_path(const char *path,
const char *name) {
const char *filename = name;
if ((!filename || filename[0] == '\0') && path)
filename = get_filename_component(path);
image_fs_type_t fs_type = detect_image_fs_type(filename);
if (fs_type != IMAGE_FS_UNKNOWN)
return fs_type;
if (is_nested_pfs_image_file_name(filename) &&
is_pfsc_image_mount_base_or_child(path)) {
return IMAGE_FS_PFS;
}
return IMAGE_FS_UNKNOWN;
}
@@ -161,6 +232,10 @@ bool is_supported_image_file_name(const char *name) {
return detect_image_fs_type(name) != IMAGE_FS_UNKNOWN;
}
bool is_supported_image_file_path(const char *full_path, const char *name) {
return detect_image_fs_type_for_path(full_path, name) != IMAGE_FS_UNKNOWN;
}
static const char *image_fs_name(image_fs_type_t fs_type) {
switch (fs_type) {
case IMAGE_FS_UFS:
@@ -169,6 +244,8 @@ static const char *image_fs_name(image_fs_type_t fs_type) {
return "exfatfs";
case IMAGE_FS_PFS:
return "pfs";
case IMAGE_FS_PFSC_CONTAINER:
return "pfsc";
default:
return "unknown";
}
@@ -220,8 +297,9 @@ static void strip_extension(const char *filename, char *out, size_t out_size) {
out[len] = '\0';
}
static void build_image_mount_point(const char *file_path,
char mount_point[MAX_PATH]) {
static void build_image_mount_point_for_fs(const char *file_path,
image_fs_type_t fs_type,
char mount_point[MAX_PATH]) {
const char *filename = get_filename_component(file_path);
char base_name[MAX_PATH];
char mount_name[MAX_PATH];
@@ -236,7 +314,16 @@ static void build_image_mount_point(const char *file_path,
snprintf(mount_name + base_len, sizeof(mount_name) - base_len, "_%08x",
sm_fnv1a32(file_path));
snprintf(mount_point, MAX_PATH, "%s/%s", IMAGE_MOUNT_BASE, mount_name);
const char *mount_base =
fs_type == IMAGE_FS_PFSC_CONTAINER ? PFSC_IMAGE_MOUNT_BASE
: IMAGE_MOUNT_BASE;
snprintf(mount_point, MAX_PATH, "%s/%s", mount_base, mount_name);
}
static void build_image_mount_point(const char *file_path,
char mount_point[MAX_PATH]) {
build_image_mount_point_for_fs(
file_path, detect_image_fs_type_for_path(file_path, NULL), mount_point);
}
void get_image_mount_point_for_source(const char *file_path,
@@ -315,6 +402,35 @@ static bool attach_lvd_backend(const char *file_path, image_fs_type_t fs_type,
bool mount_read_only, off_t file_size,
int *unit_id_out, char *devname_out,
size_t devname_size) {
/* if (pfs_path_is_nested_inner(file_path, fs_type)) {
struct stat req;
memset(&req, 0, sizeof(req));
req.st_dev = 1;
req.st_mode = 0;
req.st_gid = 1;
req.st_atim.tv_sec = 0;
int cache_fd = open(file_path, O_RDONLY);
if (cache_fd < 0) {
log_debug(" [IMG][%s] GDDR5 cache open failed for %s: %s",
attach_backend_name(ATTACH_BACKEND_LVD), file_path,
strerror(errno));
return false;
}
int cache_ret = ioctl(cache_fd, PFS_GDDR5_CACHE_IOCTL, &req);
int cache_errno = cache_ret != 0 ? errno : 0;
close(cache_fd);
if (cache_ret != 0) {
errno = cache_errno;
log_debug(" [IMG][%s] GDDR5 cache ioctl failed for %s: %s",
attach_backend_name(ATTACH_BACKEND_LVD), file_path,
strerror(errno));
return false;
}
log_debug(" [IMG][%s] GDDR5 cache enabled for nested PFS: %s",
attach_backend_name(ATTACH_BACKEND_LVD), file_path);
}
*/
int lvd_fd = open(LVD_CTRL_PATH, O_RDWR);
if (lvd_fd < 0) {
log_debug(" [IMG][%s] open %s failed: %s",
@@ -339,7 +455,7 @@ static bool attach_lvd_backend(const char *file_path, image_fs_type_t fs_type,
lvd_ioctl_attach_v0_t req;
memset(&req, 0, sizeof(req));
req.io_version = LVD_ATTACH_IO_VERSION_V0;
req.image_type = get_lvd_image_type(fs_type);
req.image_type = get_lvd_image_type(file_path, fs_type);
req.layer_count = LVD_ATTACH_LAYER_COUNT;
req.device_size = (uint64_t)file_size;
req.layers_ptr = layers;
@@ -573,6 +689,8 @@ static bool stat_image_file(const char *file_path, struct stat *st_out) {
static void ensure_mount_dirs(const char *mount_point) {
mkdir(IMAGE_MOUNT_BASE, 0777);
if (is_pfsc_image_mount_base_or_child(mount_point))
mkdir(PFSC_IMAGE_MOUNT_BASE, 0777);
mkdir(mount_point, 0777);
}
@@ -617,7 +735,20 @@ static bool perform_image_nmount(const char *file_path, image_fs_type_t fs_type,
const char *sigverify = PFS_MOUNT_SIGVERIFY ? "1" : "0";
const char *playgo = PFS_MOUNT_PLAYGO ? "1" : "0";
const char *disc = PFS_MOUNT_DISC ? "1" : "0";
const char *finalized = "0";
const char *pubkey_ver = "0";
const char *key_ver = "0";
const char *ekpfs_key = PFS_ZERO_EKPFS_KEY_HEX;
uint8_t nested_img_type = 0;
bool nested_pfs_profile = pfs_path_uses_nested_profile(file_path, fs_type);
if (nested_pfs_profile) {
nested_img_type = get_nested_pfs_img_type(file_path, fs_type);
// Temporarily disabled: keep nested PFS signature option wired but do not
// force sigverify=1.
// sigverify = "1";
disc = (nested_img_type & 0x40u) ? "1" : "0";
finalized = (nested_img_type & 0x20u) ? "1" : "0";
}
struct iovec iov_ufs[] = {
IOVEC_ENTRY("fstype"), IOVEC_ENTRY("ufs"),
@@ -661,19 +792,56 @@ static bool perform_image_nmount(const char *file_path, image_fs_type_t fs_type,
IOVEC_ENTRY("errmsg"), {(void *)mount_errmsg, sizeof(mount_errmsg)},
IOVEC_ENTRY("force"), IOVEC_ENTRY(NULL)};
struct iovec iov_nested_pfs[] = {
IOVEC_ENTRY("from"), IOVEC_ENTRY(devname),
IOVEC_ENTRY("fspath"), IOVEC_ENTRY(mount_point),
IOVEC_ENTRY("fstype"), IOVEC_ENTRY("pfs"),
IOVEC_ENTRY("sigverify"), IOVEC_ENTRY(sigverify),
IOVEC_ENTRY("mkeymode"), IOVEC_ENTRY(PFS_MOUNT_MKEYMODE),
IOVEC_ENTRY("budgetid"), IOVEC_ENTRY(PFS_MOUNT_BUDGET_ID),
IOVEC_ENTRY("playgo"), IOVEC_ENTRY(playgo),
IOVEC_ENTRY("disc"), IOVEC_ENTRY(disc),
// IOVEC_ENTRY("pubkey_ver"), IOVEC_ENTRY(pubkey_ver),
// IOVEC_ENTRY("key_ver"), IOVEC_ENTRY(key_ver),
// IOVEC_ENTRY("finalized"), IOVEC_ENTRY(finalized),
IOVEC_ENTRY("ekpfs"), IOVEC_ENTRY(ekpfs_key),
IOVEC_ENTRY("async"), IOVEC_ENTRY(NULL),
IOVEC_ENTRY("noatime"), IOVEC_ENTRY(NULL),
IOVEC_ENTRY("automounted"), IOVEC_ENTRY(NULL),
IOVEC_ENTRY("errmsg"), {(void *)mount_errmsg, sizeof(mount_errmsg)},
IOVEC_ENTRY("force"), IOVEC_ENTRY(NULL)};
if (fs_type == IMAGE_FS_UFS) {
iov = iov_ufs;
iovlen = (unsigned int)IOVEC_SIZE(iov_ufs) - (force_mount ? 0u : 2u);
} else if (fs_type == IMAGE_FS_EXFAT) {
iov = iov_exfat;
iovlen = (unsigned int)IOVEC_SIZE(iov_exfat) - (force_mount ? 0u : 2u);
} else if (fs_type == IMAGE_FS_PFS) {
log_debug(" [IMG][%s] PFS ro=%d budgetid=%s mkeymode=%s "
"sigverify=%s playgo=%s disc=%s ekpfs=zero",
attach_backend_name(attach_backend), mount_read_only ? 1 : 0,
PFS_MOUNT_BUDGET_ID, PFS_MOUNT_MKEYMODE, sigverify, playgo, disc);
iov = iov_pfs;
iovlen = (unsigned int)IOVEC_SIZE(iov_pfs) - (force_mount ? 0u : 2u);
} else if (image_fs_type_is_pfs(fs_type)) {
if (nested_pfs_profile) {
log_debug(" [IMG][%s] PFS ro=%d budgetid=%s mkeymode=%s "
"sigverify=%s playgo=%s disc=%s finalized=%s "
"pubkey_ver=%s key_ver=%s",
attach_backend_name(attach_backend), mount_read_only ? 1 : 0,
PFS_MOUNT_BUDGET_ID, PFS_MOUNT_MKEYMODE, sigverify, playgo,
disc, finalized, pubkey_ver, key_ver);
log_debug(" [IMG][%s] nested PFS profile: selector=0x%02x "
"lvd_img=%u gddr5=%d",
attach_backend_name(attach_backend), nested_img_type,
get_lvd_image_type(file_path, fs_type),
pfs_path_is_nested_inner(file_path, fs_type) ? 1 : 0);
iov = iov_nested_pfs;
iovlen =
(unsigned int)IOVEC_SIZE(iov_nested_pfs) - (force_mount ? 0u : 2u);
} else {
log_debug(" [IMG][%s] PFS ro=%d budgetid=%s mkeymode=%s "
"sigverify=%s playgo=%s disc=%s ekpfs=zero",
attach_backend_name(attach_backend), mount_read_only ? 1 : 0,
PFS_MOUNT_BUDGET_ID, PFS_MOUNT_MKEYMODE, sigverify, playgo,
disc);
iov = iov_pfs;
iovlen = (unsigned int)IOVEC_SIZE(iov_pfs) - (force_mount ? 0u : 2u);
}
} else {
log_debug(" [IMG][%s] unsupported fstype=%s",
attach_backend_name(attach_backend), image_fs_name(fs_type));
@@ -771,12 +939,14 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) {
if (filename[0] != '\0')
mount_mode_overridden =
get_image_mode_override(filename, &mount_read_only);
if (pfs_path_uses_nested_profile(file_path, fs_type))
mount_read_only = true;
if (runtime_sleep_mode_active())
return false;
char mount_point[MAX_PATH];
build_image_mount_point(file_path, mount_point);
build_image_mount_point_for_fs(file_path, fs_type, mount_point);
bool handled = false;
bool success = false;
@@ -869,9 +1039,36 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) {
return true;
}
static bool unmount_child_image_mounts_for_container(const char *mount_point) {
bool all_unmounted = true;
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
image_cache_entry_t cached_entry;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (!path_matches_root_or_child(cached_entry.path, mount_point))
continue;
log_debug(" [IMG] container child unmount: %s source=%s",
mount_point, cached_entry.path);
if (unmount_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
invalidate_image_cache_entry(k);
continue;
}
all_unmounted = false;
log_debug(" [IMG] container child unmount pending: %s source=%s",
mount_point, cached_entry.path);
}
return all_unmounted;
}
bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend) {
char mount_point[MAX_PATH];
build_image_mount_point(file_path, mount_point);
image_fs_type_t fs_type = detect_image_fs_type_for_path(file_path, NULL);
build_image_mount_point_for_fs(file_path, fs_type, mount_point);
int resolved_unit = unit_id;
attach_backend_t resolved_backend = backend;
@@ -887,6 +1084,11 @@ bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend)
attach_backend_name(resolved_backend), file_path, mount_point,
resolved_unit);
if (fs_type == IMAGE_FS_PFSC_CONTAINER &&
!unmount_child_image_mounts_for_container(mount_point)) {
return false;
}
// Remove mount.lnk and unmount /system_ex/app/<titleid> that point to this
// source before unmounting the virtual disk itself.
cleanup_mount_links_for_source_unmount(mount_point);
@@ -975,7 +1177,8 @@ void cleanup_stale_image_mounts(void) {
continue;
}
image_fs_type_t fs_type = detect_image_fs_type(cached_entry.path);
image_fs_type_t fs_type =
detect_image_fs_type_for_path(cached_entry.path, NULL);
char mount_point[MAX_PATH];
char source_path[MAX_PATH];
if (!prepare_image_mount_retry(&cached_entry, mount_point, source_path))
@@ -1024,7 +1227,8 @@ void cleanup_stale_image_mounts_for_root(const char *root) {
continue;
}
image_fs_type_t fs_type = detect_image_fs_type(cached_entry.path);
image_fs_type_t fs_type =
detect_image_fs_type_for_path(cached_entry.path, NULL);
char mount_point[MAX_PATH];
char source_path[MAX_PATH];
if (!prepare_image_mount_retry(&cached_entry, mount_point, source_path))
@@ -1071,11 +1275,11 @@ bool unmount_usb_image_mounts_for_suspend(void) {
return all_unmounted;
}
void cleanup_mount_dirs(void) {
DIR *d = opendir(IMAGE_MOUNT_BASE);
static void cleanup_mount_dirs_under(const char *base, const char *skip_name) {
DIR *d = opendir(base);
if (!d) {
if (errno != ENOENT)
log_debug(" [IMG] open %s failed: %s", IMAGE_MOUNT_BASE, strerror(errno));
log_debug(" [IMG] open %s failed: %s", base, strerror(errno));
return;
}
@@ -1085,10 +1289,11 @@ void cleanup_mount_dirs(void) {
break;
if (entry->d_name[0] == '.')
continue;
if (skip_name && strcmp(entry->d_name, skip_name) == 0)
continue;
char full_path[MAX_PATH];
snprintf(full_path, sizeof(full_path), "%s/%s", IMAGE_MOUNT_BASE,
entry->d_name);
snprintf(full_path, sizeof(full_path), "%s/%s", base, entry->d_name);
bool is_dir = false;
if (entry->d_type == DT_DIR) {
@@ -1114,9 +1319,15 @@ void cleanup_mount_dirs(void) {
closedir(d);
}
void cleanup_mount_dirs(void) {
cleanup_mount_dirs_under(PFSC_IMAGE_MOUNT_BASE, NULL);
cleanup_mount_dirs_under(IMAGE_MOUNT_BASE, "pfsc");
}
bool maybe_mount_image_file(const char *full_path, const char *display_name,
bool *unstable_out) {
image_fs_type_t fs_type = detect_image_fs_type(display_name);
image_fs_type_t fs_type =
detect_image_fs_type_for_path(full_path, display_name);
if (fs_type == IMAGE_FS_UNKNOWN)
return false;
if (!is_source_stable_for_mount(full_path, display_name, "IMG")) {
+58 -11
View File
@@ -128,6 +128,57 @@ static bool copy_sce_sys_to_appmeta(const char *src_sce_sys,
if (closedir(d) != 0)
ok = false;
return ok;
}
static bool copy_optional_trophy_metadata_file(const char *src_sce_sys,
const char *dst_base,
const char *relative_path) {
char src_path[MAX_PATH];
char dst_path[MAX_PATH];
snprintf(src_path, sizeof(src_path), "%s/%s", src_sce_sys, relative_path);
if (access(src_path, F_OK) != 0)
return true;
snprintf(dst_path, sizeof(dst_path), "%s/%s", dst_base, relative_path);
if (access(dst_path, F_OK) == 0)
return true;
if (copy_file(src_path, dst_path) == 0)
return true;
log_debug(" [COPY] Failed to copy trophy metadata: %s -> %s", src_path,
dst_path);
return false;
}
static bool update_trophy_metadata(const char *title_id,
const char *src_sce_sys) {
char dst_base[MAX_PATH];
char dst_dir[MAX_PATH];
bool ok = true;
snprintf(dst_base, sizeof(dst_base), "/system_data/priv/appmeta/%s",
title_id);
mkdir("/system_data/priv/appmeta", 0755);
mkdir(dst_base, 0755);
snprintf(dst_dir, sizeof(dst_dir), "%s/trophy2", dst_base);
mkdir(dst_dir, 0755);
snprintf(dst_dir, sizeof(dst_dir), "%s/uds", dst_base);
mkdir(dst_dir, 0755);
if (!copy_optional_trophy_metadata_file(src_sce_sys, dst_base,
"trophy2/npbind.dat"))
ok = false;
if (!copy_optional_trophy_metadata_file(src_sce_sys, dst_base,
"uds/npbind.dat"))
ok = false;
if (!copy_optional_trophy_metadata_file(src_sce_sys, dst_base, "param.json"))
ok = false;
return ok;
}
@@ -163,6 +214,7 @@ static bool mount_and_install(const char *src_path, const char *title_id,
}
}
snprintf(src_sce_sys, sizeof(src_sce_sys), "%s/sce_sys", src_path);
appmeta_missing = !has_appmeta_data(title_id);
if (is_remount && appmeta_missing) {
log_debug(" [REG] appmeta missing, restaging metadata only: %s", title_id);
@@ -175,13 +227,10 @@ static bool mount_and_install(const char *src_path, const char *title_id,
return false;
// COPY FILES
if (restage_staging || restage_appmeta) {
snprintf(src_sce_sys, sizeof(src_sce_sys), "%s/sce_sys", src_path);
snprintf(src_snd0, sizeof(src_snd0), "%s/snd0.at9", src_sce_sys);
has_src_snd0 = path_exists(src_snd0);
} else {
snprintf(src_snd0, sizeof(src_snd0), "%s/snd0.at9", src_sce_sys);
has_src_snd0 = path_exists(src_snd0);
if (!restage_staging && !restage_appmeta)
log_debug(" [SPEED] Skipping file copy (Assets already exist)");
}
if (restage_staging) {
mkdir(APP_BASE, 0777);
@@ -216,6 +265,9 @@ static bool mount_and_install(const char *src_path, const char *title_id,
metadata_restaged = true;
}
if (!update_trophy_metadata(title_id, src_sce_sys))
return false;
if (should_stop_requested() || runtime_sleep_mode_active())
return false;
@@ -281,11 +333,6 @@ static bool mount_and_install(const char *src_path, const char *title_id,
return true;
}
if (!metadata_restaged) {
snprintf(src_snd0, sizeof(src_snd0), "%s/sce_sys/snd0.at9", src_path);
has_src_snd0 = path_exists(src_snd0);
}
if (use_app_install_all) {
if (has_src_snd0_out)
*has_src_snd0_out = has_src_snd0;
+4
View File
@@ -20,6 +20,10 @@ bool is_under_image_mount_base(const char *path) {
path[image_prefix_len] == '/');
}
bool is_pfsc_image_mount_base_or_child(const char *path) {
return path_matches_root_or_child(path, PFSC_IMAGE_MOUNT_BASE);
}
bool path_matches_root_or_child(const char *path, const char *root) {
if (!path || !root || root[0] == '\0')
return false;
+40 -18
View File
@@ -69,15 +69,15 @@ static bool is_blocked_ppsa_title(
app_db_title_list_contains(blocked_ppsa_titles, title_id);
}
static void request_blocked_ppsa_uninstall(const char *title_id,
static bool request_blocked_ppsa_uninstall(const char *title_id,
const char *source_path) {
if (!title_id || title_id[0] == '\0' ||
blocked_ppsa_uninstall_requested(title_id))
return;
return false;
remember_blocked_ppsa_uninstall(title_id);
int res = sceAppInstUtilAppUnInstall(title_id);
if (res == 0) {
remember_blocked_ppsa_uninstall(title_id);
sm_install_forget_pending_title(title_id);
invalidate_app_db_title_cache();
if (source_path && source_path[0] != '\0') {
@@ -86,11 +86,12 @@ static void request_blocked_ppsa_uninstall(const char *title_id,
} else {
log_debug(" [REG] requested blocked PPSA uninstall: %s", title_id);
}
return;
return true;
}
log_debug(" [REG] blocked PPSA uninstall failed: %s code=0x%08X", title_id,
(uint32_t)res);
return false;
}
static bool get_appmeta_present_for_scan_cycle(const char *title_id) {
@@ -155,6 +156,9 @@ static directory_candidate_probe_t probe_directory_candidate(
directory_candidate_info_t *info_out) {
struct stat param_st;
if (is_pfsc_image_mount_base_or_child(full_path))
return DIRECTORY_CANDIDATE_DESCEND;
if (!allow_known_param_root &&
is_under_discovered_param_root(full_path, discovered_param_roots,
*discovered_param_root_count)) {
@@ -432,6 +436,13 @@ typedef struct {
bool *unstable_found_out;
} collect_candidates_walk_ctx_t;
static void collect_scan_candidates_from_manual_root(
const char *scan_path, const char *manual_source_path,
scan_candidate_t *candidates, int max_candidates, int *candidate_count,
const scan_app_db_context_t *app_db,
char discovered_param_roots[][MAX_PATH], int *discovered_param_root_count,
bool *unstable_found_out);
static sm_scan_tree_dir_visit_t collect_candidate_directory_visit(
const char *dir_path, unsigned int depth_from_root, void *ctx_ptr) {
if (depth_from_root == 0u)
@@ -456,7 +467,18 @@ static bool collect_candidate_image_visit(const char *image_path,
(void)depth_from_root;
collect_candidates_walk_ctx_t *ctx = (collect_candidates_walk_ctx_t *)ctx_ptr;
maybe_mount_image_file(image_path, image_name, ctx->unstable_found_out);
if (!maybe_mount_image_file(image_path, image_name, ctx->unstable_found_out))
return true;
if (ctx->manual_source_path && is_pfsc_image_mount_base_or_child(image_path)) {
char mount_point[MAX_PATH];
get_image_mount_point_for_source(image_path, mount_point);
collect_scan_candidates_from_manual_root(
mount_point, ctx->manual_source_path, ctx->candidates,
ctx->max_candidates, ctx->candidate_count, ctx->app_db,
ctx->discovered_param_roots, ctx->discovered_param_root_count,
ctx->unstable_found_out);
}
return true;
}
@@ -469,8 +491,9 @@ static void collect_scan_candidates_from_manual_root(
if (should_stop_requested() || runtime_sleep_mode_active())
return;
bool try_root_candidate = true;
if (is_under_image_mount_base(scan_path)) {
bool pfsc_container_root = is_pfsc_image_mount_base_or_child(scan_path);
bool try_root_candidate = !pfsc_container_root;
if (try_root_candidate && is_under_image_mount_base(scan_path)) {
struct stat param_st;
try_root_candidate = directory_has_param_json(scan_path, &param_st);
}
@@ -484,9 +507,7 @@ static void collect_scan_candidates_from_manual_root(
}
}
unsigned int scan_depth = runtime_config()->scan_depth;
if (scan_depth < MIN_SCAN_DEPTH)
scan_depth = MIN_SCAN_DEPTH;
unsigned int scan_depth = get_scan_depth_for_root(scan_path);
collect_candidates_walk_ctx_t ctx = {
.candidates = candidates,
@@ -500,7 +521,8 @@ static void collect_scan_candidates_from_manual_root(
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = collect_candidate_directory_visit,
.on_image_file = NULL,
.on_image_file = pfsc_container_root ? collect_candidate_image_visit
: NULL,
};
(void)sm_scan_tree_walk(scan_path, scan_path, 0u, scan_depth, &callbacks, &ctx);
}
@@ -522,7 +544,8 @@ static void collect_scan_candidates_from_manual_path(
clear_manual_missing_source(manual_path);
const char *name = get_filename_component(manual_path);
if (S_ISREG(st.st_mode) && is_supported_image_file_name(name)) {
if (S_ISREG(st.st_mode) &&
is_supported_image_file_path(manual_path, name)) {
if (!maybe_mount_image_file(manual_path, name, unstable_found_out))
return;
@@ -725,9 +748,7 @@ static void collect_scan_candidates_from_root(
if (should_stop_requested() || runtime_sleep_mode_active())
return;
unsigned int scan_depth = runtime_config()->scan_depth;
if (scan_depth < MIN_SCAN_DEPTH)
scan_depth = MIN_SCAN_DEPTH;
unsigned int scan_depth = get_scan_depth_for_root(scan_path);
collect_candidates_walk_ctx_t ctx = {
.candidates = candidates,
@@ -781,14 +802,15 @@ static void uninstall_blocked_ppsa_titles(
if (!blocked_ppsa_titles_ready || !blocked_ppsa_titles)
return;
int requested_before = g_scan_workspace.blocked_ppsa_uninstall_count;
int requested = 0;
for (int i = 0; i < blocked_ppsa_titles->count &&
!should_stop_requested() && !runtime_sleep_mode_active();
i++) {
request_blocked_ppsa_uninstall(blocked_ppsa_titles->ids[i], NULL);
const char *title_id = blocked_ppsa_titles->ids[i];
if (request_blocked_ppsa_uninstall(title_id, NULL))
requested++;
}
int requested = g_scan_workspace.blocked_ppsa_uninstall_count - requested_before;
if (requested > 0)
log_debug(" [REG] requested %d blocked PPSA uninstall(s)", requested);
}
+5 -2
View File
@@ -71,11 +71,14 @@ bool sm_scan_tree_walk(const char *scan_root, const char *dir_path,
if (!d)
return true;
bool scan_root_is_pfsc_mount_base =
is_pfsc_image_mount_base_or_child(scan_root);
bool skip_backports_root =
(depth_from_root == 0u && !is_under_image_mount_base(scan_root));
bool allow_image_file_visits =
callbacks->on_image_file &&
!path_matches_root_or_child(scan_root, IMAGE_MOUNT_BASE);
(!path_matches_root_or_child(scan_root, IMAGE_MOUNT_BASE) ||
scan_root_is_pfsc_mount_base);
struct dirent *entry;
while ((entry = readdir(d)) != NULL) {
@@ -98,7 +101,7 @@ bool sm_scan_tree_walk(const char *scan_root, const char *dir_path,
classify_scan_tree_entry(full_path, entry->d_type, &is_dir, &is_regular);
if (allow_image_file_visits && is_regular &&
is_supported_image_file_name(entry->d_name)) {
is_supported_image_file_path(full_path, entry->d_name)) {
if (!callbacks->on_image_file(full_path, entry->d_name, depth_from_root + 1u,
ctx)) {
closedir(d);
+4 -7
View File
@@ -637,9 +637,7 @@ static bool rebuild_scan_root_watch_tree(int kq, int scan_root_index) {
return true;
}
unsigned int scan_depth = runtime_config()->scan_depth;
if (scan_depth < MIN_SCAN_DEPTH)
scan_depth = MIN_SCAN_DEPTH;
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
@@ -692,9 +690,7 @@ static bool rebuild_scan_root_watch_subtree(int kq, int scan_root_index,
return true;
}
unsigned int scan_depth = runtime_config()->scan_depth;
if (scan_depth < MIN_SCAN_DEPTH)
scan_depth = MIN_SCAN_DEPTH;
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
if (!remove_scan_root_watch_entries_for_path(scan_root_index, rebuild_path))
return false;
@@ -782,7 +778,8 @@ static bool scanner_event_requires_watch_tree_refresh(
case SCANNER_WATCH_SCAN_BACKPORT_ROOT:
return (fflags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) != 0;
case SCANNER_WATCH_SCAN_SUBDIR:
return entry->depth < runtime_config()->scan_depth &&
return entry->depth <
get_scan_depth_for_root(get_scan_path(entry->scan_root_index)) &&
(fflags & tree_change_flags) != 0;
case SCANNER_WATCH_SCAN_IMAGE_FILE:
return (fflags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) != 0;