Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
#include <cstring>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <stdexcept>
#include <string>
#include <vector>

namespace {

std::mutex g_metadataMutex;
bool g_titleLoaded = false;
bool g_iconAttempted = false;
bool g_iconLoaded = false;
Expand All @@ -32,6 +34,7 @@ void copyToFixedBuffer(char* destination, std::size_t destinationSize, const std
}

void ensureTitleLoaded() {
std::lock_guard lock(g_metadataMutex);
if (g_titleLoaded) return;
const auto resolvedPath = ResolvePath_nid_no_patch(AppMetadataParamJsonGuestPath);
if (!std::filesystem::exists(resolvedPath)) throw std::runtime_error("param.json not found");
Expand Down Expand Up @@ -68,6 +71,7 @@ const bool g_dataMounted = [] {
}();

void ensureIconLoaded() {
std::lock_guard lock(g_metadataMutex);
if (g_iconAttempted) return;
g_iconAttempted = true;
const auto resolvedPath = ResolvePath_nid_no_patch(AppMetadataIconGuestPath);
Expand All @@ -76,10 +80,11 @@ void ensureIconLoaded() {
if (!file.is_open()) throw std::runtime_error("failed to open icon0.png");
file.seekg(0, std::ios::end);
const auto fileSize = file.tellg();
if (file.fail() || fileSize <= 0) throw std::runtime_error("icon0.png is empty or unreadable");
file.seekg(0, std::ios::beg);
std::vector<std::uint8_t> bytes(static_cast<std::size_t>(fileSize));
if (!bytes.empty()) file.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
if (bytes.empty()) throw std::runtime_error("icon0.png is empty");
file.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
if (!file || file.gcount() != static_cast<std::streamsize>(bytes.size())) throw std::runtime_error("failed to read icon0.png");
constexpr std::uint8_t PngSignature[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
if (bytes.size() < 8 || std::memcmp(bytes.data(), PngSignature, sizeof(PngSignature)) != 0) throw std::runtime_error("icon0.png is not a valid PNG file");
g_iconBytes = std::move(bytes);
Expand Down
10 changes: 10 additions & 0 deletions core/libs/prx/libkernel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_kernel_is_stack PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_app_icon_tests ${LIBS_INCLUDE_DIR}/tests/GuestAppIcon.cpp)
target_include_directories(guest_app_icon_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_app_icon_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_app_icon_tests)
add_test(NAME guest_app_icon COMMAND guest_app_icon_tests)
set_tests_properties(guest_app_icon PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_app_icon PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_app_info_tests ${LIBS_INCLUDE_DIR}/tests/GuestAppInfo.cpp)
target_include_directories(guest_app_info_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_app_info_tests PRIVATE libkernel libc)
Expand Down
126 changes: 126 additions & 0 deletions core/libs/tests/GuestAppIcon.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp"
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <initializer_list>
#include <random>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>

namespace {

void Require(bool value) {
if (!value) std::abort();
}

int CheckNoIcon(const char* root) {
std::filesystem::current_path(root);
Require(!HasAppIcon_nid_postfix());
bool threw = false;
try {
GetAppIconData_nid_postfix();
} catch (const std::runtime_error& error) {
threw = std::string_view(error.what()).find("icon not available") != std::string_view::npos;
}
Require(threw);
return 0;
}

int CheckValidIcon(const char* root) {
std::filesystem::current_path(root);
Require(HasAppIcon_nid_postfix());
const auto icon = GetAppIconData_nid_postfix();
Require(icon.bytes != nullptr);
Require(icon.size == 16);
Require(icon.bytes[0] == 0x89 && icon.bytes[1] == 'P' && icon.bytes[2] == 'N' && icon.bytes[3] == 'G');
return 0;
}

int CheckEmptyIcon(const char* root) {
std::filesystem::current_path(root);
bool threw = false;
try {
HasAppIcon_nid_postfix();
} catch (const std::runtime_error& error) {
threw = std::string_view(error.what()).find("icon0.png is empty or unreadable") != std::string_view::npos;
}
Require(threw);
return 0;
}

int CheckCorruptPng(const char* root) {
std::filesystem::current_path(root);
bool threw = false;
try {
HasAppIcon_nid_postfix();
} catch (const std::runtime_error& error) {
threw = std::string_view(error.what()).find("icon0.png is not a valid PNG file") != std::string_view::npos;
}
Require(threw);
return 0;
}

int CheckConcurrentAccess(const char* root) {
std::filesystem::current_path(root);
std::vector<std::thread> threads;
for (int i = 0; i < 8; ++i) {
threads.emplace_back([] {
Require(HasAppIcon_nid_postfix());
const auto icon = GetAppIconData_nid_postfix();
Require(icon.size == 16);
});
}
for (auto& t : threads) t.join();
return 0;
}

void RunCase(const char* self, const char* mode, std::initializer_list<std::pair<const char*, std::string>> files) {
const auto root = std::filesystem::temp_directory_path() / ("guest_app_icon_" + std::to_string(std::random_device{}()));
std::filesystem::remove_all(root);
std::filesystem::create_directories(root / "app0" / "sce_sys");
for (const auto& [name, content] : files) {
std::ofstream stream(root / "app0" / "sce_sys" / name, std::ios::binary);
stream.write(content.data(), static_cast<std::streamsize>(content.size()));
}
const std::string arguments = "\"" + std::string(self) + "\" " + mode + " \"" + root.string() + "\"";
#ifdef _WIN32
const std::string command = "\"" + arguments + "\"";
#else
const std::string command = arguments;
#endif
const int status = std::system(command.c_str());
std::filesystem::remove_all(root);
Require(status == 0);
}

}

int main(int argc, char** argv) {
if (argc == 3) {
const std::string_view mode = argv[1];
if (mode == "--no-icon") return CheckNoIcon(argv[2]);
if (mode == "--valid-icon") return CheckValidIcon(argv[2]);
if (mode == "--empty-icon") return CheckEmptyIcon(argv[2]);
if (mode == "--corrupt-png") return CheckCorruptPng(argv[2]);
if (mode == "--concurrent") return CheckConcurrentAccess(argv[2]);
return 1;
}

const std::string validPng("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00", 16);
const std::string corruptPng("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", 16);
const std::string emptyPng("");

RunCase(argv[0], "--no-icon", {});
RunCase(argv[0], "--valid-icon", {{"icon0.png", validPng}});
RunCase(argv[0], "--empty-icon", {{"icon0.png", emptyPng}});
RunCase(argv[0], "--corrupt-png", {{"icon0.png", corruptPng}});
RunCase(argv[0], "--concurrent", {{"icon0.png", validPng}});

return 0;
}
Loading