mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
Browser roots: the USB and extended drives ShadowMountPlus mounts
ShadowMountPlus 1.7beta4 mounts the console's /mnt (and /data) into every PPSA title's sandbox while it runs. The top of the file browser and Load Content is now INTERNAL (/app0) and each drive actually mounted there: USB 0-7 (/mnt/usbN) and EXTENDED 0-1 (/mnt/extN). A slot counts as a drive when its root is another filesystem than /mnt. With none, EXTERNAL (/mnt) stays, as before. /data is never offered (owner's decision). The startup trace records what the sandbox exposes: roots, drive slots and /mnt's mount names, no file names. tools/build-title.sh gains PS5_REUSE_STAGED_CORES (development builds only, refused for a release): stage a core as its last build left it, to deploy a title change while MAME's hour-long rebuild runs. Verified on the base PS5 (13.40), ShadowMountPlus 1.7beta4, one USB drive: run-PPSA99169-204255; trace: "directory /mnt opened=1", "storage /mnt/usb0 (USB 0) available", no DATA root (evidence/usb-storage-roots). Before, /mnt and /mnt/usb0 failed with errno 2. The owner confirmed the drive is listed and a game on it plays. Gates: tools/verify.sh format, unit and evidence PASS (87 captures, 0 failed). build and integration not run: MAME's full rebuild is in progress.
This commit is contained in:
1 parent
1cdf298db1
commit
e06c1b87c8
7 files changed
+173
-26
No files matched your search
@@ -219,9 +219,12 @@ obtained backups**: games you own, and BIOS or firmware dumped from hardware you
|
||||
own. Piracy is not condoned.
|
||||
|
||||
Ordinary scripted updates preserve content and saved settings. Back up your
|
||||
files before replacing or removing a title. The file browser has **INTERNAL**
|
||||
for the title folder and **EXTERNAL** for `/mnt`; the current title sandbox hides
|
||||
external mounts, so EXTERNAL may be empty.
|
||||
files before replacing or removing a title. The file browser opens on **INTERNAL**,
|
||||
the title folder. With [ShadowMountPlus](https://github.com/drakmor/ShadowMountPlus)
|
||||
1.7beta4 or later running, it also lists each USB drive (**USB 0** to **USB 7**,
|
||||
`/mnt/usb0` onward) and extended storage (**EXTENDED 0** and **1**, `/mnt/ext0` and
|
||||
`/mnt/ext1`), only when a drive is mounted there. Without it, the sandbox hides them
|
||||
and **EXTERNAL** (`/mnt`) may be empty.
|
||||
|
||||
## What has been verified
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"step": "usb-storage-roots",
|
||||
"recorded": "2026-10-07T00:44:06Z",
|
||||
"console_run": "base PS5 13.40, ShadowMountPlus 1.7beta4 loaded, one USB drive; run-PPSA99169-204255",
|
||||
"revision": "working tree on 1cdf298 (this commit)",
|
||||
"command": "PS5_FRONTENDS='es-de picker' PS5_REUSE_STAGED_CORES=mame tools/build-title.sh; tools/run-title.sh --no-build --watch 30; /app0/trace.txt over FTP",
|
||||
"raw_capture": "klog/usb-storage-roots/trace-storage-204255.txt",
|
||||
"raw_lines": 15,
|
||||
"raw_bytes": 844,
|
||||
"lines": [
|
||||
"frontend ps5: directory /app0/cores opened=1 entries=32 errno=0",
|
||||
"frontend ps5: directory /app0 opened=1 entries=35 errno=0",
|
||||
"frontend ps5: directory /data opened=1 entries=12 errno=0",
|
||||
"frontend ps5: directory /mnt opened=1 entries=16 errno=0",
|
||||
"frontend ps5: /mnt holds disc system_data.tmp rnps ext0 sandbox rnps2.tmp rnps2 pfs usb1 usb0 usb3 usb2 usb5 usb4 usb7 usb6",
|
||||
"frontend ps5: storage /mnt/ext0 (EXTENDED 0) absent",
|
||||
"frontend ps5: storage /mnt/ext1 (EXTENDED 1) absent",
|
||||
"frontend ps5: storage /mnt/usb0 (USB 0) available",
|
||||
"frontend ps5: storage /mnt/usb1 (USB 1) absent",
|
||||
"frontend ps5: storage /mnt/usb2 (USB 2) absent",
|
||||
"frontend ps5: storage /mnt/usb3 (USB 3) absent",
|
||||
"frontend ps5: storage /mnt/usb4 (USB 4) absent",
|
||||
"frontend ps5: storage /mnt/usb5 (USB 5) absent",
|
||||
"frontend ps5: storage /mnt/usb6 (USB 6) absent",
|
||||
"frontend ps5: storage /mnt/usb7 (USB 7) absent"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"step": "usb-storage-roots",
|
||||
"note": "With ShadowMountPlus 1.7beta4, which mounts /mnt and /data into every PPSA title's sandbox, the title opens /mnt and finds the USB drive at /mnt/usb0; before, /mnt and /mnt/usb0 failed with errno 2. The browser's roots are INTERNAL and each mounted drive; /data is never offered (owner).",
|
||||
"must_contain": [
|
||||
"frontend ps5: directory /mnt opened=1",
|
||||
"frontend ps5: storage /mnt/usb0 (USB 0) available",
|
||||
"frontend ps5: storage /mnt/ext0 (EXTENDED 0) absent"
|
||||
],
|
||||
"must_not_contain": [
|
||||
"(DATA)"
|
||||
],
|
||||
"human_check": "Owner confirmed the USB drive is listed in the title and a game from it plays."
|
||||
}
|
||||
+2
-1
@@ -195,7 +195,8 @@
|
||||
0096-frontend-thread-stacks
|
||||
|
||||
# 0097 the browser's top is the frontend's roots, INTERNAL (the title's folder)
|
||||
# and EXTERNAL (the console's mounts): Load Content lists them alone, a
|
||||
# and the drives the sandbox exposes (src/frontend_ps5.cpp: USB and
|
||||
# extended drives, or EXTERNAL): Load Content lists them alone, a
|
||||
# root shows its name, and a root's parent is the roots, not the
|
||||
# filesystem's /
|
||||
# menu/menu_displaylist.c, menu/cbs/menu_cbs_get_value.c,
|
||||
|
||||
+80
-16
@@ -14,6 +14,7 @@ extern "C"
|
||||
#include <cstring>
|
||||
#include <ps5platform/libc.h>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace
|
||||
@@ -21,15 +22,58 @@ namespace
|
||||
constexpr const char *saved_config = "/app0/config/retroarch.cfg";
|
||||
|
||||
// The top of the file browser, Load Content included (patches/series, 0097):
|
||||
// INTERNAL, the title's own folder, and EXTERNAL, where the console mounts USB
|
||||
// drives, an extended storage drive and any other external storage. Each shows
|
||||
// its name, and opening it opens its folder.
|
||||
// INTERNAL, the title's own folder, then the USB and extended storage drives
|
||||
// mounted into the title's sandbox. A sandbox shows the drives under /mnt only
|
||||
// when something mounts them into it: ShadowMountPlus 1.7beta4 and later does,
|
||||
// for every PPSA title, for as long as it runs. A drive slot is offered only if a
|
||||
// drive is mounted there; without any, the browser keeps EXTERNAL, the bare /mnt,
|
||||
// as before. The console's own /data is never offered (owner's decision), though
|
||||
// ShadowMountPlus mounts it too. Each shows its name.
|
||||
struct Root
|
||||
{
|
||||
const char *path;
|
||||
const char *name;
|
||||
};
|
||||
constexpr Root roots[] = {{"/app0", "INTERNAL"}, {"/mnt", "EXTERNAL"}};
|
||||
constexpr Root internal_root{"/app0", "INTERNAL"};
|
||||
constexpr Root storage_roots[] = {{"/mnt/usb0", "USB 0"}, {"/mnt/usb1", "USB 1"},
|
||||
{"/mnt/usb2", "USB 2"}, {"/mnt/usb3", "USB 3"},
|
||||
{"/mnt/usb4", "USB 4"}, {"/mnt/usb5", "USB 5"},
|
||||
{"/mnt/usb6", "USB 6"}, {"/mnt/usb7", "USB 7"},
|
||||
{"/mnt/ext0", "EXTENDED 0"}, {"/mnt/ext1", "EXTENDED 1"}};
|
||||
constexpr Root external_root{"/mnt", "EXTERNAL"};
|
||||
|
||||
// Whether a directory opens, and how many entries it holds besides . and ..
|
||||
bool opens(const char *path, size_t *entries = nullptr, int *error = nullptr)
|
||||
{
|
||||
errno = 0;
|
||||
DIR *dir = ps5_opendir(path);
|
||||
size_t count = 0;
|
||||
if (dir)
|
||||
{
|
||||
while (const struct dirent *entry = ps5_readdir(dir))
|
||||
if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, ".."))
|
||||
++count;
|
||||
ps5_closedir(dir);
|
||||
}
|
||||
if (entries)
|
||||
*entries = count;
|
||||
if (error)
|
||||
*error = dir ? 0 : errno;
|
||||
return dir != nullptr;
|
||||
}
|
||||
|
||||
// A drive slot holds a drive when its root is another filesystem than /mnt
|
||||
// itself; an empty slot directory is the same filesystem.
|
||||
bool available(const Root &root)
|
||||
{
|
||||
size_t entries = 0;
|
||||
if (!opens(root.path, &entries))
|
||||
return false;
|
||||
struct stat slot{}, parent{};
|
||||
if (stat(root.path, &slot) == 0 && stat("/mnt", &parent) == 0)
|
||||
return slot.st_dev != parent.st_dev;
|
||||
return entries != 0;
|
||||
}
|
||||
|
||||
void set_directory(default_dirs slot, const char *path)
|
||||
{
|
||||
@@ -100,20 +144,28 @@ void initialize(void *)
|
||||
set_directory(DEFAULT_DIR_OVERLAY, "/app0/overlays");
|
||||
set_directory(DEFAULT_DIR_LOGS, "/app0");
|
||||
std::fprintf(stderr, "frontend ps5: config=%s browser=/app0 cores=/app0/cores\n", saved_config);
|
||||
// Startup summary: known roots only; never log the user's file names.
|
||||
for (const char *path : {"/app0", "/app0/cores", "/mnt", "/mnt/usb0"})
|
||||
// Startup summary: what this sandbox exposes, roots and mount names only;
|
||||
// never the user's file names.
|
||||
for (const char *path : {"/app0", "/app0/cores", "/data", "/mnt"})
|
||||
{
|
||||
errno = 0;
|
||||
DIR *dir = ps5_opendir(path);
|
||||
size_t entries = 0;
|
||||
if (dir)
|
||||
{
|
||||
while (ps5_readdir(dir))
|
||||
++entries;
|
||||
ps5_closedir(dir);
|
||||
}
|
||||
int error = 0;
|
||||
const bool opened = opens(path, &entries, &error);
|
||||
std::fprintf(stderr, "frontend ps5: directory %s opened=%d entries=%zu errno=%d\n", path,
|
||||
dir != nullptr, entries, errno);
|
||||
opened, entries, error);
|
||||
}
|
||||
for (const Root &root : storage_roots)
|
||||
std::fprintf(stderr, "frontend ps5: storage %s (%s) %s\n", root.path, root.name,
|
||||
available(root) ? "available" : "absent");
|
||||
if (DIR *dir = ps5_opendir("/mnt"))
|
||||
{
|
||||
std::string names;
|
||||
while (const struct dirent *entry = ps5_readdir(dir))
|
||||
if (entry->d_name[0] != '.' && names.size() < 400)
|
||||
names += std::string(names.empty() ? "" : " ") + entry->d_name;
|
||||
ps5_closedir(dir);
|
||||
std::fprintf(stderr, "frontend ps5: /mnt holds %s\n",
|
||||
names.empty() ? "nothing" : names.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,9 +180,21 @@ int drives(void *data, bool content)
|
||||
auto *list = static_cast<file_list_t *>(data);
|
||||
const auto label = content ? MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR
|
||||
: MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY;
|
||||
for (const Root &root : roots)
|
||||
const auto add = [&](const Root &root)
|
||||
{
|
||||
if (menu_entries_append(list, root.path, "", label, FILE_TYPE_DIRECTORY, 0, 0, nullptr))
|
||||
file_list_set_alt_at_offset(list, list->size - 1, root.name);
|
||||
};
|
||||
add(internal_root);
|
||||
bool any = false;
|
||||
for (const Root &root : storage_roots)
|
||||
if (available(root))
|
||||
{
|
||||
add(root);
|
||||
any = true;
|
||||
}
|
||||
if (!any)
|
||||
add(external_root);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -11,11 +11,13 @@ namespace
|
||||
std::string fixture;
|
||||
std::vector<std::string> root_paths;
|
||||
std::vector<std::string> root_names;
|
||||
// Drive slots that hold a mounted drive: their stat reports another device.
|
||||
std::vector<std::string> mounted;
|
||||
std::string physical(const char *path)
|
||||
{
|
||||
std::string p(path);
|
||||
if (p == "/" || p == "/app0" || p.find("/app0/") == 0 || p == "/data" ||
|
||||
p.find("/mnt/usb") == 0)
|
||||
p.find("/data/") == 0 || p == "/mnt" || p.find("/mnt/") == 0)
|
||||
return fixture + p;
|
||||
return p;
|
||||
}
|
||||
@@ -40,7 +42,11 @@ extern "C"
|
||||
}
|
||||
int __wrap_stat(const char *p, struct stat *s)
|
||||
{
|
||||
return __real_stat(physical(p).c_str(), s);
|
||||
const int result = __real_stat(physical(p).c_str(), s);
|
||||
for (const std::string &slot : mounted)
|
||||
if (result == 0 && slot == p)
|
||||
s->st_dev += 1;
|
||||
return result;
|
||||
}
|
||||
int __wrap_mkdir(const char *p, mode_t mode)
|
||||
{
|
||||
@@ -129,11 +135,26 @@ int main(int argc, char **argv)
|
||||
assert(frontend_ctx_ps5.environment_get);
|
||||
frontend_ctx_ps5.environment_get(&count, argv, nullptr, &untouched);
|
||||
assert(count == argc && untouched == 0x1234);
|
||||
// The browser's top is two named roots, the title's folder and the mounts.
|
||||
// Without anything mounted into the sandbox, the browser's top is the title's
|
||||
// folder and the bare mounts, as before: an empty drive slot is not a drive.
|
||||
file_list_t list{};
|
||||
assert(frontend_ctx_ps5.parse_drive_list(&list, true) == 0);
|
||||
assert((root_paths == std::vector<std::string>{"/app0", "/mnt"}));
|
||||
assert((root_names == std::vector<std::string>{"INTERNAL", "EXTERNAL"}));
|
||||
std::puts("frontend_ps5: config seed/preservation, directories, argv and the INTERNAL and "
|
||||
"EXTERNAL roots PASS");
|
||||
// With /data and a USB drive in the sandbox (ShadowMountPlus 1.7beta4), the
|
||||
// drive is a root of its own, by name, and EXTERNAL gives way to it; /data is
|
||||
// never offered.
|
||||
std::filesystem::create_directories(fixture + "/data/roms");
|
||||
std::ofstream(fixture + "/mnt/usb0/game.bin") << "x";
|
||||
std::filesystem::create_directories(fixture + "/mnt/ext0");
|
||||
mounted = {"/mnt/usb0"};
|
||||
root_paths.clear();
|
||||
root_names.clear();
|
||||
list = {};
|
||||
assert(frontend_ctx_ps5.parse_drive_list(&list, false) == 0);
|
||||
assert((root_paths == std::vector<std::string>{"/app0", "/mnt/usb0"}));
|
||||
assert((root_names == std::vector<std::string>{"INTERNAL", "USB 0"}));
|
||||
frontend_ctx_ps5.init(nullptr); // the startup summary walks every root
|
||||
std::puts("frontend_ps5: config seed/preservation, directories, argv, the INTERNAL and "
|
||||
"EXTERNAL roots, and the mounted storage roots PASS");
|
||||
}
|
||||
+19
-1
@@ -75,6 +75,16 @@ if [[ ${PS5_WITH_RPCS3:-0} == 1 ]]; then
|
||||
core_names+=(rpcs3)
|
||||
echo "==> [title] RPCS3 staged (PS5_WITH_RPCS3=1, your own build only)"
|
||||
fi
|
||||
# PS5_REUSE_STAGED_CORES="mame ..." (development builds only): stage those cores as
|
||||
# their last build left them in build/cores/stage instead of building them again.
|
||||
# For deploying a title change while a long core build (MAME's, over an hour) runs
|
||||
# elsewhere; the reused core keeps the title's import table covering it. A release
|
||||
# always builds every core.
|
||||
reuse_cores=" ${PS5_REUSE_STAGED_CORES:-} "
|
||||
if [[ -n ${PS5_REUSE_STAGED_CORES:-} && -n ${PS5_RELEASE_TAG:-} ]]; then
|
||||
echo "error: PS5_REUSE_STAGED_CORES with release $PS5_RELEASE_TAG; a release builds every core" >&2
|
||||
exit 2
|
||||
fi
|
||||
echo "==> [title] step 1/3: the frontend"
|
||||
"$root/tools/build-retroarch.sh"
|
||||
core_files=()
|
||||
@@ -88,7 +98,15 @@ for core_name in "${core_names[@]}"; do
|
||||
vice_x64sc) script=vice ;;
|
||||
*) script=${core_name//_/-} ;;
|
||||
esac
|
||||
bash "$root/tools/build-$script.sh"
|
||||
if [[ $reuse_cores == *" $core_name "* ]]; then
|
||||
for staged in "$root/build/cores/stage/cores/${core_name}_libretro.so" \
|
||||
"$root/build/cores/stage/info/${core_name}_libretro.info"; do
|
||||
[[ -f $staged ]] || { echo "error: no staged $staged to reuse" >&2; exit 2; }
|
||||
done
|
||||
echo "==> [title] $core_name: reusing the staged core (PS5_REUSE_STAGED_CORES), not built"
|
||||
else
|
||||
bash "$root/tools/build-$script.sh"
|
||||
fi
|
||||
core_files+=("$root/build/cores/stage/cores/${core_name}_libretro.so")
|
||||
done
|
||||
python3 "$root/tools/core-imports.py" "${core_files[@]}"
|
||||
|
||||
Reference in new issue
Block a user