diff --git a/.gitignore b/.gitignore index 11632c986..1c5a7db27 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ *.out *.app +_SaveData/ build/ b/ Build/ diff --git a/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp b/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp index 179483edf..551b98e25 100644 --- a/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp +++ b/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp @@ -1,5 +1,6 @@ #include "prx/libSceVideoOut/include/DisplayWindow.hpp" #include "prx/libSceAgcDriver/Execution/include/AspectFit.hpp" +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include "SDL_vulkan.h" #include #include @@ -42,7 +43,8 @@ void DisplayWindow::create(std::uint32_t sourceWidth, std::uint32_t sourceHeight initialWidth = fitted.width; initialHeight = fitted.height; } - window = SDL_CreateWindow("PS5", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, static_cast(initialWidth), static_cast(initialHeight), SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + const auto title = GetAppTitle_nid_postfix(); + window = SDL_CreateWindow(title.value, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, static_cast(initialWidth), static_cast(initialHeight), SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); require(window != nullptr, SDL_GetError()); SDL_SetWindowMinimumSize(window, static_cast(DisplayWindowMinimumWidth), static_cast(DisplayWindowMinimumHeight)); installSubclass(); diff --git a/core/libs/prx/libkernel/AppMetadata/include/AppMetadata.hpp b/core/libs/prx/libkernel/AppMetadata/include/AppMetadata.hpp new file mode 100644 index 000000000..a9eab7266 --- /dev/null +++ b/core/libs/prx/libkernel/AppMetadata/include/AppMetadata.hpp @@ -0,0 +1,28 @@ +#ifndef CORE_LIBS_PRX_LIBKERNEL_APPMETADATA_INCLUDE_APPMETADATA_HPP +#define CORE_LIBS_PRX_LIBKERNEL_APPMETADATA_INCLUDE_APPMETADATA_HPP + +#include + +struct AppTitle { + char value[128]; +}; + +struct AppTitleId { + char value[12]; +}; + +struct AppIconData { + const std::uint8_t* bytes; + std::uint64_t size; +}; + +extern "C" { + +AppTitle GetAppTitle_nid_postfix(); +AppTitleId GetAppTitleId_nid_postfix(); +bool HasAppIcon_nid_postfix(); +AppIconData GetAppIconData_nid_postfix(); + +} + +#endif diff --git a/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp b/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp new file mode 100644 index 000000000..588eb745e --- /dev/null +++ b/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp @@ -0,0 +1,98 @@ +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" +#include "prx/libc/include/General.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +bool g_titleLoaded = false; +bool g_iconAttempted = false; +bool g_iconLoaded = false; +char g_title[128] = {}; +char g_titleId[12] = {}; +std::vector g_iconBytes; + +constexpr const char* AppMetadataParamJsonGuestPath = "/app0/sce_sys/param.json"; +constexpr const char* AppMetadataIconGuestPath = "/app0/sce_sys/icon0.png"; + +struct ParsedParamJson { + std::string title; + std::string titleId; +}; + +ParsedParamJson parseParamJson(const std::filesystem::path& paramJsonPath) { + static_cast(paramJsonPath); + throw std::runtime_error("param.json parsing is not implemented"); +} + +void copyToFixedBuffer(char* destination, std::size_t destinationSize, const std::string& source) { + if (source.empty()) throw std::runtime_error("source string is empty"); + if (source.size() + 1 > destinationSize) throw std::length_error("source string does not fit into destination buffer"); + std::memcpy(destination, source.c_str(), source.size() + 1); +} + +void ensureTitleLoaded() { + 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"); + const auto parsed = parseParamJson(resolvedPath); + copyToFixedBuffer(g_title, sizeof(g_title), parsed.title); + copyToFixedBuffer(g_titleId, sizeof(g_titleId), parsed.titleId); + g_titleLoaded = true; +} + +void ensureIconLoaded() { + if (g_iconAttempted) return; + g_iconAttempted = true; + const auto resolvedPath = ResolvePath_nid_no_patch(AppMetadataIconGuestPath); + if (!std::filesystem::exists(resolvedPath)) return; + std::ifstream file(resolvedPath, std::ios::binary); + if (!file.is_open()) throw std::runtime_error("failed to open icon0.png"); + file.seekg(0, std::ios::end); + const auto fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector bytes(static_cast(fileSize)); + if (!bytes.empty()) file.read(reinterpret_cast(bytes.data()), static_cast(bytes.size())); + if (bytes.empty()) throw std::runtime_error("icon0.png is empty"); + 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); + g_iconLoaded = true; +} + +} + +extern "C" { + +AppTitle GetAppTitle_nid_postfix() { + ensureTitleLoaded(); + AppTitle result{}; + std::memcpy(result.value, g_title, sizeof(result.value)); + return result; +} + +AppTitleId GetAppTitleId_nid_postfix() { + ensureTitleLoaded(); + AppTitleId result{}; + std::memcpy(result.value, g_titleId, sizeof(result.value)); + return result; +} + +bool HasAppIcon_nid_postfix() { + ensureIconLoaded(); + return g_iconLoaded; +} + +AppIconData GetAppIconData_nid_postfix() { + ensureIconLoaded(); + if (!g_iconLoaded) throw std::runtime_error("icon not available"); + return AppIconData{ g_iconBytes.data(), g_iconBytes.size() }; +} + +} diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 22e484a6c..cacc7f77a 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -33,6 +33,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Rtld.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Process.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Time/Time.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/AppMetadata/src/AppMetadata.cpp ) target_include_directories(libkernel PRIVATE ${LIBS_INCLUDE_DIR})