mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
Cult of the Lamb (PPSA06465) imports libSceContentSearch, including sceContentSearchGetContentLastUpdateId, OpenMetadataByContentId, GetTotalContentSize and GetNumOfContent (import audit in #2686), which were declared without parameters and threw. Give the nine pending calls the signatures of AlexAltea/orbital db_types.json (cc408367, lines 555-569) and SharpProspero Interop/Content/ContentSearch.cs (5dfdb4b0), and implement the content library the host has, which is empty: - GetNumOfContent, GetTotalContentSize and SearchContent report 0 rows and 0 bytes, GetContentLastUpdateId and the search calls report update id 0. A search limit above 92 returns 0x809D100A and every call before Init returns 0x809D1001, both from SharpProspero. - Filters and sort conditions use SharpProspero's layouts (added to SceTypes.hpp with size checks) and are checked against its enums; unknown values, null outputs and null arrays with a length throw. - OpenMetadata and OpenMetadataByContentId throw, as no source gives the error for content that is not in the library, so no metadata id is handed out and CloseMetadata, GetMetadataFieldInfo and GetMetadataValue throw too. The update id value, the argument errors and the check order are recorded in TechnicalDebt. The four ContentSearch calls leave the unimplemented-imports test, and guest_content_search covers the library.
145 lines
7.4 KiB
C++
145 lines
7.4 KiB
C++
#include <cstdint>
|
|
#include <cstddef>
|
|
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/General.hpp"
|
|
#include <atomic>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
namespace {
|
|
std::atomic<bool> g_initialized{false};
|
|
|
|
constexpr int ErrorNotInitialized = static_cast<int>(0x809D1001u);
|
|
constexpr int ErrorLimitTooBig = static_cast<int>(0x809D100Au);
|
|
constexpr std::uint32_t MaxSearchLimit = 92;
|
|
constexpr std::int64_t LibraryUpdateId = 0;
|
|
|
|
bool IsContentColumn(std::int32_t column) {
|
|
switch (column) {
|
|
case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 9: case 10: case 11: case 12: case 14: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool IsSortColumn(std::int32_t column) {
|
|
switch (column) {
|
|
case 1: case 4: case 5: case 6: case 9: case 10: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool IsConnector(std::int32_t connector) { return connector >= 0 && connector <= 2; }
|
|
|
|
void ValidateColumnSets(const char* funcName, const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength) {
|
|
if (columnSet == nullptr && columnSetLength != 0) throw std::invalid_argument(std::string(funcName) + ": null column set");
|
|
for (std::uint32_t i = 0; i < columnSetLength; ++i) {
|
|
const auto& set = columnSet[i];
|
|
if (set.columns == nullptr || set.columns_length <= 0) throw std::invalid_argument(std::string(funcName) + ": empty column set");
|
|
if (!IsConnector(set.logical_connector)) throw std::invalid_argument(std::string(funcName) + ": unknown logical connector " + std::to_string(set.logical_connector));
|
|
for (std::int32_t j = 0; j < set.columns_length; ++j) {
|
|
const auto& column = set.columns[j];
|
|
if (!IsContentColumn(column.column_type)) throw std::invalid_argument(std::string(funcName) + ": unknown column " + std::to_string(column.column_type));
|
|
if (column.column_operator < 0 || column.column_operator > 5) throw std::invalid_argument(std::string(funcName) + ": unknown column operator " + std::to_string(column.column_operator));
|
|
if (column.value == nullptr) throw std::invalid_argument(std::string(funcName) + ": null column value");
|
|
if (!IsConnector(column.logical_connector)) throw std::invalid_argument(std::string(funcName) + ": unknown logical connector " + std::to_string(column.logical_connector));
|
|
}
|
|
}
|
|
}
|
|
|
|
void ValidateOrderBy(const char* funcName, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength) {
|
|
if (orderByConditions == nullptr && orderByConditionsLength != 0) throw std::invalid_argument(std::string(funcName) + ": null order-by conditions");
|
|
for (std::uint32_t i = 0; i < orderByConditionsLength; ++i) {
|
|
const auto& condition = orderByConditions[i];
|
|
if (!IsSortColumn(condition.column_type)) throw std::invalid_argument(std::string(funcName) + ": unknown sort column " + std::to_string(condition.column_type));
|
|
if (condition.sort_type != 0 && condition.sort_type != 1) throw std::invalid_argument(std::string(funcName) + ": unknown sort type " + std::to_string(condition.sort_type));
|
|
}
|
|
}
|
|
|
|
[[noreturn]] void ThrowUnknownMetadataId(const char* funcName, std::int32_t metadataId) {
|
|
throw std::logic_error(std::string(funcName) + ": metadata id " + std::to_string(metadataId) + " was never opened, as the host content library is empty, and the console's error for it is unknown");
|
|
}
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
int APS5_VABI sceContentSearchInit(const ContentSearchInitParam* init_param) {
|
|
if (init_param == nullptr) APS5_INVALID_ARG_EX;
|
|
bool expected = false;
|
|
if (!g_initialized.compare_exchange_strong(expected, true)) throw std::logic_error(std::string(__func__) + ": already initialized");
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchCloseMetadata(std::int32_t metadataId) {
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
ThrowUnknownMetadataId(__func__, metadataId);
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchGetMetadataFieldInfo(std::int32_t metadataId, const char* field, std::int32_t* metadataType, std::int32_t* size) {
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
if (field == nullptr || metadataType == nullptr || size == nullptr) APS5_INVALID_ARG_EX;
|
|
ThrowUnknownMetadataId(__func__, metadataId);
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchGetMetadataValue(std::int32_t metadataId, const char* field, void* value) {
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
if (field == nullptr || value == nullptr) APS5_INVALID_ARG_EX;
|
|
ThrowUnknownMetadataId(__func__, metadataId);
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchOpenMetadata(const char* filePath, std::int32_t* metadataId) {
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
if (filePath == nullptr || metadataId == nullptr) APS5_INVALID_ARG_EX;
|
|
throw std::logic_error(std::string(__func__) + ": " + filePath + " is not in the host content library, which is empty, and the console's error for it is unknown");
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchSearchContent(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::uint32_t offset, std::uint32_t limit, std::int64_t* numOfContent, void* infos, std::int64_t* lastUpdateId) {
|
|
(void)offset;
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
if (numOfContent == nullptr || lastUpdateId == nullptr || (infos == nullptr && limit != 0)) APS5_INVALID_ARG_EX;
|
|
ValidateColumnSets(__func__, columnSet, columnSetLength);
|
|
ValidateOrderBy(__func__, orderByConditions, orderByConditionsLength);
|
|
if (limit > MaxSearchLimit) return ErrorLimitTooBig;
|
|
*numOfContent = 0;
|
|
*lastUpdateId = LibraryUpdateId;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchGetContentLastUpdateId(std::int64_t* lastUpdateId) {
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
if (lastUpdateId == nullptr) APS5_INVALID_ARG_EX;
|
|
*lastUpdateId = LibraryUpdateId;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchOpenMetadataByContentId(std::int64_t contentId, std::int32_t* metadataId) {
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
if (metadataId == nullptr) APS5_INVALID_ARG_EX;
|
|
throw std::logic_error(std::string(__func__) + ": content id " + std::to_string(contentId) + " is not in the host content library, which is empty, and the console's error for it is unknown");
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchGetTotalContentSize(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, std::int64_t* size, std::int64_t* lastUpdateId) {
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
if (size == nullptr || lastUpdateId == nullptr) APS5_INVALID_ARG_EX;
|
|
ValidateColumnSets(__func__, columnSet, columnSetLength);
|
|
*size = 0;
|
|
*lastUpdateId = LibraryUpdateId;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchGetNumOfContent(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, std::int64_t* numOfContent, std::int64_t* lastUpdateId) {
|
|
if (!g_initialized.load()) return ErrorNotInitialized;
|
|
if (numOfContent == nullptr || lastUpdateId == nullptr) APS5_INVALID_ARG_EX;
|
|
ValidateColumnSets(__func__, columnSet, columnSetLength);
|
|
*numOfContent = 0;
|
|
*lastUpdateId = LibraryUpdateId;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceContentSearchTerm(void) {
|
|
bool expected = true;
|
|
if (!g_initialized.compare_exchange_strong(expected, false)) throw std::logic_error(std::string(__func__) + ": not initialized");
|
|
return 0;
|
|
}
|
|
|
|
}
|