From 84fa55722db966dc153a412119f78712a779ff84 Mon Sep 17 00:00:00 2001 From: LeandroLP23 Date: Thu, 8 Oct 2026 10:18:21 +0200 Subject: [PATCH 1/2] fix(kernel): create /download0 at start-up when the title declares download data On the console a title whose param.json declares downloadDataSize finds /download0 mounted from the start. The host directory was only created by sceAppContentDownloadDataGetAvailableSpaceKb, so a title that opens files there directly got ENOENT. libkernel now creates it when it loads, reading only the run directory's param.json, without resolving guest paths, so the libc path root is still fixed at first use. Co-Authored-By: Claude Opus 5.5 --- .../libkernel/AppMetadata/src/AppMetadata.cpp | 8 ++++++++ core/libs/prx/libkernel/CMakeLists.txt | 19 +++++++++++++++++++ core/libs/tests/GuestDownloadDataMount.cpp | 14 ++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 core/libs/tests/GuestDownloadDataMount.cpp diff --git a/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp b/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp index bc423779a..42a377dfa 100644 --- a/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp +++ b/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp @@ -42,6 +42,14 @@ void ensureTitleLoaded() { g_titleLoaded = true; } +const bool g_downloadDataMounted = [] { + const auto run = std::filesystem::current_path(); + const auto paramJson = run / "app0" / "sce_sys" / "param.json"; + if (!std::filesystem::exists(paramJson) || parseParamJson(paramJson).downloadDataSizeMiB == 0) return false; + std::filesystem::create_directories(run / "download0"); + return true; +}(); + void ensureIconLoaded() { if (g_iconAttempted) return; g_iconAttempted = true; diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index c581ad8ae..67d500764 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -65,6 +65,25 @@ endif() configure_windows_unwind(libkernel) if(BUILD_TESTING) + add_test_executable(download_data_mount_tests ${LIBS_INCLUDE_DIR}/tests/GuestDownloadDataMount.cpp) + target_include_directories(download_data_mount_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(download_data_mount_tests PRIVATE libkernel libc) + configure_windows_unwind(download_data_mount_tests) + foreach(downloadDataCase IN ITEMS declared:256 none:0) + string(REPLACE ":" ";" downloadDataCase ${downloadDataCase}) + list(GET downloadDataCase 0 downloadDataName) + list(GET downloadDataCase 1 downloadDataSize) + set(downloadDataRun ${CMAKE_CURRENT_BINARY_DIR}/download_data_mount_${downloadDataName}) + file(WRITE ${downloadDataRun}/app0/sce_sys/param.json + "{\"titleId\":\"PPSA00000\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}},\"downloadDataSize\":${downloadDataSize}," + "\"userDefinedParam1\":0,\"userDefinedParam2\":0,\"userDefinedParam3\":0,\"userDefinedParam4\":0}") + add_test(NAME download_data_mount_${downloadDataName} COMMAND download_data_mount_tests ${downloadDataName} + WORKING_DIRECTORY ${downloadDataRun}) + if(WIN32) + set_tests_properties(download_data_mount_${downloadDataName} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() add_test_executable(param_json_parser_tests ${LIBS_INCLUDE_DIR}/tests/ParamJsonParser.cpp) target_include_directories(param_json_parser_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(param_json_parser_tests PRIVATE libkernel libc) diff --git a/core/libs/tests/GuestDownloadDataMount.cpp b/core/libs/tests/GuestDownloadDataMount.cpp new file mode 100644 index 000000000..a345f07f4 --- /dev/null +++ b/core/libs/tests/GuestDownloadDataMount.cpp @@ -0,0 +1,14 @@ +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +int main(int argc, char** argv) { + Require(argc == 2); + const bool declared = std::strcmp(argv[1], "declared") == 0; + Require(std::filesystem::is_directory("download0") == declared); + Require(std::strcmp(GetAppTitleId_nid_postfix().value, "PPSA00000") == 0); + std::filesystem::remove_all("download0"); +} From c1c7c7fc5604b7754a63942c2ab4aef36fe5db60 Mon Sep 17 00:00:00 2001 From: LeandroLP23 Date: Thu, 8 Oct 2026 16:07:18 +0200 Subject: [PATCH 2/2] fix(kernel): skip the download0 mount when param.json cannot be read Co-Authored-By: Claude Opus 5.5 --- .../libkernel/AppMetadata/src/AppMetadata.cpp | 14 +++++++++----- core/libs/prx/libkernel/CMakeLists.txt | 12 ++++++++---- core/libs/tests/GuestDownloadDataMount.cpp | 16 ++++++++++++++-- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp b/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp index 42a377dfa..79df79827 100644 --- a/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp +++ b/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp @@ -43,11 +43,15 @@ void ensureTitleLoaded() { } const bool g_downloadDataMounted = [] { - const auto run = std::filesystem::current_path(); - const auto paramJson = run / "app0" / "sce_sys" / "param.json"; - if (!std::filesystem::exists(paramJson) || parseParamJson(paramJson).downloadDataSizeMiB == 0) return false; - std::filesystem::create_directories(run / "download0"); - return true; + try { + const auto run = std::filesystem::current_path(); + const auto paramJson = run / "app0" / "sce_sys" / "param.json"; + if (!std::filesystem::exists(paramJson) || parseParamJson(paramJson).downloadDataSizeMiB == 0) return false; + std::filesystem::create_directories(run / "download0"); + return true; + } catch (const std::exception&) { + return false; + } }(); void ensureIconLoaded() { diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 67d500764..a59d5da61 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -69,14 +69,18 @@ if(BUILD_TESTING) target_include_directories(download_data_mount_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(download_data_mount_tests PRIVATE libkernel libc) configure_windows_unwind(download_data_mount_tests) - foreach(downloadDataCase IN ITEMS declared:256 none:0) + foreach(downloadDataCase IN ITEMS declared:256 none:0 invalid:256) string(REPLACE ":" ";" downloadDataCase ${downloadDataCase}) list(GET downloadDataCase 0 downloadDataName) list(GET downloadDataCase 1 downloadDataSize) set(downloadDataRun ${CMAKE_CURRENT_BINARY_DIR}/download_data_mount_${downloadDataName}) - file(WRITE ${downloadDataRun}/app0/sce_sys/param.json - "{\"titleId\":\"PPSA00000\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}},\"downloadDataSize\":${downloadDataSize}," - "\"userDefinedParam1\":0,\"userDefinedParam2\":0,\"userDefinedParam3\":0,\"userDefinedParam4\":0}") + if(downloadDataName STREQUAL "invalid") + file(WRITE ${downloadDataRun}/app0/sce_sys/param.json "{\"downloadDataSize\":${downloadDataSize}}") + else() + file(WRITE ${downloadDataRun}/app0/sce_sys/param.json + "{\"titleId\":\"PPSA00000\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}},\"downloadDataSize\":${downloadDataSize}," + "\"userDefinedParam1\":0,\"userDefinedParam2\":0,\"userDefinedParam3\":0,\"userDefinedParam4\":0}") + endif() add_test(NAME download_data_mount_${downloadDataName} COMMAND download_data_mount_tests ${downloadDataName} WORKING_DIRECTORY ${downloadDataRun}) if(WIN32) diff --git a/core/libs/tests/GuestDownloadDataMount.cpp b/core/libs/tests/GuestDownloadDataMount.cpp index a345f07f4..e588f786d 100644 --- a/core/libs/tests/GuestDownloadDataMount.cpp +++ b/core/libs/tests/GuestDownloadDataMount.cpp @@ -2,13 +2,25 @@ #include #include #include +#include +#include static void Require(bool value) { if (!value) std::abort(); } int main(int argc, char** argv) { Require(argc == 2); - const bool declared = std::strcmp(argv[1], "declared") == 0; - Require(std::filesystem::is_directory("download0") == declared); + const std::string_view mode = argv[1]; + Require(std::filesystem::is_directory("download0") == (mode == "declared")); + if (mode == "invalid") { + bool rejected = false; + try { + GetAppTitleId_nid_postfix(); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected); + return 0; + } Require(std::strcmp(GetAppTitleId_nid_postfix().value, "PPSA00000") == 0); std::filesystem::remove_all("download0"); }