mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
feat(libSceAppContent): answer bundle license, playable status and game trials queries
sceAppContentCheckBundleLicenseOnDisc and sceAppContentGetPlayableStatus answer NOT_FOUND for any label, as dlc_emu does for content it does not know, after the initialization guard, the null checks and the padded label check from SDK 0x01500000 or 0x03500000. sceAppContentGetGameTrialsFlag writes 0, as dlc_emu does. The prototypes are dlc_emu's.
This commit is contained in:
1 parent
e9a33a703b
commit
fa458d6467
6 files changed
+141
-12
No files matched your search
@@ -13,3 +13,15 @@ if(BUILD_TESTING)
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAppContent>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_appcontent_license_tests ${LIBS_INCLUDE_DIR}/tests/GuestAppContentLicense.cpp)
|
||||
target_include_directories(guest_appcontent_license_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_appcontent_license_tests PRIVATE libSceAppContent libc ${CMAKE_DL_LIBS})
|
||||
configure_windows_unwind(guest_appcontent_license_tests)
|
||||
add_test(NAME guest_appcontent_license COMMAND guest_appcontent_license_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_appcontent_license PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAppContent>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -70,20 +70,31 @@ static void ClearDirectory(const std::filesystem::path& directory) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAppContentCheckBundleLicenseOnDisc() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
int APS5_VABI sceAppContentCheckBundleLicenseOnDisc(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)serviceLabel;
|
||||
if (!entitlementLabel) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!validEntitlementLabel(*entitlementLabel, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentDownloadDataGetBlockSize() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentGetGameTrialsFlag() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
int APS5_VABI sceAppContentGetGameTrialsFlag(uint32_t* gameTrialsFlag) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!gameTrialsFlag) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
*gameTrialsFlag = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentGetPlayableStatus() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
int APS5_VABI sceAppContentGetPlayableStatus(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, uint32_t* playableStatus) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)serviceLabel;
|
||||
if (!entitlementLabel || !playableStatus) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!validEntitlementLabel(*entitlementLabel, 0x03500000)) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ int APS5_VABI sceAppContentAddcontEnqueueDownloadByEntitlemetId(const char* enti
|
||||
int APS5_VABI sceAppContentAddcontEnqueueDownloadSp(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel);
|
||||
int APS5_VABI sceAppContentAddcontMountByEntitlemetId(const char* entitlementId, AppContentMountPoint* mountPoint);
|
||||
int APS5_VABI sceAppContentAddcontShrink(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel);
|
||||
int APS5_VABI sceAppContentCheckBundleLicenseOnDisc(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel);
|
||||
int APS5_VABI sceAppContentAppParamGetString(void);
|
||||
int APS5_VABI sceAppContentDownload0Expand(void);
|
||||
int APS5_VABI sceAppContentDownload0Shrink(void);
|
||||
@@ -41,7 +42,9 @@ int APS5_VABI sceAppContentGetAddcontInfoByEntitlementId(const char* entitlement
|
||||
int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(uint32_t iroTag, AppContentAddcontInfo* list, uint32_t listNum, uint32_t* hitNum);
|
||||
int APS5_VABI sceAppContentGetDownloadedStoreCountry(void);
|
||||
int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key);
|
||||
int APS5_VABI sceAppContentGetGameTrialsFlag(uint32_t* gameTrialsFlag);
|
||||
int APS5_VABI sceAppContentGetPftFlag(uint32_t* pftFlag);
|
||||
int APS5_VABI sceAppContentGetPlayableStatus(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, uint32_t* playableStatus);
|
||||
int APS5_VABI sceAppContentGetRegion(void);
|
||||
int APS5_VABI sceAppContentRequestPatchInstall(void);
|
||||
int APS5_VABI sceAppContentTemporaryDataMount(AppContentMountPoint* mountPoint);
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAppContentCheckBundleLicenseOnDisc();
|
||||
int APS5_VABI sceAppContentDownloadDataGetBlockSize();
|
||||
int APS5_VABI sceAppContentGetGameTrialsFlag();
|
||||
int APS5_VABI sceAppContentGetPlayableStatus();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
#include "prx/libSceAppContent/include/Api.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
#include "ProcessParametersFixture.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int NotInitialized = static_cast<int>(0x80D90001u);
|
||||
constexpr int Parameter = static_cast<int>(0x80D90002u);
|
||||
constexpr int NotFound = static_cast<int>(0x80D90005u);
|
||||
constexpr std::uint32_t Untouched = 0x5a5a5a5au;
|
||||
|
||||
using CheckBundleLicenseFn = int (APS5_VABI*)(std::uint32_t, const NpUnifiedEntitlementLabel*);
|
||||
using GetGameTrialsFlagFn = int (APS5_VABI*)(std::uint32_t*);
|
||||
using GetPlayableStatusFn = int (APS5_VABI*)(std::uint32_t, const NpUnifiedEntitlementLabel*, std::uint32_t*);
|
||||
|
||||
template <typename Function>
|
||||
Function Resolve(const char* name) {
|
||||
#ifdef _WIN32
|
||||
void* symbol = reinterpret_cast<void*>(GetProcAddress(LoadLibraryA("libSceAppContent.prx"), name));
|
||||
#else
|
||||
void* symbol = dlsym(RTLD_DEFAULT, name);
|
||||
#endif
|
||||
if (!symbol) {
|
||||
std::fprintf(stderr, "%s is not exported by libSceAppContent\n", name);
|
||||
std::abort();
|
||||
}
|
||||
return reinterpret_cast<Function>(symbol);
|
||||
}
|
||||
|
||||
void Require(bool condition, const char* message) {
|
||||
if (condition) return;
|
||||
std::fprintf(stderr, "AppContent license: %s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
static std::array<std::byte, 64> process{};
|
||||
Require(ProcessParametersFixture::Install(process.data(), process.size()), "the process parameters could not be installed");
|
||||
std::uint32_t sdkVersion = 0x03500000;
|
||||
std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion));
|
||||
|
||||
const auto checkBundleLicense = Resolve<CheckBundleLicenseFn>("sceAppContentCheckBundleLicenseOnDisc");
|
||||
const auto getGameTrialsFlag = Resolve<GetGameTrialsFlagFn>("sceAppContentGetGameTrialsFlag");
|
||||
const auto getPlayableStatus = Resolve<GetPlayableStatusFn>("sceAppContentGetPlayableStatus");
|
||||
|
||||
NpUnifiedEntitlementLabel label{};
|
||||
std::memcpy(label.data, "TESTADDON0000001", 17);
|
||||
std::uint32_t flag = Untouched;
|
||||
std::uint32_t status = Untouched;
|
||||
Require(checkBundleLicense(0, &label) == NotInitialized, "the bundle license was checked before initialization");
|
||||
Require(getGameTrialsFlag(&flag) == NotInitialized && flag == Untouched, "the game trials flag was read before initialization");
|
||||
Require(getPlayableStatus(0, &label, &status) == NotInitialized && status == Untouched, "the playable status was read before initialization");
|
||||
|
||||
const auto original = std::filesystem::current_path();
|
||||
const auto directory = original / ("appcontent-license-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
Require(std::filesystem::create_directory(directory), "the test directory could not be created");
|
||||
std::filesystem::current_path(directory);
|
||||
std::filesystem::create_directories("app0/sce_sys");
|
||||
{
|
||||
std::ofstream param("app0/sce_sys/param.json", std::ios::binary);
|
||||
param << R"({"titleId":"PPSA00000","localizedParameters":{"en-US":{"titleName":"Example"}},"downloadDataSize":0})";
|
||||
Require(static_cast<bool>(param), "param.json could not be written");
|
||||
}
|
||||
AppContentInitParam init{};
|
||||
AppContentBootParam boot{};
|
||||
Require(sceAppContentInitialize(&init, &boot) == 0, "the library could not be initialized");
|
||||
|
||||
Require(checkBundleLicense(0, &label) == NotFound && checkBundleLicense(7, &label) == NotFound, "a bundle license was found for unknown content");
|
||||
Require(checkBundleLicense(0, nullptr) == Parameter, "a null label was taken by the bundle license check");
|
||||
Require(getGameTrialsFlag(&flag) == 0 && flag == 0, "the game trials flag was not 0");
|
||||
Require(getGameTrialsFlag(nullptr) == Parameter, "a null game trials flag was taken");
|
||||
Require(getPlayableStatus(0, &label, &status) == NotFound && getPlayableStatus(7, &label, &status) == NotFound && status == Untouched, "a playable status was given for unknown content");
|
||||
Require(getPlayableStatus(0, nullptr, &status) == Parameter && getPlayableStatus(0, &label, nullptr) == Parameter && status == Untouched, "a null argument was taken by the playable status");
|
||||
|
||||
for (const std::uint32_t version : {0x014fffffu, 0x01500000u, 0x034fffffu, 0x03500000u}) {
|
||||
std::memcpy(process.data() + 16, &version, sizeof(version));
|
||||
for (char& padding : label.padding) {
|
||||
padding = 1;
|
||||
Require(checkBundleLicense(0, &label) == (version >= 0x01500000 ? Parameter : NotFound), "a padded label was not checked from SDK 0x01500000 by the bundle license check");
|
||||
Require(getPlayableStatus(0, &label, &status) == (version >= 0x03500000 ? Parameter : NotFound) && status == Untouched, "a padded label was not checked from SDK 0x03500000 by the playable status");
|
||||
padding = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::current_path(original);
|
||||
std::filesystem::remove_all(directory);
|
||||
std::puts("AppContent license and trials tests passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -6,10 +6,7 @@
|
||||
|
||||
| Name | NID |
|
||||
| --- | --- |
|
||||
| `sceAppContentCheckBundleLicenseOnDisc` | `xZo2-418Wdo` |
|
||||
| `sceAppContentDownloadDataGetBlockSize` | `9ht66cHTj7g` |
|
||||
| `sceAppContentGetGameTrialsFlag` | `1saJukIkcKw` |
|
||||
| `sceAppContentGetPlayableStatus` | `y8meQn-Qy5c` |
|
||||
|
||||
- [sceAppContentDownloadDataGetAvailableSpaceKb](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - a title whose `param.json` declares `downloadDataSize` 0 gets 0 KB and OK; the console's behaviour for a title without a download data area is unverified (it may return an error instead). KytyPS5 does not special-case it, and shadPS4 stubs 1048576 KB. One disassembled title (PPSA15246) calls it with the `/download0` mount point and a zeroed 64-bit output, ignores the return value and multiplies the output by 1024, so for that title an error that leaves the output untouched and an OK with 0 KB look the same
|
||||
- [sceAppContentDownloadDataFormat](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - empties the `/download0` directory, creating it if needed. Imported by PPSA01317. A null mount point returns `PARAMETER` and another mount point returns `NOT_MOUNTED` as in `sceAppContentTemporaryDataFormat` and `sceAppContentDownloadDataGetAvailableSpaceKb`; the console's error codes for both and its behaviour with files still open are unverified
|
||||
@@ -23,5 +20,6 @@
|
||||
- [Additional content](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries; `sceAppContentAddcontEnqueueDownload` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no download is ever queued; `sceAppContentAddcontDelete` answers `PARAMETER` for a null label and `NOT_FOUND` otherwise, since no additional content is ever installed. `sceAppContentGetAddcontDownloadProgress` answers `PARAMETER` for a null label or progress and `NOT_FOUND` otherwise, as [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2328-L2341) does for a label that is not installed additional content, and never writes the progress. Its 16-byte layout (`DataSize`, then `DownloadedSize`, both 64-bit) follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L45-L54) and is unused here. The signatures of `sceAppContentAddcontEnqueueDownload` and `sceAppContentGetAddcontDownloadProgress` follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppContent/AppContent.cs#L140-L148), and the signature of `sceAppContentAddcontDelete` follows [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L91-L92); dlc_emu is not a console measurement and no console source gives the error codes, so the signatures and the error codes are unverified
|
||||
- [sceAppContentAddcontEnqueueDownloadByEntitlemetId, sceAppContentAddcontEnqueueDownloadSp, sceAppContentAddcontMountByEntitlemetId and sceAppContentAddcontShrink](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - unverified on a PS5. After the initialization guard they answer `PARAMETER` for a null argument and `NOT_FOUND` otherwise, without writing the mount point, as [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2125-L2209) does for content it does not know; the prototypes are [dlc_emu's](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L88-L99). dlc_emu rejects a padded label at every SDK while the label calls above check it only from SDK 0x01500000 or 0x03500000, so `Sp` and `Shrink` answer `PARAMETER` for one from SDK 0x03500000 and throw below it
|
||||
- [sceAppContentGetAddcontInfoByEntitlementId, sceAppContentGetAddcontInfoListByIroTag and sceAppContentGetPftFlag](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - unverified on a PS5. After the initialization guard they give [dlc_emu's](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2040-L2086) answers for content it does not know: `DRM_NO_ENTITLEMENT` without writing the info, 0 entries for any tag (a null `hitNum` beside a list returns 0, unlike `sceAppContentGetAddcontInfoList`), and a [PFT flag of 0](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2343-L2347), and `PARAMETER` for null arguments, with dlc_emu's [prototypes](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L74-L131)
|
||||
- [sceAppContentCheckBundleLicenseOnDisc, sceAppContentGetPlayableStatus and sceAppContentGetGameTrialsFlag](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - unverified on a PS5. After the initialization guard they give [dlc_emu's](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2211-L2256) answers for content it does not know: `NOT_FOUND` for both label calls without writing the playable status, and a game trials flag of 0. Null arguments return `PARAMETER`, and so does a padded label from SDK 0x01500000 (CheckBundleLicenseOnDisc) or 0x03500000 (GetPlayableStatus), the versions [dlc_emu checks](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L1570-L1576) them from. The prototypes are [dlc_emu's](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.h#L100-L109)
|
||||
- [sceAppContentGetEntitlementKey](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - the service result without an entitlement is unverified: this model returns `DRM_NO_ENTITLEMENT` and leaves the 16-byte key unchanged. Initialization, null-pointer and SDK 0x01500000 label-padding checks match the reported [firmware 13.42 module probe](https://github.com/boykopovar/AnyPS5/pull/9638#issuecomment-6390573735), run in an emulator, not on hardware. That module forwards service labels 0, 7, 8 and 0xFFFFFFFF without checking them; this library ignores the service label. The system service's own validation and errors remain unknown.
|
||||
- [Temporary app-content storage](../../../core/libs/prx/libSceAppContent/Export.cpp) console behaviour and hardware return values are unverified, including behaviour with open files across unmount; host filesystem failures throw. Storage models one `/temp0` mount, preserving files on unmount while rejecting new path resolutions until remount, and leaving existing open host descriptors usable. `sceAppContentTemporaryDataMount` delegates to `sceAppContentTemporaryDataMount2` with option 1 (`TEMPORARY_DATA_OPTION_FORMAT`) to format on mount as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/app_content/app_content.cpp#L550-L553) does. Mount, format, space-query and unmount errors follow [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/sys/sys_path.pas); pointer ABI follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/AppContent/AppContent.cs).
|
||||
Reference in new issue
Block a user