diff --git a/CMakeLists.txt b/CMakeLists.txt index f940723c7..2ebfa982c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.18...4.3) +cmake_minimum_required(VERSION 3.19...4.3) if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0) set(CMAKE_POLICY_VERSION_MINIMUM 3.10 CACHE STRING "Minimum policy version for bundled dependencies") @@ -725,6 +725,15 @@ endif() add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src}) configure_macos_guest_address_space(kyty_emulator) +set(KYTY_TROPHY_UNLOCK_SOUND "${CMAKE_CURRENT_SOURCE_DIR}/assets/sounds/trophy-unlock.wav") +add_custom_target(kyty_trophy_sound + COMMAND ${CMAKE_COMMAND} -E make_directory "$/assets/sounds" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_TROPHY_UNLOCK_SOUND}" + "$/assets/sounds/trophy-unlock.wav" + VERBATIM) +add_dependencies(kyty_emulator kyty_trophy_sound) +install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION assets/sounds) + target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries}) if (WIN32) target_link_libraries(kyty_emulator opengl32) diff --git a/assets/sounds/trophy-unlock.wav b/assets/sounds/trophy-unlock.wav new file mode 100644 index 000000000..22d065c49 Binary files /dev/null and b/assets/sounds/trophy-unlock.wav differ diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c6978a724..d3617c077 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -27,6 +27,7 @@ target_link_libraries(common kyty_git_version spdlog::spdlog ZArchive::zarchive + nlohmann_json::nlohmann_json ) get_property(inc_headers TARGET common PROPERTY INCLUDE_DIRECTORIES) diff --git a/src/common/emulatorConfig.cpp b/src/common/emulatorConfig.cpp index ccceb5f8c..2884b788b 100644 --- a/src/common/emulatorConfig.cpp +++ b/src/common/emulatorConfig.cpp @@ -156,6 +156,10 @@ bool TessellationEnabled() { return g_config->tessellation_enabled; } +bool TrophyEnabled() { + return g_config->trophy_enabled; +} + bool PlayGoHackEnabled() { return g_config->playgo_hack_enabled; } diff --git a/src/common/emulatorConfig.h b/src/common/emulatorConfig.h index 92cea9103..fc29e94bf 100644 --- a/src/common/emulatorConfig.h +++ b/src/common/emulatorConfig.h @@ -74,6 +74,7 @@ struct ConfigOptions { bool renderdoc_enabled = false; bool readback_linear_images = false; bool tessellation_enabled = false; + bool trophy_enabled = true; bool playgo_hack_enabled = false; #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS bool red_zone_protection_enabled = false; @@ -123,6 +124,7 @@ bool GpuAssistedValidationEnabled(); bool RenderDocEnabled(); bool ReadbackLinearImagesEnabled(); bool TessellationEnabled(); +bool TrophyEnabled(); bool PlayGoHackEnabled(); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS bool RedZoneProtectionEnabled(); diff --git a/src/common/trophies.cpp b/src/common/trophies.cpp new file mode 100644 index 000000000..b28dbb554 --- /dev/null +++ b/src/common/trophies.cpp @@ -0,0 +1,264 @@ +#include "common/trophies.h" + +#include "common/file.h" + +#include +#include +#include +#include +#include +#include + +namespace Common::Trophies { +namespace { + +using Json = nlohmann::json; +using Files = std::map>; + +constexpr uint64_t MaxUnlocksSize = uint64_t {1} << 20u; +constexpr uint64_t MaxPackageSize = uint64_t {1} << 30u; + +uint64_t ReadBigEndian(const std::byte* data, size_t size) { + uint64_t value = 0; + for (size_t i = 0; i < size; ++i) { + value = (value << 8u) | std::to_integer(data[i]); + } + return value; +} + +int ReadId(const Json& value) { + if (value.is_number_integer()) { + const auto number = value.get(); + return number >= 0 && number <= INT32_MAX ? static_cast(number) : -1; + } + if (!value.is_string()) { + return -1; + } + const auto& text = value.get_ref(); + int id = -1; + const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), id); + return error == std::errc {} && end == text.data() + text.size() && id >= 0 ? id : -1; +} + +Json ReadJson(const Files& files, std::string_view name) { + const auto entry = files.find(name); + if (entry == files.end()) { + return {}; + } + return Json::parse(entry->second.begin(), entry->second.end(), nullptr, false); +} + +Package ParsePackage(std::span data, int console_language) { + if (data.size() < 0x40 || data.size() > MaxPackageSize || + ReadBigEndian(data.data(), 4) != 0xb228c60a || ReadBigEndian(data.data() + 4, 4) != 1) { + return {}; + } + const auto size = ReadBigEndian(data.data() + 8, 8); + const auto count = ReadBigEndian(data.data() + 0x10, 4); + const auto toc = ReadBigEndian(data.data() + 0x14, 4); + if (size < 0x40 || size > data.size() || count > 4096 || toc > size || + 0x20 + count * 0x40 > size - toc) { + return {}; + } + Files files; + for (uint64_t i = 0; i < count; ++i) { + const auto* entry = data.data() + toc + 0x20 + i * 0x40; + const auto* name = reinterpret_cast(entry); + const std::string_view filename(name, std::find(name, name + 0x20, '\0') - name); + const auto offset = ReadBigEndian(entry + 0x20, 8); + const auto length = ReadBigEndian(entry + 0x28, 8); + if (offset > size || length > size - offset) { + return {}; + } + files.emplace(filename, data.subspan(offset, length)); + } + const auto conf = ReadJson(files, "tropconf.json"); + if (!conf.is_object() || !conf.contains("trophies") || !conf["trophies"].is_array() || + conf["trophies"].size() > 1000 || !conf.contains("defaultLanguage") || + !conf["defaultLanguage"].is_string()) { + return {}; + } + static constexpr std::array locales = { + "ja-JP", "en-US", "fr-FR", "es-ES", "de-DE", "it-IT", "nl-NL", "pt-PT", + "ru-RU", "ko-KR", "zh-Hant", "zh-Hans", "fi-FI", "sv-SE", "da-DK", "no-NO", + "pl-PL", "pt-BR", "en-GB", "tr-TR", "es-419", "ar-AE", "fr-CA", "cs-CZ", + "hu-HU", "el-GR", "ro-RO", "th-TH", "vi-VN", "id-ID"}; + const auto locale = + locales[console_language >= 0 && console_language < locales.size() ? console_language : 1]; + auto meta = ReadJson(files, fmt::format("tropmeta_{}.json", locale)); + if (meta.is_null()) { + meta = ReadJson(files, fmt::format("tropmeta_{}.json", + conf["defaultLanguage"].get_ref())); + } + if (!meta.is_object() || !meta.contains("metadata") || !meta["metadata"].is_object()) { + return {}; + } + const auto& texts = meta["metadata"]; + if (!texts.contains("titleMetadata") || !texts["titleMetadata"].is_object() || + !texts["titleMetadata"].contains("name") || !texts["titleMetadata"]["name"].is_string() || + !texts.contains("trophyMetadata") || !texts["trophyMetadata"].is_array()) { + return {}; + } + Package package; + package.title = texts["titleMetadata"]["name"].get(); + package.groups.emplace(-1, package.title); + uint64_t icon_bytes = 0; + for (const auto& definition: conf["trophies"]) { + if (!definition.is_object() || !definition.contains("id") || + !definition.contains("grade") || !definition["grade"].is_string()) { + return {}; + } + Trophy trophy; + trophy.id = ReadId(definition["id"]); + const auto grade = definition["grade"].get(); + const auto grade_index = std::string_view("PGSB").find(grade); + if (trophy.id < 0 || grade.size() != 1 || grade_index == std::string_view::npos) { + return {}; + } + trophy.grade = static_cast(grade_index) + 1; + if (definition.contains("groupId")) { + trophy.group_id = ReadId(definition["groupId"]); + if (trophy.group_id < 0) { + return {}; + } + } + if (definition.contains("platinumTrophyId")) { + trophy.platinum_id = ReadId(definition["platinumTrophyId"]); + } + trophy.hidden = definition.contains("hidden") && definition["hidden"] == true; + trophy.has_reward = definition.contains("hasReward") && definition["hasReward"] == true; + if (definition.contains("unlockCondition")) { + const auto& condition = definition["unlockCondition"]; + if (condition.is_object() && condition.contains("progressive") && + condition["progressive"] == true) { + if (!condition.contains("targetValue") || !condition["targetValue"].is_string()) { + return {}; + } + const auto& value = condition["targetValue"].get_ref(); + uint64_t target = 0; + const auto [end, error] = + std::from_chars(value.data(), value.data() + value.size(), target); + if (error != std::errc {} || end != value.data() + value.size()) { + return {}; + } + trophy.target = target; + } + } + const auto icon = files.find(fmt::format("trop{:04}.png", trophy.id)); + if (icon != files.end()) { + if (icon->second.size() > size - icon_bytes) { + return {}; + } + trophy.icon_png.assign(icon->second.begin(), icon->second.end()); + icon_bytes += icon->second.size(); + } + package.groups.try_emplace(trophy.group_id); + if (package.groups.size() > 50 || + !package.trophies.emplace(trophy.id, std::move(trophy)).second) { + return {}; + } + } + if (texts.contains("groupMetadata") && texts["groupMetadata"].is_array()) { + for (const auto& group: texts["groupMetadata"]) { + if (group.is_object() && group.contains("id") && group.contains("name") && + group["name"].is_string()) { + const auto found = package.groups.find(ReadId(group["id"])); + if (found != package.groups.end()) { + found->second = group["name"].get(); + } + } + } + } + for (const auto& text: texts["trophyMetadata"]) { + if (!text.is_object() || !text.contains("id")) { + continue; + } + const auto found = package.trophies.find(ReadId(text["id"])); + if (found == package.trophies.end()) { + continue; + } + for (const auto& [key, field]: {std::pair {"name", &found->second.name}, + {"detail", &found->second.description}, + {"reward", &found->second.reward}}) { + if (text.contains(key) && text[key].is_string()) { + *field = text[key].get(); + } + } + } + return package; +} + +} // namespace + +Package LoadPackage(const std::filesystem::path& path, int console_language) { + File file(path, File::Mode::Read); + if (file.IsInvalid() || file.Size() > MaxPackageSize) { + return {}; + } + return ParsePackage(file.ReadWholeBuffer(), console_language); +} + +std::filesystem::path PackagePath(uint32_t service_label) { + return std::filesystem::path(PackageDirectory) / fmt::format("trophy{:02}.ucp", service_label); +} + +std::filesystem::path UnlocksPath(const std::filesystem::path& root, std::string_view title_id, + int user_id, uint32_t service_label) { + if (title_id.empty() || !std::all_of(title_id.begin(), title_id.end(), [](char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + c == '_' || c == '-'; + })) { + return {}; + } + return root / "_SaveData" / title_id / + fmt::format("trophies_{}_{}.json", user_id, service_label); +} + +std::set LoadUnlocks(const std::filesystem::path& path) { + File file(path, File::Mode::Read); + if (file.IsInvalid() || file.Size() > MaxUnlocksSize) { + return {}; + } + const auto bytes = file.ReadWholeBuffer(); + const auto json = Json::parse(bytes.begin(), bytes.end(), nullptr, false); + if (!json.is_object() || !json.contains("unlockedTrophies") || + !json["unlockedTrophies"].is_array()) { + return {}; + } + std::set unlocked; + for (const auto& value: json["unlockedTrophies"]) { + const auto id = ReadId(value); + if (id >= 0) { + unlocked.insert(id); + } + } + return unlocked; +} + +bool SaveUnlocks(const std::filesystem::path& path, const std::set& unlocked) { + if (path.empty() || !File::CreateDirectories(path.parent_path())) { + return false; + } + const auto text = Json {{"unlockedTrophies", unlocked}}.dump(); + auto temporary = path; + temporary += ".tmp"; + File file; + if (text.size() > MaxUnlocksSize || !file.Create(temporary)) { + return false; + } + uint32_t written = 0; + file.Write(text.data(), static_cast(text.size()), &written); + const bool complete = written == text.size() && file.Flush(); + file.Close(); + std::error_code error; + if (complete) { + std::filesystem::rename(temporary, path, error); + if (!error) { + return true; + } + } + std::filesystem::remove(temporary, error); + return false; +} + +} // namespace Common::Trophies diff --git a/src/common/trophies.h b/src/common/trophies.h new file mode 100644 index 000000000..7be1ed726 --- /dev/null +++ b/src/common/trophies.h @@ -0,0 +1,48 @@ +#ifndef KYTY_COMMON_TROPHIES_H_ +#define KYTY_COMMON_TROPHIES_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Common::Trophies { + +inline constexpr char PackageDirectory[] = "sce_sys/trophy2"; + +struct Trophy { + int id = 0; + int group_id = -1; + int platinum_id = -1; + int grade = 0; + std::optional target; + std::string name; + std::string description; + std::string reward; + std::vector icon_png; + bool hidden = false; + bool has_reward = false; +}; + +struct Package { + std::string title; + std::map groups; + std::map trophies; +}; + +[[nodiscard]] Package LoadPackage(const std::filesystem::path& path, int console_language); +[[nodiscard]] std::filesystem::path PackagePath(uint32_t service_label); +[[nodiscard]] std::filesystem::path UnlocksPath(const std::filesystem::path& root, + std::string_view title_id, int user_id, + uint32_t service_label); +[[nodiscard]] std::set LoadUnlocks(const std::filesystem::path& path); +[[nodiscard]] bool SaveUnlocks(const std::filesystem::path& path, const std::set& unlocked); + +} // namespace Common::Trophies + +#endif // KYTY_COMMON_TROPHIES_H_ diff --git a/src/graphics/presentation/systemOverlay.cpp b/src/graphics/presentation/systemOverlay.cpp index 56ed70617..ab4863b88 100644 --- a/src/graphics/presentation/systemOverlay.cpp +++ b/src/graphics/presentation/systemOverlay.cpp @@ -3,14 +3,17 @@ #include #include "common/assert.h" +#include "common/logging/log.h" #include "common/stringUtils.h" #include "graphics/host_gpu/graphicContext.h" #include "imgui.h" +#include "imgui_internal.h" #include "imgui_impl_vulkan.h" #include "libs/controller.h" #include "libs/dialog.h" #include "libs/ime.h" #include "libs/imeDialog.h" +#include "stb_image.h" #include #include @@ -18,12 +21,17 @@ #include #include #include +#include #include +#include +#include #include +#include #include #include #include #include +#include namespace Libs::Graphics { @@ -178,6 +186,14 @@ bool g_input_lifecycle_active = false; bool g_controller_captured = false; OverlaySession g_session; SDL_Window* g_input_window = nullptr; +struct TrophyNotification { + std::string name; + int32_t grade; + std::vector icon_png; +}; +std::mutex g_trophy_notification_mutex; +std::deque g_trophy_notifications; +std::atomic g_trophy_notification_active {false}; void ClearInputEvents() { std::scoped_lock lock(g_input_mutex); @@ -467,8 +483,9 @@ SystemOverlayVisualState GetSystemOverlayVisualState() noexcept { const auto core = CoreIme::GetVisualState(); const auto dialog = DialogIme::GetVisualState(); const auto system = SystemDialog::GetVisualState(); - return {core.active || dialog.active || system.active, - core.revision + dialog.revision + system.revision}; + const bool trophy_active = g_trophy_notification_active.load(std::memory_order_acquire); + return {core.active || dialog.active || system.active || trophy_active, + core.revision + dialog.revision + system.revision + (trophy_active ? 1u : 0u)}; } bool ProcessSystemOverlayInput(const SDL_Event& event) { @@ -599,12 +616,34 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) { } } +void NotifyTrophyUnlocked(std::string_view name, int32_t grade, + std::span icon_png) { + std::scoped_lock lock(g_trophy_notification_mutex); + g_trophy_notifications.push_back( + {std::string(name), grade, std::vector(icon_png.begin(), icon_png.end())}); + g_trophy_notification_active.store(true, std::memory_order_release); +} + struct SystemOverlay::Impl { + using Clock = std::chrono::steady_clock; + explicit Impl(GraphicContext& context): graphics(context) {} ~Impl() { + SDL_DestroyAudioStream(trophy_sound_stream); + SDL_free(trophy_sound_data); + if (trophy_sound_initialized) { + SDL_QuitSubSystem(SDL_INIT_AUDIO); + } ReleaseVulkan(); if (imgui_context != nullptr) { + ImGui::SetCurrentContext(imgui_context); + if (trophy_notification_image) { + ImGui::UnregisterUserTexture(trophy_notification_image.get()); + } + for (const auto& texture: retired_trophy_images) { + ImGui::UnregisterUserTexture(texture.get()); + } ImGui::DestroyContext(imgui_context); } } @@ -631,6 +670,75 @@ struct SystemOverlay::Impl { style.ItemSpacing = {8.0f, 8.0f}; } + void PlayTrophySound() { + if (!trophy_sound_attempted) { + trophy_sound_attempted = true; + trophy_sound_initialized = SDL_InitSubSystem(SDL_INIT_AUDIO); + if (!trophy_sound_initialized) { + LOGF("[Trophy] could not initialize audio: %s\n", SDL_GetError()); + return; + } + const char* base_path = SDL_GetBasePath(); + if (base_path == nullptr) { + LOGF("[Trophy] could not locate sound: %s\n", SDL_GetError()); + return; + } + const std::string path = std::string(base_path) + "assets/sounds/trophy-unlock.wav"; + SDL_AudioSpec spec {}; + if (!SDL_LoadWAV(path.c_str(), &spec, &trophy_sound_data, &trophy_sound_size)) { + LOGF("[Trophy] could not load %s: %s\n", path.c_str(), SDL_GetError()); + return; + } + if (trophy_sound_size > static_cast(std::numeric_limits::max())) { + LOGF("[Trophy] sound is too large: %s\n", path.c_str()); + return; + } + trophy_sound_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, + &spec, nullptr, nullptr); + if (trophy_sound_stream == nullptr) { + LOGF("[Trophy] could not open audio stream: %s\n", SDL_GetError()); + return; + } + } + if (trophy_sound_stream != nullptr && + (!SDL_PutAudioStreamData(trophy_sound_stream, trophy_sound_data, + static_cast(trophy_sound_size)) || + !SDL_ResumeAudioStreamDevice(trophy_sound_stream))) { + LOGF("[Trophy] could not play sound: %s\n", SDL_GetError()); + } + } + + std::unique_ptr LoadTrophyIcon(std::span encoded) { + if (encoded.empty() || + encoded.size() > static_cast(std::numeric_limits::max())) { + return nullptr; + } + + const auto* data = reinterpret_cast(encoded.data()); + const int size = static_cast(encoded.size()); + int width = 0; + int height = 0; + if (!stbi_info_from_memory(data, size, &width, &height, nullptr) || width <= 0 || + height <= 0 || width > 512 || height > 512) { + LOGF("[Trophy] invalid icon or dimensions exceed 512x512\n"); + return nullptr; + } + auto* pixels = stbi_load_from_memory(data, size, &width, &height, nullptr, 4); + if (pixels == nullptr) { + LOGF("[Trophy] could not decode trophy icon: %s\n", stbi_failure_reason()); + return nullptr; + } + + auto texture = std::make_unique(); + texture->Create(ImTextureFormat_RGBA32, width, height); + std::memcpy(texture->Pixels, pixels, + static_cast(width) * static_cast(height) * 4); + texture->UseColors = true; + stbi_image_free(pixels); + ImGui::RegisterUserTexture(texture.get()); + return texture; + } + void EnsureVulkan(vk::Format format, uint32_t image_count) { EnsureContext(); if (vulkan_initialized) { @@ -952,9 +1060,104 @@ struct SystemOverlay::Impl { } } + void DrawTrophyNotification(vk::Extent2D frame_extent, Clock::time_point now) { + std::erase_if(retired_trophy_images, [](const auto& texture) { + if (texture->Status != ImTextureStatus_Destroyed) { + return false; + } + ImGui::UnregisterUserTexture(texture.get()); + return true; + }); + constexpr float TOAST_LIFETIME = 5.0f; + if (trophy_notification && + std::chrono::duration(now - trophy_notification_born).count() >= + TOAST_LIFETIME) { + trophy_notification.reset(); + if (trophy_notification_image) { + // Keep the texture registered until the backend retires in-flight frames. + trophy_notification_image->WantDestroyNextFrame = true; + retired_trophy_images.push_back(std::move(trophy_notification_image)); + } + } + if (!trophy_notification) { + { + std::scoped_lock lock(g_trophy_notification_mutex); + if (g_trophy_notifications.empty()) { + g_trophy_notification_active.store(!retired_trophy_images.empty(), + std::memory_order_release); + return; + } + trophy_notification = std::move(g_trophy_notifications.front()); + g_trophy_notifications.pop_front(); + } + trophy_notification_born = now; + trophy_notification_image = LoadTrophyIcon(trophy_notification->icon_png); + PlayTrophySound(); + } + const float age = std::chrono::duration(now - trophy_notification_born).count(); + const float screen_scale = + std::clamp(std::min(static_cast(frame_extent.width) / 1920.0f, + static_cast(frame_extent.height) / 1080.0f), + 0.65f, 1.25f); + const float width = std::max( + 0.0f, std::min(440.0f * screen_scale, static_cast(frame_extent.width) - 32.0f)); + const float height = 104.0f * screen_scale; + const float fade_in = std::clamp(age / 0.22f, 0.0f, 1.0f); + const float fade_out = std::clamp((TOAST_LIFETIME - age) / 0.4f, 0.0f, 1.0f); + const float alpha = std::min(fade_in, fade_out); + const float slide = (1.0f - fade_in) * (width + 24.0f); + const ImVec2 top_left {static_cast(frame_extent.width) - 24.0f - width + slide, + 32.0f}; + const ImVec2 bottom_right {top_left.x + width, top_left.y + height}; + auto* draw = ImGui::GetForegroundDrawList(); + draw->AddRectFilled({top_left.x + 5.0f, top_left.y + 6.0f}, + {bottom_right.x + 5.0f, bottom_right.y + 6.0f}, + IM_COL32(0, 0, 0, static_cast(95.0f * alpha)), 10.0f); + draw->AddRectFilled(top_left, bottom_right, + IM_COL32(18, 21, 29, static_cast(242.0f * alpha)), 10.0f); + draw->AddRect(top_left, bottom_right, + IM_COL32(190, 154, 78, static_cast(200.0f * alpha)), 10.0f, + ImDrawFlags_RoundCornersAll, 1.5f); + + if (trophy_notification_image != nullptr) { + constexpr float ICON_SIZE = 88.0f; + const ImVec2 icon_min {top_left.x + 8.0f * screen_scale, + top_left.y + (height - ICON_SIZE * screen_scale) * 0.5f}; + const ImVec2 icon_max {icon_min.x + ICON_SIZE * screen_scale, + icon_min.y + ICON_SIZE * screen_scale}; + draw->AddImage(trophy_notification_image->GetTexRef(), icon_min, icon_max, {0, 0}, + {1, 1}, IM_COL32(255, 255, 255, static_cast(255.0f * alpha))); + } + + const ImVec2 text_position {top_left.x + + (trophy_notification_image ? 112.0f : 18.0f) * screen_scale, + top_left.y + 18.0f * screen_scale}; + draw->PushClipRect(top_left, {bottom_right.x - 12.0f * screen_scale, bottom_right.y}, true); + draw->AddText(nullptr, 13.0f * screen_scale, text_position, + IM_COL32(232, 203, 139, static_cast(255.0f * alpha)), "Trophy earned"); + draw->AddText(nullptr, 21.0f * screen_scale, + {text_position.x, text_position.y + 22.0f * screen_scale}, + IM_COL32(255, 255, 255, static_cast(255.0f * alpha)), + trophy_notification->name.c_str()); + constexpr std::array GRADE_LABELS {"Platinum", "Gold", "Silver", "Bronze"}; + constexpr std::array GRADE_COLORS { + IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255), IM_COL32(211, 220, 232, 255), + IM_COL32(211, 142, 91, 255)}; + const int grade = trophy_notification->grade; + if (grade > 0 && grade <= static_cast(GRADE_LABELS.size())) { + draw->AddText(nullptr, 13.0f * screen_scale, + {text_position.x, text_position.y + 50.0f * screen_scale}, + ImGui::GetColorU32(GRADE_COLORS[grade - 1], alpha), + GRADE_LABELS[grade - 1]); + } + draw->PopClipRect(); + } + bool PrepareFrame(vk::Extent2D frame_extent, vk::Format format, uint32_t image_count) { OverlaySnapshot snapshot; - if (!GetOverlaySnapshot(&snapshot)) { + const bool has_overlay = GetOverlaySnapshot(&snapshot); + const auto now = Clock::now(); + if (!has_overlay && !g_trophy_notification_active.load(std::memory_order_acquire)) { return false; } const auto prepared_session = snapshot.session; @@ -977,7 +1180,6 @@ struct SystemOverlay::Impl { auto& io = ImGui::GetIO(); io.DisplaySize = {static_cast(frame_extent.width), static_cast(frame_extent.height)}; - const auto now = std::chrono::steady_clock::now(); io.DeltaTime = last_frame == std::chrono::steady_clock::time_point {} ? 1.0f / 60.0f : std::clamp(std::chrono::duration(now - last_frame).count(), @@ -985,15 +1187,19 @@ struct SystemOverlay::Impl { last_frame = now; ImGui_ImplVulkan_NewFrame(); ImGui::NewFrame(); - if (!GetOverlaySnapshot(&snapshot) || snapshot.session != prepared_session) { + GetOverlaySnapshot(&snapshot); + if (snapshot.session != prepared_session) { ImGui::EndFrame(); return false; } - if (snapshot.session.kind == OverlayKind::Dialog) { - DrawDialog(snapshot.dialog, frame_extent); - } else { - DrawIme(snapshot.ime, frame_extent); + if (has_overlay) { + if (snapshot.session.kind == OverlayKind::Dialog) { + DrawDialog(snapshot.dialog, frame_extent); + } else { + DrawIme(snapshot.ime, frame_extent); + } } + DrawTrophyNotification(frame_extent, now); ImGui::Render(); extent = frame_extent; return true; @@ -1040,9 +1246,18 @@ struct SystemOverlay::Impl { float button_height = 42.0f; ImVec2 panel_offset {}; ImVec2 right_stick {}; - OverlaySession session; - vk::Extent2D extent {}; - std::chrono::steady_clock::time_point last_frame; + OverlaySession session; + std::unique_ptr trophy_notification_image; + std::optional trophy_notification; + Clock::time_point trophy_notification_born; + std::vector> retired_trophy_images; + SDL_AudioStream* trophy_sound_stream = nullptr; + uint8_t* trophy_sound_data = nullptr; + uint32_t trophy_sound_size = 0; + bool trophy_sound_attempted = false; + bool trophy_sound_initialized = false; + vk::Extent2D extent {}; + std::chrono::steady_clock::time_point last_frame; }; SystemOverlay::SystemOverlay(GraphicContext& graphics): m_impl(std::make_unique(graphics)) {} diff --git a/src/graphics/presentation/systemOverlay.h b/src/graphics/presentation/systemOverlay.h index 7fbb157f2..1ecdbc3d8 100644 --- a/src/graphics/presentation/systemOverlay.h +++ b/src/graphics/presentation/systemOverlay.h @@ -4,7 +4,10 @@ #include "common/common.h" #include "graphics/host_gpu/vulkanCommon.h" +#include #include +#include +#include union SDL_Event; struct SDL_Window; @@ -22,6 +25,8 @@ void InitializeSystemOverlayInput(SDL_Window* window); void ShutdownSystemOverlayInput(); bool ProcessSystemOverlayInput(const SDL_Event& event); SystemOverlayVisualState GetSystemOverlayVisualState() noexcept; +void NotifyTrophyUnlocked(std::string_view name, int32_t grade, + std::span icon_png); class SystemOverlay final { public: diff --git a/src/launcher/CMakeLists.txt b/src/launcher/CMakeLists.txt index d57962375..77949e559 100644 --- a/src/launcher/CMakeLists.txt +++ b/src/launcher/CMakeLists.txt @@ -96,6 +96,7 @@ install(TARGETS launcher if(APPLE) install(TARGETS kyty_emulator DESTINATION "KytyPS5.app/Contents/MacOS") + install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION "KytyPS5.app/Contents/Resources/assets/sounds") # Deploy and ad-hoc sign the Qt frameworks and plugins in the bundle. find_program(KYTY_MACDEPLOYQT NAMES macdeployqt PATHS "${Qt6_DIR}/../../../bin" NO_DEFAULT_PATH) if(NOT KYTY_MACDEPLOYQT) diff --git a/src/launcher/forms/configuration_edit_dialog.ui b/src/launcher/forms/configuration_edit_dialog.ui index 52589949a..82fdd0072 100644 --- a/src/launcher/forms/configuration_edit_dialog.ui +++ b/src/launcher/forms/configuration_edit_dialog.ui @@ -6,7 +6,7 @@ 0 0 - 760 + 840 620 @@ -182,7 +182,22 @@ - + + + Notifications + + + + Show trophy unlock notifications and play their sound. +To use your own sound, replace the emulator's assets/sounds/trophy-unlock.wav with a WAV file and restart the game. + Enable trophy notifications + true + + + + + + Debugging && logs diff --git a/src/launcher/include/configuration.h b/src/launcher/include/configuration.h index 92c05b8da..5b88a28e6 100644 --- a/src/launcher/include/configuration.h +++ b/src/launcher/include/configuration.h @@ -122,6 +122,7 @@ public: bool hide_cursor_enabled = false; bool readback_linear_images = false; bool tessellation_enabled = false; + bool trophy_enabled = true; int vblank_frequency = 60; int console_language = DEFAULT_CONSOLE_LANGUAGE; bool vulkan_validation_enabled = false; @@ -154,6 +155,7 @@ public: hide_cursor_enabled = other.hide_cursor_enabled; readback_linear_images = other.readback_linear_images; tessellation_enabled = other.tessellation_enabled; + trophy_enabled = other.trophy_enabled; vblank_frequency = other.vblank_frequency; console_language = other.console_language; vulkan_validation_enabled = other.vulkan_validation_enabled; @@ -201,6 +203,7 @@ public: KYTY_CFG_SET(hide_cursor_enabled); KYTY_CFG_SET(readback_linear_images); KYTY_CFG_SET(tessellation_enabled); + KYTY_CFG_SET(trophy_enabled); KYTY_CFG_SET(vblank_frequency); KYTY_CFG_SET(console_language); KYTY_CFG_SET(vulkan_validation_enabled); @@ -244,6 +247,7 @@ public: KYTY_CFG_GET(hide_cursor_enabled); KYTY_CFG_GET(readback_linear_images); KYTY_CFG_GET(tessellation_enabled); + trophy_enabled = s->value("trophy_enabled", trophy_enabled).toBool(); vblank_frequency = s->value("vblank_frequency", vblank_frequency).toInt(); console_language = s->value("console_language", console_language).toInt(); if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) { diff --git a/src/launcher/include/configurationListWidget.h b/src/launcher/include/configurationListWidget.h index 34b58ad96..097fa8e5b 100644 --- a/src/launcher/include/configurationListWidget.h +++ b/src/launcher/include/configurationListWidget.h @@ -28,6 +28,7 @@ public: ~ConfigurationListWidget() override; void SetRunEnabled(bool flag) { m_run_enabled = flag; } + void SetRuntimeDirectory(const QString& directory) { m_runtime_directory = directory; } [[nodiscard]] const ConfigurationItem* GetSelectedItem() const { return m_selected_item; } ConfigurationItem* GetSelectedItem() { return m_selected_item; } @@ -78,6 +79,7 @@ private: bool m_run_enabled = true; Ui::ConfigurationListWidget* m_ui = nullptr; QString m_settings_file; + QString m_runtime_directory; QStringList m_game_dirs; Configuration m_global_info; QMap m_custom_infos; diff --git a/src/launcher/include/gameContent.h b/src/launcher/include/gameContent.h index 08765170d..f5d31ac27 100644 --- a/src/launcher/include/gameContent.h +++ b/src/launcher/include/gameContent.h @@ -12,7 +12,6 @@ namespace GameContent { inline constexpr uint64_t MaxMetadataSize = uint64_t {1} << 20u; inline constexpr uint64_t MaxImageSize = uint64_t {32} << 20u; -inline constexpr uint64_t MaxTrophyPackageSize = uint64_t {128} << 20u; [[nodiscard]] std::filesystem::path ToPath(const QString& path); [[nodiscard]] QString FromPath(const std::filesystem::path& path); diff --git a/src/launcher/include/trophyViewerDialog.h b/src/launcher/include/trophyViewerDialog.h index 9dc013664..589cf2282 100644 --- a/src/launcher/include/trophyViewerDialog.h +++ b/src/launcher/include/trophyViewerDialog.h @@ -12,10 +12,11 @@ public: explicit TrophyViewerDialog(QWidget* parent = nullptr); static bool HasTrophyData(const Configuration* info); - static void ShowForGame(const Configuration* info, QWidget* parent); + static void ShowForGame(const Configuration* info, const QString& runtime_directory, + QWidget* parent); private: - bool LoadGame(const Configuration& info, QString& error); + bool LoadGame(const Configuration& info, const QString& runtime_directory, QString& error); QTabWidget* m_tabs = nullptr; }; diff --git a/src/launcher/src/configurationEditDialog.cpp b/src/launcher/src/configurationEditDialog.cpp index 682be2a91..140f881a1 100644 --- a/src/launcher/src/configurationEditDialog.cpp +++ b/src/launcher/src/configurationEditDialog.cpp @@ -123,6 +123,7 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p : QDialog(parent, Qt::WindowCloseButtonHint), m_ui(new Ui::ConfigurationEditDialog), m_info(info) { m_ui->setupUi(this); + setMinimumWidth(width()); InitGameDirectories(); m_ui->controller_group->setVisible(false); @@ -164,9 +165,6 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p m_ui->lineEdit_printf_file->setEnabled(log == Configuration::LogDirection::File); }); - // Keep the controls at a usable minimum while allowing the settings window - // and its expanding fields to use any additional space the user gives them. - layout()->setSizeConstraint(QLayout::SetMinimumSize); setSizeGripEnabled(true); restoreGeometry(g_last_geometry); @@ -276,6 +274,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) { m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled); m_ui->checkBox_readback->setChecked(info.readback_linear_images); m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled); + m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled); m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency); m_ui->comboBox_console_language->clear(); m_ui->comboBox_console_language->addItems(CONSOLE_LANGUAGE_NAMES); @@ -429,6 +428,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked(); info.readback_linear_images = ui.checkBox_readback->isChecked(); info.tessellation_enabled = ui.checkBox_tessellation->isChecked(); + info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked(); info.vblank_frequency = ui.spinBox_vblank_frequency->value(); info.console_language = ui.comboBox_console_language->currentIndex(); info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked(); diff --git a/src/launcher/src/configurationListWidget.cpp b/src/launcher/src/configurationListWidget.cpp index 5fb5e9161..39eaa44b8 100644 --- a/src/launcher/src/configurationListWidget.cpp +++ b/src/launcher/src/configurationListWidget.cpp @@ -835,7 +835,8 @@ void ConfigurationListWidget::ViewTrophies() { return; } - TrophyViewerDialog::ShowForGame(&item->GetInfo(), this); + const auto config = CreateConfiguration(*item); + TrophyViewerDialog::ShowForGame(config.get(), m_runtime_directory, this); } void ConfigurationListWidget::open_game_folder() { diff --git a/src/launcher/src/mainDialog.cpp b/src/launcher/src/mainDialog.cpp index 43792d4fe..dbc6d4e00 100644 --- a/src/launcher/src/mainDialog.cpp +++ b/src/launcher/src/mainDialog.cpp @@ -170,6 +170,7 @@ void MainDialogPrivate::FindInterpreter() { } bool found = QFile::exists(m_interpreter); + m_ui->widget->SetRuntimeDirectory(QFileInfo(m_interpreter).absolutePath()); if (found) { m_ui->label_Interpreter->setText(tr("Emulator: ") + m_interpreter); @@ -244,6 +245,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) { args << "--hide-cursor"; } args << "--readback-linear-images" << BoolArg(info.readback_linear_images); + args << "--trophy-notifications" << BoolArg(info.trophy_enabled); if (info.tessellation_enabled) { args << "--tessellation"; } diff --git a/src/launcher/src/trophyViewerDialog.cpp b/src/launcher/src/trophyViewerDialog.cpp index e08d78d10..886a6811e 100644 --- a/src/launcher/src/trophyViewerDialog.cpp +++ b/src/launcher/src/trophyViewerDialog.cpp @@ -1,462 +1,62 @@ #include "trophyViewerDialog.h" #include "common/archive.h" +#include "common/trophies.h" #include "configuration.h" #include "gameContent.h" #include -#include -#include #include -#include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include #include #include -#include -#include -#include #include #include #include -#include #include -#include -#include -#include - -#include namespace { -constexpr quint32 UCP_MAGIC = 0xb228c60a; -constexpr quint32 UCP_VERSION = 1; -constexpr int UCP_HEADER_LEN = 0x40; -constexpr int UCP_TOC_SKIP = 0x20; -constexpr int UCP_ENTRY_LEN = 0x40; -constexpr int UCP_NAME_LEN = 0x20; -constexpr quint32 UCP_MAX_FILES = 4096; -constexpr quint64 UCP_MAX_EXTRACTED_SIZE = quint64 {64} << 20u; - -struct UcpEntry { - QString name; - quint64 offset = 0; - quint64 size = 0; -}; - -struct TrophyDefinition { - QString id; - QString grade; - bool hidden = false; - bool has_reward = false; -}; - -struct TrophyText { - QString name; - QString detail; - QString reward; -}; - -struct TrophyRow { - QString id; - QString name; - QString detail; - QString grade; - QString reward; - bool hidden = false; - bool has_reward = false; - QPixmap icon; -}; - -struct TrophySet { - QString tab_title; - QList trophies; -}; - -static bool CanRead(const QByteArray& data, qsizetype offset, qsizetype size) { - return offset >= 0 && size >= 0 && offset <= data.size() && size <= data.size() - offset; -} - -static QString ReadFixedString(const QByteArray& data, qsizetype offset, qsizetype max_size) { - if (!CanRead(data, offset, max_size)) { - return {}; - } - - qsizetype len = 0; - while (len < max_size && data.at(offset + len) != '\0') { - len++; - } - - return QString::fromLatin1(data.constData() + offset, len); -} - -static bool IsUsedUcpEntry(const QString& name) { - const auto lower = name.toCaseFolded(); - return lower == QStringLiteral("tropconf.json") || lower == QStringLiteral("tropmeta.json") || - (lower.startsWith(QStringLiteral("tropmeta_")) && - lower.endsWith(QStringLiteral(".json"))) || - (lower.startsWith(QStringLiteral("trop")) && lower.endsWith(QStringLiteral(".png"))); -} - -static bool ReadUcp(const QString& file_name, QMap& files, QString& error) { - const QByteArray data = - GameContent::ReadPath(GameContent::ToPath(file_name), GameContent::MaxTrophyPackageSize); - if (data.isEmpty()) { - error = QObject::tr("Could not open %1").arg(QDir::toNativeSeparators(file_name)); - return false; - } - - if (data.size() < UCP_HEADER_LEN) { - error = QObject::tr("%1 is too small to be a trophy package.") - .arg(QFileInfo(file_name).fileName()); - return false; - } - - const auto magic = qFromBigEndian(data.constData() + 0x00); - if (magic != UCP_MAGIC) { - error = QObject::tr("%1 has an invalid trophy package magic.") - .arg(QFileInfo(file_name).fileName()); - return false; - } - - const auto version = qFromBigEndian(data.constData() + 0x04); - if (version != UCP_VERSION) { - error = QObject::tr("%1 uses unsupported trophy package version %2.") - .arg(QFileInfo(file_name).fileName(), QString::number(version)); - return false; - } - - const auto declared_size = qFromBigEndian(data.constData() + 0x08); - if (declared_size < UCP_HEADER_LEN || declared_size > static_cast(data.size())) { - error = QObject::tr("%1 is truncated.").arg(QFileInfo(file_name).fileName()); - return false; - } - - const auto file_count = qFromBigEndian(data.constData() + 0x10); - if (file_count > UCP_MAX_FILES) { - error = QObject::tr("%1 contains too many files.").arg(QFileInfo(file_name).fileName()); - return false; - } - const auto toc_offset = static_cast(qFromBigEndian(data.constData() + 0x14)); - const auto data_size = declared_size; - - const quint64 table_size = UCP_TOC_SKIP + static_cast(file_count) * UCP_ENTRY_LEN; - if (toc_offset > data_size || table_size > data_size - toc_offset || - !std::in_range(toc_offset + table_size)) { - error = QObject::tr("%1 has an invalid table of contents.") - .arg(QFileInfo(file_name).fileName()); - return false; - } - - quint64 extracted_size = 0; - for (quint32 i = 0; i < file_count; i++) { - const auto entry_offset = static_cast(toc_offset + UCP_TOC_SKIP + - static_cast(i) * UCP_ENTRY_LEN); - UcpEntry entry; - entry.name = ReadFixedString(data, entry_offset, UCP_NAME_LEN).trimmed(); - entry.offset = qFromBigEndian(data.constData() + entry_offset + 0x20); - entry.size = qFromBigEndian(data.constData() + entry_offset + 0x28); - - if (entry.name.isEmpty()) { - continue; - } - if (entry.offset > data_size || entry.size > data_size - entry.offset || - !std::in_range(entry.offset) || !std::in_range(entry.size)) { - error = QObject::tr("%1 has an invalid entry for %2.") - .arg(QFileInfo(file_name).fileName(), entry.name); - return false; - } - if (!IsUsedUcpEntry(entry.name)) { - continue; - } - - const auto entry_limit = entry.name.endsWith(QStringLiteral(".png"), Qt::CaseInsensitive) - ? GameContent::MaxImageSize - : GameContent::MaxMetadataSize; - if (entry.size > entry_limit || entry.size > UCP_MAX_EXTRACTED_SIZE - extracted_size) { - error = QObject::tr("%1 contains an oversized entry for %2.") - .arg(QFileInfo(file_name).fileName(), entry.name); - return false; - } - - files.insert(entry.name.toCaseFolded(), data.mid(static_cast(entry.offset), - static_cast(entry.size))); - extracted_size += entry.size; - } - - return true; -} - -static const QByteArray* FindFile(const QMap& files, const QString& name) { - const auto it = files.constFind(name.toCaseFolded()); - return it == files.constEnd() ? nullptr : &it.value(); -} - -static const QByteArray* FindFirstTrophyMetadataFile(const QMap& files) { - for (auto it = files.constBegin(); it != files.constEnd(); ++it) { - if (it.key().startsWith(QStringLiteral("tropmeta_")) && - it.key().endsWith(QStringLiteral(".json"))) { - return &it.value(); - } - } - - return FindFile(files, QStringLiteral("tropmeta.json")); -} - -static bool ReadJsonObject(const QByteArray& data, const QString& file_name, QJsonObject& object, - QString& error) { - QJsonParseError parse_error; - const auto doc = QJsonDocument::fromJson(data, &parse_error); - if (parse_error.error != QJsonParseError::NoError || !doc.isObject()) { - error = QObject::tr("Could not read %1: %2").arg(file_name, parse_error.errorString()); - return false; - } - - object = doc.object(); - return true; -} - -static QString JsonString(const QJsonValue& value) { - if (value.isString()) { - return value.toString(); - } - if (value.isDouble()) { - const double number = value.toDouble(); - if (number == static_cast(number)) { - return QString::number(static_cast(number)); - } - return QString::number(number); - } - if (value.isBool()) { - return value.toBool() ? QStringLiteral("true") : QStringLiteral("false"); - } - - return {}; -} - -static QList ReadDefinitions(const QJsonObject& tropconf) { - QList ret; - - const auto trophies = tropconf.value(QStringLiteral("trophies")).toArray(); - for (const auto& value: trophies) { - const auto obj = value.toObject(); - TrophyDefinition def; - def.id = JsonString(obj.value(QStringLiteral("id"))).trimmed(); - def.grade = JsonString(obj.value(QStringLiteral("grade"))).trimmed(); - def.hidden = obj.value(QStringLiteral("hidden")).toBool(false); - def.has_reward = obj.value(QStringLiteral("hasReward")).toBool(false); - - if (!def.id.isEmpty()) { - ret.append(def); - } - } - - return ret; -} - -static QMap ReadMetadata(const QJsonObject& tropmeta) { - QMap ret; - - const auto metadata = tropmeta.value(QStringLiteral("metadata")).toObject(); - const auto trophies = metadata.value(QStringLiteral("trophyMetadata")).toArray(); - for (const auto& value: trophies) { - const auto obj = value.toObject(); - const auto id = JsonString(obj.value(QStringLiteral("id"))).trimmed(); - if (id.isEmpty()) { - continue; - } - - TrophyText text; - text.name = JsonString(obj.value(QStringLiteral("name"))).trimmed(); - text.detail = JsonString(obj.value(QStringLiteral("detail"))).trimmed(); - text.reward = JsonString(obj.value(QStringLiteral("reward"))).trimmed(); - ret.insert(id, text); - } - - return ret; -} - -static QString GradeToText(const QString& grade) { - if (grade == QStringLiteral("P")) { - return QObject::tr("Platinum"); - } - if (grade == QStringLiteral("G")) { - return QObject::tr("Gold"); - } - if (grade == QStringLiteral("S")) { - return QObject::tr("Silver"); - } - if (grade == QStringLiteral("B")) { - return QObject::tr("Bronze"); - } - - return grade; -} - -static QPixmap LoadTrophyIcon(const QMap& files, const QString& id) { - QStringList names; - names.append(QStringLiteral("trop%1.png").arg(id)); - - bool id_is_number = false; - const int id_num = id.toInt(&id_is_number); - if (id_is_number) { - names.append(QStringLiteral("trop%1.png").arg(id_num, 4, 10, QLatin1Char('0'))); - } - - for (const auto& name: names) { - const auto* data = FindFile(files, name); - if (data == nullptr) { - continue; - } - - QPixmap pixmap; - if (pixmap.loadFromData(*data)) { - return pixmap; - } - } - - return {}; -} - -static QString TrophyTabTitle(const QString& file_name) { - static const QRegularExpression trophy_file_re(QStringLiteral("^trophy(\\d+)\\.ucp$"), - QRegularExpression::CaseInsensitiveOption); - - const auto name = QFileInfo(file_name).fileName(); - const auto match = trophy_file_re.match(name); - if (match.hasMatch()) { - return QObject::tr("Trophy %1").arg(match.captured(1)); - } - - return QFileInfo(file_name).completeBaseName(); -} - -static bool BuildTrophySet(const QString& ucp_file, TrophySet& set, QString& error) { - QMap files; - if (!ReadUcp(ucp_file, files, error)) { - return false; - } - - const auto* conf_data = FindFile(files, QStringLiteral("tropconf.json")); - if (conf_data == nullptr) { - error = - QObject::tr("%1 does not contain tropconf.json.").arg(QFileInfo(ucp_file).fileName()); - return false; - } - - QJsonObject tropconf; - if (!ReadJsonObject(*conf_data, QStringLiteral("tropconf.json"), tropconf, error)) { - return false; - } - - const auto default_language = - JsonString(tropconf.value(QStringLiteral("defaultLanguage"))).trimmed(); - const QByteArray* meta_data = nullptr; - if (!default_language.isEmpty()) { - meta_data = FindFile(files, QStringLiteral("tropmeta_%1.json").arg(default_language)); - } - if (meta_data == nullptr) { - meta_data = FindFirstTrophyMetadataFile(files); - } - if (meta_data == nullptr) { - error = QObject::tr("%1 does not contain readable trophy metadata.") - .arg(QFileInfo(ucp_file).fileName()); - return false; - } - - QJsonObject tropmeta; - if (!ReadJsonObject(*meta_data, QStringLiteral("tropmeta.json"), tropmeta, error)) { - return false; - } - - const auto definitions = ReadDefinitions(tropconf); - if (definitions.isEmpty()) { - error = QObject::tr("%1 does not define any trophies.").arg(QFileInfo(ucp_file).fileName()); - return false; - } - - const auto texts = ReadMetadata(tropmeta); - - set.tab_title = TrophyTabTitle(ucp_file); - for (const auto& def: definitions) { - const auto text = texts.value(def.id); - - TrophyRow row; - row.id = def.id; - row.grade = def.grade; - row.hidden = def.hidden; - row.has_reward = def.has_reward; - row.reward = text.reward; - row.name = text.name; - row.detail = text.detail; - row.icon = LoadTrophyIcon(files, def.id); - - if (row.name.isEmpty()) { - row.name = - row.hidden ? QObject::tr("Hidden Trophy") : QObject::tr("Trophy %1").arg(def.id); - } - if (row.detail.isEmpty() && row.hidden) { - row.detail = QObject::tr("This trophy is hidden."); - } - - set.trophies.append(row); - } - - return true; -} +static const QRegularExpression TrophyFilePattern(QStringLiteral("^trophy(\\d+)\\.ucp$")); static QStringList FindTrophyFiles(const Configuration* info) { if (info == nullptr || info->basedir.isEmpty()) { return {}; } - QStringList files; - for (const auto& file: - GameContent::ListFiles(info->basedir, QStringLiteral("sce_sys/trophy2"))) { - const auto name = QFileInfo(file).fileName(); - if (name.startsWith(QStringLiteral("trophy"), Qt::CaseInsensitive) && - name.endsWith(QStringLiteral(".ucp"), Qt::CaseInsensitive)) { + for (const auto& file: GameContent::ListFiles( + info->basedir, QString::fromLatin1(Common::Trophies::PackageDirectory))) { + if (TrophyFilePattern.match(QFileInfo(file).fileName()).hasMatch()) { files.append(file); } } - files.sort(Qt::CaseInsensitive); + files.sort(); + return files; +} - QStringList trophy_files; - QSet seen; - for (const auto& file: files) { - auto key = QFileInfo(file).canonicalFilePath(); - if (key.isEmpty()) { - key = file; - } - key = QDir::cleanPath(key).toCaseFolded(); - - if (!seen.contains(key)) { - seen.insert(key); - trophy_files.append(file); - } +static QString GradeToText(int grade) { + switch (grade) { + case 1: return QObject::tr("Platinum"); + case 2: return QObject::tr("Gold"); + case 3: return QObject::tr("Silver"); + case 4: return QObject::tr("Bronze"); + default: return {}; } - return trophy_files; } static void PrepareTable(QTableWidget* table) { - table->setColumnCount(4); table->setHorizontalHeaderLabels({QObject::tr("Unlocked"), QObject::tr("Trophy"), QObject::tr("Name"), QObject::tr("Description")}); table->setAlternatingRowColors(true); table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setSelectionBehavior(QAbstractItemView::SelectRows); table->setSelectionMode(QAbstractItemView::SingleSelection); + table->setIconSize(QSize(96, 96)); table->setShowGrid(false); table->setWordWrap(true); table->verticalHeader()->setVisible(false); @@ -469,30 +69,16 @@ static void PrepareTable(QTableWidget* table) { table->setColumnWidth(3, 480); } -static QTableWidgetItem* CreateItem(const QString& text) { - auto* item = new QTableWidgetItem(text); - item->setFlags(item->flags() & ~Qt::ItemIsEditable); - return item; -} - -static QString TrophyTooltip(const TrophyRow& row) { - QStringList lines; - lines.append(row.name); - if (!row.detail.isEmpty()) { - lines.append(row.detail); - } - - const auto grade = GradeToText(row.grade); - if (!grade.isEmpty()) { - lines.append(QObject::tr("Grade: %1").arg(grade)); - } - if (row.hidden) { +static QString TrophyTooltip(const Common::Trophies::Trophy& trophy) { + QStringList lines {QString::fromStdString(trophy.name), + QString::fromStdString(trophy.description), + QObject::tr("Grade: %1").arg(GradeToText(trophy.grade))}; + if (trophy.hidden) { lines.append(QObject::tr("Hidden trophy")); } - if (row.has_reward && !row.reward.isEmpty()) { - lines.append(QObject::tr("Reward: %1").arg(row.reward)); + if (trophy.has_reward && !trophy.reward.empty()) { + lines.append(QObject::tr("Reward: %1").arg(QString::fromStdString(trophy.reward))); } - return lines.join(QLatin1Char('\n')); } @@ -516,7 +102,8 @@ bool TrophyViewerDialog::HasTrophyData(const Configuration* info) { return !FindTrophyFiles(info).isEmpty(); } -void TrophyViewerDialog::ShowForGame(const Configuration* info, QWidget* parent) { +void TrophyViewerDialog::ShowForGame(const Configuration* info, const QString& runtime_directory, + QWidget* parent) { if (info == nullptr) { return; } @@ -527,7 +114,7 @@ void TrophyViewerDialog::ShowForGame(const Configuration* info, QWidget* parent) } QString error; - if (!dlg.LoadGame(*info, error)) { + if (!dlg.LoadGame(*info, runtime_directory, error)) { QMessageBox::warning(parent, tr("Trophy Viewer"), error); return; } @@ -535,66 +122,71 @@ void TrophyViewerDialog::ShowForGame(const Configuration* info, QWidget* parent) dlg.exec(); } -bool TrophyViewerDialog::LoadGame(const Configuration& info, QString& error) { - const auto reader = Common::OpenArchive(GameContent::ToPath(info.basedir)); +bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runtime_directory, + QString& error) { + const auto reader = Common::OpenArchive(GameContent::ToPath(info.basedir)); const auto trophy_files = FindTrophyFiles(&info); if (trophy_files.isEmpty()) { - error = tr("No trophy package found in sce_sys/trophy2."); + error = tr("No trophy package found in %1.") + .arg(QString::fromLatin1(Common::Trophies::PackageDirectory)); return false; } - QStringList errors; for (const auto& file: trophy_files) { - TrophySet set; - QString set_error; - if (!BuildTrophySet(file, set, set_error)) { - errors.append(set_error); + const auto package = + Common::Trophies::LoadPackage(GameContent::ToPath(file), info.console_language); + if (package.trophies.empty()) { + errors.append(tr("Could not read trophy package %1.").arg(QFileInfo(file).fileName())); continue; } - - auto* table = new QTableWidget(set.trophies.size(), 4, m_tabs); + const auto label = TrophyFilePattern.match(QFileInfo(file).fileName()).captured(1).toUInt(); + const auto unlocked = Common::Trophies::LoadUnlocks( + Common::Trophies::UnlocksPath(GameContent::ToPath(runtime_directory), + info.title_id.toStdString(), info.user_id, label)); + auto* table = new QTableWidget(static_cast(package.trophies.size()), 4, m_tabs); PrepareTable(table); - - for (int row_index = 0; row_index < set.trophies.size(); row_index++) { - const auto& row = set.trophies.at(row_index); - table->setRowHeight(row_index, 112); - - auto* status = CreateItem(tr("locked")); + int row_index = 0; + for (const auto& [id, trophy]: package.trophies) { + const bool trophy_unlocked = unlocked.contains(id); + auto* status = new QTableWidgetItem(trophy_unlocked ? tr("UNLOCKED") : tr("LOCKED")); status->setTextAlignment(Qt::AlignCenter); - if (row.hidden) { - status->setForeground(QBrush(Qt::gray)); - } + auto status_font = status->font(); + status_font.setBold(true); + status->setFont(status_font); table->setItem(row_index, 0, status); - auto* icon_label = new QLabel(table); - icon_label->setAlignment(Qt::AlignCenter); - icon_label->setMinimumSize(QSize(108, 108)); - if (!row.icon.isNull()) { - icon_label->setPixmap( - row.icon.scaled(QSize(96, 96), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + auto* icon_item = new QTableWidgetItem; + icon_item->setTextAlignment(Qt::AlignCenter); + QPixmap icon; + if (icon.loadFromData(reinterpret_cast(trophy.icon_png.data()), + static_cast(trophy.icon_png.size()))) { + if (!trophy_unlocked) { + icon = QPixmap::fromImage( + icon.toImage().convertToFormat(QImage::Format_Grayscale8)); + } + icon_item->setIcon(QIcon(icon)); } - table->setCellWidget(row_index, 1, icon_label); + table->setItem(row_index, 1, icon_item); - auto* name = CreateItem(row.name); - auto font = name->font(); + const auto name_text = trophy.name.empty() ? (trophy.hidden ? tr("Hidden Trophy") + : tr("Trophy %1").arg(id)) + : QString::fromStdString(trophy.name); + auto* name = new QTableWidgetItem(name_text); + auto font = name->font(); font.setBold(true); name->setFont(font); - name->setToolTip(TrophyTooltip(row)); + name->setToolTip(TrophyTooltip(trophy)); + auto* detail = new QTableWidgetItem(QString::fromStdString(trophy.description)); + detail->setToolTip(name->toolTip()); table->setItem(row_index, 2, name); - - auto* detail = CreateItem(row.detail); - detail->setToolTip(TrophyTooltip(row)); table->setItem(row_index, 3, detail); + ++row_index; } - - m_tabs->addTab(table, set.tab_title); + m_tabs->addTab(table, tr("Trophy %1").arg(label)); } - if (m_tabs->count() == 0) { - error = errors.isEmpty() ? tr("No readable trophy data found.") - : errors.join(QLatin1Char('\n')); + error = errors.join(QLatin1Char('\n')); return false; } - return true; } diff --git a/src/libs/libNet.cpp b/src/libs/libNet.cpp index 84733440a..59d4c97ba 100644 --- a/src/libs/libNet.cpp +++ b/src/libs/libNet.cpp @@ -8,7 +8,6 @@ #include "loader/symbolDatabase.h" #include -#include #include #include #include @@ -19,6 +18,13 @@ namespace Libs { +namespace LibNpTrophy2 { +void InitNet_1_NpTrophy2(Loader::SymbolDatabase* s); +} +namespace LibNpUniversalDataSystem { +void InitNet_1_NpUniversalDataSystem(Loader::SymbolDatabase* s); +} + namespace Network::Net { struct NetEtherAddr; } // namespace Network::Net @@ -1930,1123 +1936,6 @@ LIB_DEFINE(InitNet_1_NpAuth) { } // namespace LibNpAuth -namespace LibNpTrophy2 { - -LIB_VERSION("NpTrophy2", 1, "NpTrophy2", 1, 1); - -constexpr int NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND = -2141898479; /* 0x80553911 */ - -struct NpTrophy2Progress { - int32_t type; - uint8_t reserved[4]; - uint64_t value; -}; - -struct NpTrophy2GameDetails { - uint32_t num_groups; - uint32_t num_trophies; - uint32_t num_platinum; - uint32_t num_gold; - uint32_t num_silver; - uint32_t num_bronze; - char title[128]; -}; - -struct NpTrophy2GameData { - uint32_t unlocked_trophies; - uint32_t unlocked_platinum; - uint32_t unlocked_gold; - uint32_t unlocked_silver; - uint32_t unlocked_bronze; - uint32_t progress_percentage; -}; - -struct NpTrophy2GroupDetails { - int32_t group_id; - uint32_t num_trophies; - uint32_t num_platinum; - uint32_t num_gold; - uint32_t num_silver; - uint32_t num_bronze; - char title[128]; -}; - -struct NpTrophy2GroupData { - int32_t group_id; - uint32_t unlocked_trophies; - uint32_t unlocked_platinum; - uint32_t unlocked_gold; - uint32_t unlocked_silver; - uint32_t unlocked_bronze; - uint32_t progress_percentage; - uint8_t reserved[4]; -}; - -struct NpTrophy2Details { - int32_t trophy_id; - int32_t trophy_grade; - int32_t group_id; - bool hidden; - bool has_reward; - uint8_t reserved2[2]; - NpTrophy2Progress target; - char name[128]; - char description[1024]; - char reward[128]; -}; - -struct NpTrophy2Data { - int32_t trophy_id; - bool unlocked; - uint8_t reserved[3]; - NpTrophy2Progress progress; - uint64_t timestamp_tick; -}; - -static_assert(sizeof(NpTrophy2GameDetails) == 152); -static_assert(sizeof(NpTrophy2GameData) == 24); -static_assert(sizeof(NpTrophy2GroupDetails) == 152); -static_assert(sizeof(NpTrophy2GroupData) == 32); -static_assert(sizeof(NpTrophy2Details) == 1312); -static_assert(sizeof(NpTrophy2Data) == 32); - -static void NpTrophy2FillTitle(char* dst, size_t dst_size, const char* src) { - if (dst == nullptr || dst_size == 0) { - return; - } - - std::strncpy(dst, src, dst_size - 1); - dst[dst_size - 1] = '\0'; -} - -static int KYTY_SYSV_ABI NpTrophy2CreateHandle(int* handle) { - PRINT_NAME(); - - if (handle != nullptr) { - *handle = 1; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2CreateContext(int* context, int user_id, uint32_t service_label, - uint64_t options) { - PRINT_NAME(); - - if (context != nullptr) { - *context = 1; - } - - LOGF("\t user_id = %d\n" - "\t service_label = %u\n" - "\t options = 0x%016" PRIx64 "\n", - user_id, service_label, options); - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2RegisterContext(int context, int handle, uint64_t options) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t options = 0x%016" PRIx64 "\n", - context, handle, options); - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2GetGameInfo(int context, int handle, - NpTrophy2GameDetails* details, - NpTrophy2GameData* data) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t details = 0x%016" PRIx64 "\n" - "\t data = 0x%016" PRIx64 "\n", - context, handle, reinterpret_cast(details), reinterpret_cast(data)); - - if (details != nullptr) { - std::memset(details, 0, sizeof(*details)); - details->num_trophies = 1; - details->num_bronze = 1; - NpTrophy2FillTitle(details->title, sizeof(details->title), "Kyty"); - } - if (data != nullptr) { - std::memset(data, 0, sizeof(*data)); - } - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2GetGroupInfo(int context, int handle, int group_id, - NpTrophy2GroupDetails* details, - NpTrophy2GroupData* data) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t group_id = %d\n" - "\t details = 0x%016" PRIx64 "\n" - "\t data = 0x%016" PRIx64 "\n", - context, handle, group_id, reinterpret_cast(details), - reinterpret_cast(data)); - - const auto normalized_group_id = (group_id < 0 ? 0 : group_id); - - if (details != nullptr) { - std::memset(details, 0, sizeof(*details)); - details->group_id = normalized_group_id; - details->num_trophies = 1; - details->num_bronze = 1; - NpTrophy2FillTitle(details->title, sizeof(details->title), "Base Game"); - } - if (data != nullptr) { - std::memset(data, 0, sizeof(*data)); - data->group_id = normalized_group_id; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2GetGroupInfoArray(int context, int handle, uint32_t offset, - uint32_t limit, - NpTrophy2GroupDetails* details_array, - NpTrophy2GroupData* data_array, - uint32_t* count) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t offset = %" PRIu32 "\n" - "\t limit = %" PRIu32 "\n" - "\t details_array = 0x%016" PRIx64 "\n" - "\t data_array = 0x%016" PRIx64 "\n" - "\t count = 0x%016" PRIx64 "\n", - context, handle, offset, limit, reinterpret_cast(details_array), - reinterpret_cast(data_array), reinterpret_cast(count)); - - const uint32_t out_count = (offset == 0 && limit != 0 ? 1u : 0u); - - if (count != nullptr) { - *count = out_count; - } - if (out_count != 0 && details_array != nullptr) { - std::memset(details_array, 0, sizeof(*details_array)); - details_array->group_id = 0; - details_array->num_trophies = 1; - details_array->num_bronze = 1; - NpTrophy2FillTitle(details_array->title, sizeof(details_array->title), "Base Game"); - } - if (out_count != 0 && data_array != nullptr) { - std::memset(data_array, 0, sizeof(*data_array)); - data_array->group_id = 0; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2GetTrophyInfo(int context, int handle, int trophy_id, - NpTrophy2Details* details, NpTrophy2Data* data) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t trophy_id = %d\n" - "\t details = 0x%016" PRIx64 "\n" - "\t data = 0x%016" PRIx64 "\n", - context, handle, trophy_id, reinterpret_cast(details), - reinterpret_cast(data)); - - if (details != nullptr) { - std::memset(details, 0, sizeof(*details)); - details->trophy_id = trophy_id; - details->trophy_grade = 4; - details->group_id = 0; - details->target.type = 0; - details->target.value = 0; - NpTrophy2FillTitle(details->name, sizeof(details->name), "Trophy"); - NpTrophy2FillTitle(details->description, sizeof(details->description), "Trophy"); - } - if (data != nullptr) { - std::memset(data, 0, sizeof(*data)); - data->trophy_id = trophy_id; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2GetTrophyInfoArray(int context, int handle, uint32_t offset, - uint32_t limit, - NpTrophy2Details* details_array, - NpTrophy2Data* data_array, uint32_t* count) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t offset = %" PRIu32 "\n" - "\t limit = %" PRIu32 "\n" - "\t details_array = 0x%016" PRIx64 "\n" - "\t data_array = 0x%016" PRIx64 "\n" - "\t count = 0x%016" PRIx64 "\n", - context, handle, offset, limit, reinterpret_cast(details_array), - reinterpret_cast(data_array), reinterpret_cast(count)); - - const uint32_t out_count = (offset == 0 && limit != 0 ? 1u : 0u); - - if (count != nullptr) { - *count = out_count; - } - if (out_count != 0 && details_array != nullptr) { - std::memset(details_array, 0, sizeof(*details_array)); - details_array->trophy_id = 0; - details_array->trophy_grade = 4; - details_array->group_id = 0; - NpTrophy2FillTitle(details_array->name, sizeof(details_array->name), "Trophy"); - NpTrophy2FillTitle(details_array->description, sizeof(details_array->description), - "Trophy"); - } - if (out_count != 0 && data_array != nullptr) { - std::memset(data_array, 0, sizeof(*data_array)); - data_array->trophy_id = 0; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2GetGameIcon(int context, int handle, void* buffer, size_t* size) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t buffer = 0x%016" PRIx64 "\n" - "\t size = 0x%016" PRIx64 "\n", - context, handle, reinterpret_cast(buffer), reinterpret_cast(size)); - - if (size != nullptr) { - *size = 0; - } - - return NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND; -} - -static int KYTY_SYSV_ABI NpTrophy2GetGroupIcon(int context, int handle, int group_id, void* buffer, - size_t* size) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t group_id = %d\n" - "\t buffer = 0x%016" PRIx64 "\n" - "\t size = 0x%016" PRIx64 "\n", - context, handle, group_id, reinterpret_cast(buffer), - reinterpret_cast(size)); - - if (size != nullptr) { - *size = 0; - } - - return NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND; -} - -static int KYTY_SYSV_ABI NpTrophy2GetTrophyIcon(int context, int handle, int trophy_id, - void* buffer, size_t* size) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t trophy_id = %d\n" - "\t buffer = 0x%016" PRIx64 "\n" - "\t size = 0x%016" PRIx64 "\n", - context, handle, trophy_id, reinterpret_cast(buffer), - reinterpret_cast(size)); - - if (size != nullptr) { - *size = 0; - } - - return NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND; -} - -static int KYTY_SYSV_ABI NpTrophy2RegisterUnlockCallback(void* callback, void* userdata) { - PRINT_NAME(); - - LOGF("\t callback = 0x%016" PRIx64 "\n" - "\t userdata = 0x%016" PRIx64 "\n", - reinterpret_cast(callback), reinterpret_cast(userdata)); - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2AbortHandle(int handle) { - PRINT_NAME(); - - LOGF("\t handle = %d\n", handle); - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2DestroyHandle(int handle) { - PRINT_NAME(); - - LOGF("\t handle = %d\n", handle); - - return 0; -} - -static int KYTY_SYSV_ABI NpTrophy2DestroyContext(int context) { - PRINT_NAME(); - - LOGF("\t context = %d\n", context); - - return 0; -} - -LIB_DEFINE(InitNet_1_NpTrophy2) { - LIB_FUNC("Bagshr7OQ6Q", LibNpTrophy2::NpTrophy2CreateContext); - LIB_FUNC("Gz1rmUZpROM", LibNpTrophy2::NpTrophy2CreateHandle); - LIB_FUNC("bIDov3wBu5Q", LibNpTrophy2::NpTrophy2RegisterContext); - LIB_FUNC("4IzqhhUQ3nk", LibNpTrophy2::NpTrophy2GetGameInfo); - LIB_FUNC("DoZWauG8mu0", LibNpTrophy2::NpTrophy2GetGroupInfo); - LIB_FUNC("+PDSI6WgPRc", LibNpTrophy2::NpTrophy2GetGroupInfoArray); - LIB_FUNC("EwNylPdWUTM", LibNpTrophy2::NpTrophy2GetTrophyInfo); - LIB_FUNC("y3zHpdZO6ME", LibNpTrophy2::NpTrophy2GetTrophyInfoArray); - LIB_FUNC("2QgUy+xJqS0", LibNpTrophy2::NpTrophy2GetGameIcon); - LIB_FUNC("6IjXJUy6ZnA", LibNpTrophy2::NpTrophy2GetGroupIcon); - LIB_FUNC("-9LLVU0uvs8", LibNpTrophy2::NpTrophy2GetTrophyIcon); - LIB_FUNC("sUXGfNMalIo", LibNpTrophy2::NpTrophy2RegisterUnlockCallback); - LIB_FUNC("fYapWA9xVmA", LibNpTrophy2::NpTrophy2AbortHandle); - LIB_FUNC("d8P11CI40KE", LibNpTrophy2::NpTrophy2DestroyHandle); - LIB_FUNC("sysY2FHYff4", LibNpTrophy2::NpTrophy2DestroyContext); -} - -} // namespace LibNpTrophy2 - -namespace LibNpUniversalDataSystem { - -LIB_VERSION("NpUniversalDataSystem", 1, "NpUniversalDataSystem", 1, 1); - -constexpr int NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT = -2141900542; /* 0x80553102 */ - -struct NpUniversalDataSystemInitParam { - size_t size; - size_t pool_size; -}; - -struct NpUniversalDataSystemMemoryStat { - size_t pool_size; - size_t max_inuse_size; - size_t current_inuse_size; -}; - -struct NpUniversalDataSystemEvent {}; - -struct NpUniversalDataSystemEventPropertyObject {}; - -struct NpUniversalDataSystemEventPropertyArray {}; - -struct NpUniversalDataSystemStorageStat { - size_t in_events; - size_t out_events; - size_t lost_events; - size_t max_inuse_size; - size_t current_events; - size_t current_inuse_size; - size_t current_free_size; -}; - -static int KYTY_SYSV_ABI -NpUniversalDataSystemInitialize(const NpUniversalDataSystemInitParam* param) { - PRINT_NAME(); - - if (param == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - LOGF("\t size = %" PRIu64 "\n" - "\t pool_size = %" PRIu64 "\n", - static_cast(param->size), static_cast(param->pool_size)); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemCreateContext(int* context, int user_id, - uint32_t service_label, - uint64_t options) { - PRINT_NAME(); - - if (context != nullptr) { - *context = 1; - } - - LOGF("\t user_id = %d\n" - "\t service_label = %u\n" - "\t options = 0x%016" PRIx64 "\n", - user_id, service_label, options); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemCreateHandle(int* handle) { - PRINT_NAME(); - - if (handle == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - if (handle != nullptr) { - *handle = 1; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemDestroyHandle(int handle) { - PRINT_NAME(); - - LOGF("\t handle = %d\n", handle); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemAbortHandle(int handle) { - PRINT_NAME(); - - LOGF("\t handle = %d\n", handle); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemCreateEvent( - const char* event_name, const NpUniversalDataSystemEventPropertyObject* prop, - NpUniversalDataSystemEvent** new_event, NpUniversalDataSystemEventPropertyObject** prop_ptr) { - PRINT_NAME(); - - if (event_name == nullptr || new_event == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - *new_event = new NpUniversalDataSystemEvent; - if (prop_ptr != nullptr) { - *prop_ptr = (prop != nullptr ? const_cast(prop) - : new NpUniversalDataSystemEventPropertyObject); - } - - LOGF("\t event_name = %s\n" - "\t prop = 0x%016" PRIx64 "\n" - "\t new_event = 0x%016" PRIx64 "\n" - "\t prop_ptr = 0x%016" PRIx64 "\n", - event_name != nullptr ? event_name : "", reinterpret_cast(prop), - reinterpret_cast(*new_event), - prop_ptr != nullptr ? reinterpret_cast(*prop_ptr) : 0); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemPostEvent(int context, int handle, const void* event, - uint64_t options) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t event = 0x%016" PRIx64 "\n" - "\t options = 0x%016" PRIx64 "\n", - context, handle, reinterpret_cast(event), options); - - return 0; -} - -static int KYTY_SYSV_ABI -NpUniversalDataSystemEventEstimateSize(const NpUniversalDataSystemEvent* event, size_t* size) { - PRINT_NAME(); - - LOGF("\t event = 0x%016" PRIx64 "\n" - "\t size = 0x%016" PRIx64 "\n", - reinterpret_cast(event), reinterpret_cast(size)); - - if (event == nullptr || size == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - *size = 3; - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventToString(const NpUniversalDataSystemEvent* event, - char* buf, size_t buf_size, - size_t* string_size) { - PRINT_NAME(); - - LOGF("\t event = 0x%016" PRIx64 "\n" - "\t buf = 0x%016" PRIx64 "\n" - "\t buf_size = %" PRIu64 "\n" - "\t string_size = 0x%016" PRIx64 "\n", - reinterpret_cast(event), reinterpret_cast(buf), - static_cast(buf_size), reinterpret_cast(string_size)); - - if (event == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - const char* json = "{}"; - if (string_size != nullptr) { - *string_size = std::strlen(json) + 1; - } - if (buf != nullptr && buf_size > 0) { - std::snprintf(buf, buf_size, "%s", json); - } - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemDestroyEvent(NpUniversalDataSystemEvent* event) { - PRINT_NAME(); - - LOGF("\t event = 0x%016" PRIx64 "\n", reinterpret_cast(event)); - - delete event; - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemRegisterContext(int context, int handle, - uint64_t options) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t handle = %d\n" - "\t options = 0x%016" PRIx64 "\n", - context, handle, options); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemDestroyContext(int context) { - PRINT_NAME(); - - LOGF("\t context = %d\n", context); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemGetMemoryStat(NpUniversalDataSystemMemoryStat* stat) { - PRINT_NAME(); - - if (stat == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - *stat = {}; - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemCreateEventPropertyObject( - NpUniversalDataSystemEventPropertyObject** new_object) { - PRINT_NAME(); - - if (new_object == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - *new_object = new NpUniversalDataSystemEventPropertyObject; - - LOGF("\t new_object = 0x%016" PRIx64 "\n", reinterpret_cast(*new_object)); - - return 0; -} - -static int KYTY_SYSV_ABI -NpUniversalDataSystemDestroyEventPropertyObject(NpUniversalDataSystemEventPropertyObject* object) { - PRINT_NAME(); - - LOGF("\t object = 0x%016" PRIx64 "\n", reinterpret_cast(object)); - - delete object; - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetString( - NpUniversalDataSystemEventPropertyObject* object, const char* key, const char* value) { - PRINT_NAME(); - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = %s\n", - reinterpret_cast(object), key != nullptr ? key : "", - value != nullptr ? value : ""); - - if (object == nullptr || key == nullptr || value == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetInt32( - NpUniversalDataSystemEventPropertyObject* object, const char* key, int32_t value) { - PRINT_NAME(); - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = %" PRId32 "\n", - reinterpret_cast(object), key != nullptr ? key : "", value); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetUInt32( - NpUniversalDataSystemEventPropertyObject* object, const char* key, uint32_t value) { - PRINT_NAME(); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = %" PRIu32 "\n", - reinterpret_cast(object), key, value); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetInt64( - NpUniversalDataSystemEventPropertyObject* object, const char* key, int64_t value) { - PRINT_NAME(); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = %" PRId64 "\n", - reinterpret_cast(object), key, value); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetUInt64( - NpUniversalDataSystemEventPropertyObject* object, const char* key, uint64_t value) { - PRINT_NAME(); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = %" PRIu64 "\n", - reinterpret_cast(object), key, value); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetFloat32( - NpUniversalDataSystemEventPropertyObject* object, const char* key, float value) { - PRINT_NAME(); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = %f\n", - reinterpret_cast(object), key, static_cast(value)); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetFloat64( - NpUniversalDataSystemEventPropertyObject* object, const char* key, double value) { - PRINT_NAME(); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = %f\n", - reinterpret_cast(object), key, value); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetBool( - NpUniversalDataSystemEventPropertyObject* object, const char* key, bool value) { - PRINT_NAME(); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = %d\n", - reinterpret_cast(object), key, value ? 1 : 0); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetBinary( - NpUniversalDataSystemEventPropertyObject* object, const char* key, const void* value, - size_t value_size) { - PRINT_NAME(); - - if (object == nullptr || key == nullptr || (value == nullptr && value_size != 0)) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = 0x%016" PRIx64 "\n" - "\t value_size = %" PRIu64 "\n", - reinterpret_cast(object), key, reinterpret_cast(value), - static_cast(value_size)); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetObject( - NpUniversalDataSystemEventPropertyObject* object, const char* key, - const NpUniversalDataSystemEventPropertyObject* value, - NpUniversalDataSystemEventPropertyObject** value_ptr) { - PRINT_NAME(); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - if (value_ptr != nullptr) { - *value_ptr = - (value != nullptr ? const_cast(value) - : new NpUniversalDataSystemEventPropertyObject); - } - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = 0x%016" PRIx64 "\n" - "\t value_ptr = 0x%016" PRIx64 "\n", - reinterpret_cast(object), key, reinterpret_cast(value), - value_ptr != nullptr ? reinterpret_cast(*value_ptr) : 0); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetArray( - NpUniversalDataSystemEventPropertyObject* object, const char* key, - const NpUniversalDataSystemEventPropertyArray* value, - NpUniversalDataSystemEventPropertyArray** value_ptr) { - PRINT_NAME(); - - LOGF("\t object = 0x%016" PRIx64 "\n" - "\t key = %s\n" - "\t value = 0x%016" PRIx64 "\n" - "\t value_ptr = 0x%016" PRIx64 "\n", - reinterpret_cast(object), key != nullptr ? key : "", - reinterpret_cast(value), reinterpret_cast(value_ptr)); - - if (object == nullptr || key == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - if (value_ptr != nullptr) { - *value_ptr = (value != nullptr ? const_cast(value) - : new NpUniversalDataSystemEventPropertyArray); - } - - return 0; -} - -static int KYTY_SYSV_ABI -NpUniversalDataSystemCreateEventPropertyArray(NpUniversalDataSystemEventPropertyArray** new_array) { - PRINT_NAME(); - - if (new_array == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - *new_array = new NpUniversalDataSystemEventPropertyArray; - - LOGF("\t new_array = 0x%016" PRIx64 "\n", reinterpret_cast(*new_array)); - - return 0; -} - -static int KYTY_SYSV_ABI -NpUniversalDataSystemDestroyEventPropertyArray(NpUniversalDataSystemEventPropertyArray* array) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n", reinterpret_cast(array)); - - delete array; - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetString( - NpUniversalDataSystemEventPropertyArray* array, const char* value) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = %s\n", - reinterpret_cast(array), value != nullptr ? value : ""); - - if (array == nullptr || value == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetInt32( - NpUniversalDataSystemEventPropertyArray* array, int32_t value) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = %" PRId32 "\n", - reinterpret_cast(array), value); - - return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetUInt32( - NpUniversalDataSystemEventPropertyArray* array, uint32_t value) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = %" PRIu32 "\n", - reinterpret_cast(array), value); - - return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetInt64( - NpUniversalDataSystemEventPropertyArray* array, int64_t value) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = %" PRId64 "\n", - reinterpret_cast(array), value); - - return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetUInt64( - NpUniversalDataSystemEventPropertyArray* array, uint64_t value) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = %" PRIu64 "\n", - reinterpret_cast(array), value); - - return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetFloat32( - NpUniversalDataSystemEventPropertyArray* array, float value) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = %f\n", - reinterpret_cast(array), static_cast(value)); - - return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetFloat64( - NpUniversalDataSystemEventPropertyArray* array, double value) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = %f\n", - reinterpret_cast(array), value); - - return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetBool( - NpUniversalDataSystemEventPropertyArray* array, bool value) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = %d\n", - reinterpret_cast(array), value ? 1 : 0); - - return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetBinary( - NpUniversalDataSystemEventPropertyArray* array, const void* value, size_t value_size) { - PRINT_NAME(); - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = 0x%016" PRIx64 "\n" - "\t value_size = %" PRIu64 "\n", - reinterpret_cast(array), reinterpret_cast(value), - static_cast(value_size)); - - if (array == nullptr || (value == nullptr && value_size != 0)) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetObject( - NpUniversalDataSystemEventPropertyArray* array, - const NpUniversalDataSystemEventPropertyObject* value, - NpUniversalDataSystemEventPropertyObject** value_ptr) { - PRINT_NAME(); - - if (array == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - if (value_ptr != nullptr) { - *value_ptr = - (value != nullptr ? const_cast(value) - : new NpUniversalDataSystemEventPropertyObject); - } - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = 0x%016" PRIx64 "\n" - "\t value_ptr = 0x%016" PRIx64 "\n", - reinterpret_cast(array), reinterpret_cast(value), - value_ptr != nullptr ? reinterpret_cast(*value_ptr) : 0); - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetArray( - NpUniversalDataSystemEventPropertyArray* array, - const NpUniversalDataSystemEventPropertyArray* value, - NpUniversalDataSystemEventPropertyArray** value_ptr) { - PRINT_NAME(); - - if (array == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - if (value_ptr != nullptr) { - *value_ptr = (value != nullptr ? const_cast(value) - : new NpUniversalDataSystemEventPropertyArray); - } - - LOGF("\t array = 0x%016" PRIx64 "\n" - "\t value = 0x%016" PRIx64 "\n" - "\t value_ptr = 0x%016" PRIx64 "\n", - reinterpret_cast(array), reinterpret_cast(value), - value_ptr != nullptr ? reinterpret_cast(*value_ptr) : 0); - - return 0; -} - -static int KYTY_SYSV_ABI -NpUniversalDataSystemGetStorageStat(int context, NpUniversalDataSystemStorageStat* stat) { - PRINT_NAME(); - - LOGF("\t context = %d\n" - "\t stat = 0x%016" PRIx64 "\n", - context, reinterpret_cast(stat)); - - if (stat == nullptr) { - return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; - } - - *stat = {}; - - return 0; -} - -static int KYTY_SYSV_ABI NpUniversalDataSystemTerminate() { - PRINT_NAME(); - - return 0; -} - -LIB_DEFINE(InitNet_1_NpUniversalDataSystem) { - LIB_FUNC("sjaobBgqeB4", LibNpUniversalDataSystem::NpUniversalDataSystemInitialize); - LIB_FUNC("5zBnau1uIEo", LibNpUniversalDataSystem::NpUniversalDataSystemCreateContext); - LIB_FUNC("hT0IAEvN+M0", LibNpUniversalDataSystem::NpUniversalDataSystemCreateHandle); - LIB_FUNC("p+GcLqwpL9M", LibNpUniversalDataSystem::NpUniversalDataSystemCreateEvent); - LIB_FUNC("CzkKf7ahIyU", LibNpUniversalDataSystem::NpUniversalDataSystemPostEvent); - LIB_FUNC("AUIHb7jUX3I", LibNpUniversalDataSystem::NpUniversalDataSystemDestroyHandle); - LIB_FUNC("jZCqWFgMehE", LibNpUniversalDataSystem::NpUniversalDataSystemAbortHandle); - LIB_FUNC("wB7IWzGp2v0", LibNpUniversalDataSystem::NpUniversalDataSystemDestroyContext); - LIB_FUNC("su7jW3VDDb4", LibNpUniversalDataSystem::NpUniversalDataSystemGetMemoryStat); - LIB_FUNC("+s14jq-KGYw", LibNpUniversalDataSystem::NpUniversalDataSystemEventEstimateSize); - LIB_FUNC("vj6CQGWtEBg", LibNpUniversalDataSystem::NpUniversalDataSystemEventToString); - LIB_FUNC("wG+84pnNIuo", LibNpUniversalDataSystem::NpUniversalDataSystemDestroyEvent); - LIB_FUNC("tpFJ8LIKvPw", LibNpUniversalDataSystem::NpUniversalDataSystemRegisterContext); - LIB_FUNC("s6W4Zl4Slgk", - LibNpUniversalDataSystem::NpUniversalDataSystemCreateEventPropertyObject); - LIB_FUNC("kKUH0Viib3c", - LibNpUniversalDataSystem::NpUniversalDataSystemDestroyEventPropertyObject); - LIB_FUNC("MfDb+4Nln64", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetString); - LIB_FUNC("YE4dbtbz6OE", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetInt32); - LIB_FUNC("AzD4irAcKE4", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetUInt32); - LIB_FUNC("56QLTqx911s", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetInt64); - LIB_FUNC("xvsP5Yz6FmY", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetUInt64); - LIB_FUNC("lbPlT4+QVcE", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetFloat32); - LIB_FUNC("4Fu8tHW+u-k", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetFloat64); - LIB_FUNC("Fidd8vWgyVE", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetBool); - LIB_FUNC("wAcxBDLHj1M", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetBinary); - LIB_FUNC("74ASEqxSnkM", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetObject); - LIB_FUNC("Wxbg5x3pTXA", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetArray); - LIB_FUNC("Hm7qubT3b70", - LibNpUniversalDataSystem::NpUniversalDataSystemCreateEventPropertyArray); - LIB_FUNC("W-0xwY0ZMjw", - LibNpUniversalDataSystem::NpUniversalDataSystemDestroyEventPropertyArray); - LIB_FUNC("4llLk7YJRTE", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetString); - LIB_FUNC("BypQuF113-k", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetInt32); - LIB_FUNC("yMi0xAOpmXM", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetUInt32); - LIB_FUNC("viVXAwmmYrY", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetInt64); - LIB_FUNC("Qo9qR7v5zO4", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetUInt64); - LIB_FUNC("JmgwKm96Lq4", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetFloat32); - LIB_FUNC("sbSYZLR5AiE", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetFloat64); - LIB_FUNC("0+l4QSWCM4E", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetBool); - LIB_FUNC("IEdUCV9j2Cw", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetBinary); - LIB_FUNC("XY14n3jNIpE", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetObject); - LIB_FUNC("rdi9BAfDLq8", - LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetArray); - LIB_FUNC("KmN62tT4U8A", LibNpUniversalDataSystem::NpUniversalDataSystemGetStorageStat); - LIB_FUNC("47UAEuQl+iI", LibNpUniversalDataSystem::NpUniversalDataSystemTerminate); -} - -} // namespace LibNpUniversalDataSystem - namespace LibNpGameIntent { LIB_VERSION("NpGameIntent", 1, "NpGameIntent", 1, 1); diff --git a/src/libs/libTrophy.cpp b/src/libs/libTrophy.cpp new file mode 100644 index 000000000..d06441c92 --- /dev/null +++ b/src/libs/libTrophy.cpp @@ -0,0 +1,1288 @@ +#include "common/emulatorConfig.h" +#include "common/trophies.h" +#include "graphics/presentation/systemOverlay.h" +#include "kernel/fileSystem.h" +#include "libs/libs.h" +#include "loader/symbolDatabase.h" +#include "loader/systemContent.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Libs { + +namespace LibNpTrophy2 { + +LIB_VERSION("NpTrophy2", 1, "NpTrophy2", 1, 1); + +constexpr int NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND = -2141898479; /* 0x80553911 */ + +struct NpTrophy2Progress { + int32_t type; + uint8_t reserved[4]; + uint64_t value; +}; + +struct NpTrophy2GameDetails { + uint32_t num_groups; + uint32_t num_trophies; + uint32_t num_platinum; + uint32_t num_gold; + uint32_t num_silver; + uint32_t num_bronze; + char title[128]; +}; + +struct NpTrophy2GameData { + uint32_t unlocked_trophies; + uint32_t unlocked_platinum; + uint32_t unlocked_gold; + uint32_t unlocked_silver; + uint32_t unlocked_bronze; + uint32_t progress_percentage; +}; + +struct NpTrophy2GroupDetails { + int32_t group_id; + uint32_t num_trophies; + uint32_t num_platinum; + uint32_t num_gold; + uint32_t num_silver; + uint32_t num_bronze; + char title[128]; +}; + +struct NpTrophy2GroupData { + int32_t group_id; + uint32_t unlocked_trophies; + uint32_t unlocked_platinum; + uint32_t unlocked_gold; + uint32_t unlocked_silver; + uint32_t unlocked_bronze; + uint32_t progress_percentage; + uint8_t reserved[4]; +}; + +struct NpTrophy2Details { + int32_t trophy_id; + int32_t trophy_grade; + int32_t group_id; + bool hidden; + bool has_reward; + uint8_t reserved2[2]; + NpTrophy2Progress target; + char name[128]; + char description[1024]; + char reward[128]; +}; + +struct NpTrophy2Data { + int32_t trophy_id; + bool unlocked; + uint8_t reserved[3]; + NpTrophy2Progress progress; + uint64_t timestamp_tick; +}; + +static_assert(sizeof(NpTrophy2GameDetails) == 152); +static_assert(sizeof(NpTrophy2GameData) == 24); +static_assert(sizeof(NpTrophy2GroupDetails) == 152); +static_assert(sizeof(NpTrophy2GroupData) == 32); +static_assert(sizeof(NpTrophy2Details) == 1312); +static_assert(sizeof(NpTrophy2Data) == 32); + +namespace Trophies = Common::Trophies; +using TrophyKey = std::pair; + +struct TrophyState { + const Trophies::Package* package = nullptr; + std::set unlocked; + std::filesystem::path path; +}; + +struct TrophyContext { + TrophyKey key; + TrophyState* state = nullptr; +}; + +static std::mutex g_trophy_mutex; +static std::map g_packages; +static std::map g_states; +static std::map g_contexts; +static int g_next_context = 1; + +constexpr int NP_TROPHY2_ERROR_INVALID_ARGUMENT = static_cast(0x80553904); +constexpr int NP_TROPHY2_ERROR_INVALID_CONTEXT = static_cast(0x80553909); +constexpr int NP_TROPHY2_ERROR_INVALID_TROPHY_ID = static_cast(0x8055390a); +constexpr int NP_TROPHY2_ERROR_INVALID_GROUP_ID = static_cast(0x8055390b); +constexpr int NP_TROPHY2_ERROR_NOT_REGISTERED = static_cast(0x80553920); + +// Caller holds g_trophy_mutex. Both NpTrophy2 and UDS use the same user/service record. +static TrophyState* LoadTrophyState(TrophyKey key) { + if (const auto it = g_states.find(key); it != g_states.end()) { + return &it->second; + } + auto& package = g_packages[key.second]; + if (package.trophies.empty()) { + const auto filename = "/app0/" + Trophies::PackagePath(key.second).generic_string(); + package = Trophies::LoadPackage(LibKernel::FileSystem::GetRealFilename(filename), + Config::GetConsoleLanguage()); + if (package.trophies.empty()) { + return nullptr; + } + } + std::string title_id; + Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id); + auto path = Trophies::UnlocksPath({}, title_id, key.first, key.second); + auto unlocked = Trophies::LoadUnlocks(path); + return &g_states.emplace(key, TrophyState {&package, std::move(unlocked), std::move(path)}) + .first->second; +} + +static int GetTrophyState(int context, TrophyState** state) { + const auto it = g_contexts.find(context); + if (it == g_contexts.end()) { + return NP_TROPHY2_ERROR_INVALID_CONTEXT; + } + *state = it->second.state; + return *state == nullptr ? NP_TROPHY2_ERROR_NOT_REGISTERED : 0; +} + +static void FillTrophyInfo(const TrophyState& state, const Trophies::Trophy& trophy, + NpTrophy2Details* details, NpTrophy2Data* data) { + if (details != nullptr) { + *details = {}; + details->trophy_id = trophy.id; + details->trophy_grade = trophy.grade; + details->group_id = trophy.group_id; + details->hidden = trophy.hidden; + details->has_reward = trophy.has_reward; + if (trophy.target) { + details->target.type = 1; + details->target.value = *trophy.target; + } + std::snprintf(details->name, sizeof(details->name), "%s", trophy.name.c_str()); + std::snprintf(details->description, sizeof(details->description), "%s", + trophy.description.c_str()); + std::snprintf(details->reward, sizeof(details->reward), "%s", trophy.reward.c_str()); + } + if (data != nullptr) { + *data = {}; + data->trophy_id = trophy.id; + data->unlocked = state.unlocked.contains(trophy.id); + if (trophy.target) { + data->progress.type = 1; + data->progress.value = data->unlocked ? *trophy.target : 0; + } + } +} + +// Game and group structures share the same count fields. +template +static void FillTrophyCounts(const TrophyState& state, int group, Details* details, Data* data) { + std::array total {}, unlocked {}; + for (const auto& [id, trophy]: state.package->trophies) { + if (group != -2 && trophy.group_id != group) { + continue; + } + ++total[trophy.grade]; + if (state.unlocked.contains(id)) { + ++unlocked[trophy.grade]; + } + } + if (details != nullptr) { + *details = {}; + details->num_trophies = total[1] + total[2] + total[3] + total[4]; + details->num_platinum = total[1]; + details->num_gold = total[2]; + details->num_silver = total[3]; + details->num_bronze = total[4]; + } + if (data != nullptr) { + *data = {}; + data->unlocked_trophies = unlocked[1] + unlocked[2] + unlocked[3] + unlocked[4]; + data->unlocked_platinum = unlocked[1]; + data->unlocked_gold = unlocked[2]; + data->unlocked_silver = unlocked[3]; + data->unlocked_bronze = unlocked[4]; + const auto points = total[2] * 6 + total[3] * 2 + total[4]; + const auto earned = unlocked[2] * 6 + unlocked[3] * 2 + unlocked[4]; + data->progress_percentage = points == 0 ? 0 : earned * 100 / points; + } +} + +static void FillGroupInfo(const TrophyState& state, int group_id, const std::string& title, + NpTrophy2GroupDetails* details, NpTrophy2GroupData* data) { + FillTrophyCounts(state, group_id, details, data); + if (details != nullptr) { + details->group_id = group_id; + std::snprintf(details->title, sizeof(details->title), "%s", title.c_str()); + } + if (data != nullptr) { + data->group_id = group_id; + } +} + +static void RecordTrophyUnlock(TrophyState& state, int trophy_id) { + const auto found = state.package->trophies.find(trophy_id); + if (found == state.package->trophies.end() || found->second.grade == 1 || + found->second.target || !state.unlocked.insert(trophy_id).second) { + return; + } + std::vector earned {trophy_id}; + const auto platinum_id = found->second.platinum_id; + if (platinum_id >= 0 && state.package->trophies.contains(platinum_id) && + std::all_of(state.package->trophies.begin(), state.package->trophies.end(), + [&](const auto& entry) { + return entry.second.platinum_id != platinum_id || + state.unlocked.contains(entry.first); + }) && + state.unlocked.insert(platinum_id).second) { + earned.push_back(platinum_id); + } + if (!Trophies::SaveUnlocks(state.path, state.unlocked)) { + LOGF("[Trophy] could not save unlocks to %s\n", state.path.string().c_str()); + } + if (Config::TrophyEnabled()) { + for (const auto id: earned) { + const auto& trophy = state.package->trophies.at(id); + Graphics::NotifyTrophyUnlocked(trophy.name, trophy.grade, trophy.icon_png); + } + } +} + +static int KYTY_SYSV_ABI NpTrophy2CreateHandle(int* handle) { + PRINT_NAME(); + if (handle == nullptr) { + return NP_TROPHY2_ERROR_INVALID_ARGUMENT; + } + *handle = 1; + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2CreateContext(int* context, int user_id, uint32_t service_label, + uint64_t options) { + PRINT_NAME(); + if (context == nullptr || options != 0 || user_id < 0) { + return NP_TROPHY2_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lock(g_trophy_mutex); + const TrophyKey key {user_id, service_label}; + for (const auto& [id, value]: g_contexts) { + if (value.key == key) { + return static_cast(0x80553910); // CONTEXT_ALREADY_EXISTS + } + } + if (g_contexts.size() == 8) { + return static_cast(0x8055391b); // CONTEXT_EXCEEDS_MAX + } + *context = g_next_context++; + g_contexts.emplace(*context, TrophyContext {key}); + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2RegisterContext(int context, int handle, uint64_t options) { + PRINT_NAME(); + std::scoped_lock lock(g_trophy_mutex); + const auto it = g_contexts.find(context); + if (it == g_contexts.end()) { + return NP_TROPHY2_ERROR_INVALID_CONTEXT; + } + if (options != 0) { + return NP_TROPHY2_ERROR_INVALID_ARGUMENT; + } + if (it->second.state != nullptr) { + return static_cast(0x80553921); // ALREADY_REGISTERED + } + it->second.state = LoadTrophyState(it->second.key); + return it->second.state != nullptr ? 0 + : static_cast(0x8055391e); // TITLE_CONF_NOT_INSTALLED +} + +static int KYTY_SYSV_ABI NpTrophy2GetGameInfo(int context, int handle, + NpTrophy2GameDetails* details, + NpTrophy2GameData* data) { + PRINT_NAME(); + if (details == nullptr && data == nullptr) { + return NP_TROPHY2_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lock(g_trophy_mutex); + TrophyState* state = nullptr; + if (const int error = GetTrophyState(context, &state); error != 0) { + return error; + } + FillTrophyCounts(*state, -2, details, data); + if (details != nullptr) { + details->num_groups = state->package->groups.size(); + std::snprintf(details->title, sizeof(details->title), "%s", state->package->title.c_str()); + } + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2GetGroupInfo(int context, int handle, int group_id, + NpTrophy2GroupDetails* details, + NpTrophy2GroupData* data) { + PRINT_NAME(); + if (details == nullptr && data == nullptr) { + return NP_TROPHY2_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lock(g_trophy_mutex); + TrophyState* state = nullptr; + if (const int error = GetTrophyState(context, &state); error != 0) { + return error; + } + const auto group = state->package->groups.find(group_id); + if (group == state->package->groups.end()) { + return NP_TROPHY2_ERROR_INVALID_GROUP_ID; + } + FillGroupInfo(*state, group_id, group->second, details, data); + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2GetGroupInfoArray(int context, int handle, uint32_t offset, + uint32_t limit, + NpTrophy2GroupDetails* details_array, + NpTrophy2GroupData* data_array, + uint32_t* count) { + PRINT_NAME(); + if (count == nullptr || (details_array == nullptr && data_array == nullptr)) { + return NP_TROPHY2_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lock(g_trophy_mutex); + TrophyState* state = nullptr; + if (const int error = GetTrophyState(context, &state); error != 0) { + return error; + } + const auto& groups = state->package->groups; + auto it = groups.begin(); + std::advance(it, std::min(offset, groups.size())); + *count = 0; + for (; it != groups.end() && *count < limit; ++it, ++*count) { + FillGroupInfo(*state, it->first, it->second, + details_array != nullptr ? &details_array[*count] : nullptr, + data_array != nullptr ? &data_array[*count] : nullptr); + } + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2GetTrophyInfo(int context, int handle, int trophy_id, + NpTrophy2Details* details, NpTrophy2Data* data) { + PRINT_NAME(); + if (details == nullptr && data == nullptr) { + return NP_TROPHY2_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lock(g_trophy_mutex); + TrophyState* state = nullptr; + if (const int error = GetTrophyState(context, &state); error != 0) { + return error; + } + const auto trophy = state->package->trophies.find(trophy_id); + if (trophy == state->package->trophies.end()) { + return NP_TROPHY2_ERROR_INVALID_TROPHY_ID; + } + FillTrophyInfo(*state, trophy->second, details, data); + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2GetTrophyInfoArray(int context, int handle, uint32_t offset, + uint32_t limit, + NpTrophy2Details* details_array, + NpTrophy2Data* data_array, uint32_t* count) { + PRINT_NAME(); + if (count == nullptr || (details_array == nullptr && data_array == nullptr)) { + return NP_TROPHY2_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lock(g_trophy_mutex); + TrophyState* state = nullptr; + if (const int error = GetTrophyState(context, &state); error != 0) { + return error; + } + const auto& trophies = state->package->trophies; + auto it = trophies.begin(); + std::advance(it, std::min(offset, trophies.size())); + *count = 0; + for (; it != trophies.end() && *count < limit; ++it, ++*count) { + FillTrophyInfo(*state, it->second, + details_array != nullptr ? &details_array[*count] : nullptr, + data_array != nullptr ? &data_array[*count] : nullptr); + } + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2GetGameIcon(int context, int handle, void* buffer, size_t* size) { + PRINT_NAME(); + + LOGF("\t context = %d\n" + "\t handle = %d\n" + "\t buffer = 0x%016" PRIx64 "\n" + "\t size = 0x%016" PRIx64 "\n", + context, handle, reinterpret_cast(buffer), reinterpret_cast(size)); + + if (size != nullptr) { + *size = 0; + } + + return NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND; +} + +static int KYTY_SYSV_ABI NpTrophy2GetGroupIcon(int context, int handle, int group_id, void* buffer, + size_t* size) { + PRINT_NAME(); + + LOGF("\t context = %d\n" + "\t handle = %d\n" + "\t group_id = %d\n" + "\t buffer = 0x%016" PRIx64 "\n" + "\t size = 0x%016" PRIx64 "\n", + context, handle, group_id, reinterpret_cast(buffer), + reinterpret_cast(size)); + + if (size != nullptr) { + *size = 0; + } + + return NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND; +} + +static int KYTY_SYSV_ABI NpTrophy2GetTrophyIcon(int context, int handle, int trophy_id, + void* buffer, size_t* size) { + PRINT_NAME(); + + LOGF("\t context = %d\n" + "\t handle = %d\n" + "\t trophy_id = %d\n" + "\t buffer = 0x%016" PRIx64 "\n" + "\t size = 0x%016" PRIx64 "\n", + context, handle, trophy_id, reinterpret_cast(buffer), + reinterpret_cast(size)); + + if (size != nullptr) { + *size = 0; + } + + return NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND; +} + +static int KYTY_SYSV_ABI NpTrophy2RegisterUnlockCallback(void* callback, void* userdata) { + PRINT_NAME(); + + LOGF("\t callback = 0x%016" PRIx64 "\n" + "\t userdata = 0x%016" PRIx64 "\n", + reinterpret_cast(callback), reinterpret_cast(userdata)); + + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2AbortHandle(int handle) { + PRINT_NAME(); + + LOGF("\t handle = %d\n", handle); + + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2DestroyHandle(int handle) { + PRINT_NAME(); + + LOGF("\t handle = %d\n", handle); + + return 0; +} + +static int KYTY_SYSV_ABI NpTrophy2DestroyContext(int context) { + PRINT_NAME(); + std::scoped_lock lock(g_trophy_mutex); + return g_contexts.erase(context) != 0 ? 0 : NP_TROPHY2_ERROR_INVALID_CONTEXT; +} + +LIB_DEFINE(InitNet_1_NpTrophy2) { + LIB_FUNC("Bagshr7OQ6Q", LibNpTrophy2::NpTrophy2CreateContext); + LIB_FUNC("Gz1rmUZpROM", LibNpTrophy2::NpTrophy2CreateHandle); + LIB_FUNC("bIDov3wBu5Q", LibNpTrophy2::NpTrophy2RegisterContext); + LIB_FUNC("4IzqhhUQ3nk", LibNpTrophy2::NpTrophy2GetGameInfo); + LIB_FUNC("DoZWauG8mu0", LibNpTrophy2::NpTrophy2GetGroupInfo); + LIB_FUNC("+PDSI6WgPRc", LibNpTrophy2::NpTrophy2GetGroupInfoArray); + LIB_FUNC("EwNylPdWUTM", LibNpTrophy2::NpTrophy2GetTrophyInfo); + LIB_FUNC("y3zHpdZO6ME", LibNpTrophy2::NpTrophy2GetTrophyInfoArray); + LIB_FUNC("2QgUy+xJqS0", LibNpTrophy2::NpTrophy2GetGameIcon); + LIB_FUNC("6IjXJUy6ZnA", LibNpTrophy2::NpTrophy2GetGroupIcon); + LIB_FUNC("-9LLVU0uvs8", LibNpTrophy2::NpTrophy2GetTrophyIcon); + LIB_FUNC("sUXGfNMalIo", LibNpTrophy2::NpTrophy2RegisterUnlockCallback); + LIB_FUNC("fYapWA9xVmA", LibNpTrophy2::NpTrophy2AbortHandle); + LIB_FUNC("d8P11CI40KE", LibNpTrophy2::NpTrophy2DestroyHandle); + LIB_FUNC("sysY2FHYff4", LibNpTrophy2::NpTrophy2DestroyContext); +} + +} // namespace LibNpTrophy2 + +namespace LibNpUniversalDataSystem { + +LIB_VERSION("NpUniversalDataSystem", 1, "NpUniversalDataSystem", 1, 1); + +constexpr int NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT = -2141900542; /* 0x80553102 */ + +struct NpUniversalDataSystemInitParam { + size_t size; + size_t pool_size; +}; + +struct NpUniversalDataSystemMemoryStat { + size_t pool_size; + size_t max_inuse_size; + size_t current_inuse_size; +}; + +struct NpUniversalDataSystemEventPropertyObject { + std::optional trophy_id; + + void SetTrophyId(const char* key, std::optional value) { + if (std::strcmp(key, "_trophy_id") == 0) { + trophy_id = value; + } + } +}; + +struct NpUniversalDataSystemEvent { + std::string name; + NpUniversalDataSystemEventPropertyObject properties; +}; + +struct UdsContext { + LibNpTrophy2::TrophyKey key; + bool registered = false; +}; +static std::map g_contexts; +static int g_next_context = 1; + +struct NpUniversalDataSystemEventPropertyArray {}; + +struct NpUniversalDataSystemStorageStat { + size_t in_events; + size_t out_events; + size_t lost_events; + size_t max_inuse_size; + size_t current_events; + size_t current_inuse_size; + size_t current_free_size; +}; + +static int KYTY_SYSV_ABI +NpUniversalDataSystemInitialize(const NpUniversalDataSystemInitParam* param) { + PRINT_NAME(); + + if (param == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + LOGF("\t size = %" PRIu64 "\n" + "\t pool_size = %" PRIu64 "\n", + static_cast(param->size), static_cast(param->pool_size)); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemCreateContext(int* context, int user_id, + uint32_t service_label, + uint64_t options) { + PRINT_NAME(); + if (context == nullptr || options != 0) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + if (user_id < 0) { + return static_cast(0x8055310a); // INVALID_USER + } + std::scoped_lock lock(LibNpTrophy2::g_trophy_mutex); + if (g_contexts.size() == 8) { + return static_cast(0x80553105); // CONTEXT_EXCEEDS_MAX + } + *context = g_next_context++; + g_contexts.emplace(*context, UdsContext {{user_id, service_label}}); + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemCreateHandle(int* handle) { + PRINT_NAME(); + + if (handle == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + if (handle != nullptr) { + *handle = 1; + } + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemDestroyHandle(int handle) { + PRINT_NAME(); + + LOGF("\t handle = %d\n", handle); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemAbortHandle(int handle) { + PRINT_NAME(); + + LOGF("\t handle = %d\n", handle); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemCreateEvent( + const char* event_name, const NpUniversalDataSystemEventPropertyObject* prop, + NpUniversalDataSystemEvent** new_event, NpUniversalDataSystemEventPropertyObject** prop_ptr) { + PRINT_NAME(); + if (event_name == nullptr || new_event == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + *new_event = new NpUniversalDataSystemEvent { + event_name, prop != nullptr ? *prop : NpUniversalDataSystemEventPropertyObject {}}; + if (prop_ptr != nullptr) { + *prop_ptr = &(*new_event)->properties; + } + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemPostEvent(int context, int handle, const void* event, + uint64_t options) { + PRINT_NAME(); + if (event == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lock(LibNpTrophy2::g_trophy_mutex); + const auto it = g_contexts.find(context); + if (it == g_contexts.end()) { + return static_cast(0x80553104); // INVALID_CONTEXT + } + if (!it->second.registered) { + return static_cast(0x80553120); // NOT_REGISTERED + } + const auto& uds_event = *static_cast(event); + if (uds_event.name == "_UnlockTrophy") { + const auto trophy_id = uds_event.properties.trophy_id; + if (!trophy_id || *trophy_id < 0) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + if (auto* state = LibNpTrophy2::LoadTrophyState(it->second.key); state != nullptr) { + LibNpTrophy2::RecordTrophyUnlock(*state, *trophy_id); + } + } + return 0; +} + +static int KYTY_SYSV_ABI +NpUniversalDataSystemEventEstimateSize(const NpUniversalDataSystemEvent* event, size_t* size) { + PRINT_NAME(); + + LOGF("\t event = 0x%016" PRIx64 "\n" + "\t size = 0x%016" PRIx64 "\n", + reinterpret_cast(event), reinterpret_cast(size)); + + if (event == nullptr || size == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + *size = 3; + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventToString(const NpUniversalDataSystemEvent* event, + char* buf, size_t buf_size, + size_t* string_size) { + PRINT_NAME(); + + LOGF("\t event = 0x%016" PRIx64 "\n" + "\t buf = 0x%016" PRIx64 "\n" + "\t buf_size = %" PRIu64 "\n" + "\t string_size = 0x%016" PRIx64 "\n", + reinterpret_cast(event), reinterpret_cast(buf), + static_cast(buf_size), reinterpret_cast(string_size)); + + if (event == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + const char* json = "{}"; + if (string_size != nullptr) { + *string_size = std::strlen(json) + 1; + } + if (buf != nullptr && buf_size > 0) { + std::snprintf(buf, buf_size, "%s", json); + } + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemDestroyEvent(NpUniversalDataSystemEvent* event) { + PRINT_NAME(); + + LOGF("\t event = 0x%016" PRIx64 "\n", reinterpret_cast(event)); + + delete event; + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemRegisterContext(int context, int handle, + uint64_t options) { + PRINT_NAME(); + std::scoped_lock lock(LibNpTrophy2::g_trophy_mutex); + const auto it = g_contexts.find(context); + if (it == g_contexts.end()) { + return static_cast(0x80553104); // INVALID_CONTEXT + } + if (options != 0) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + if (it->second.registered) { + return static_cast(0x80553121); // ALREADY_REGISTERED + } + it->second.registered = true; + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemDestroyContext(int context) { + PRINT_NAME(); + std::scoped_lock lock(LibNpTrophy2::g_trophy_mutex); + return g_contexts.erase(context) != 0 ? 0 : static_cast(0x80553104); +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemGetMemoryStat(NpUniversalDataSystemMemoryStat* stat) { + PRINT_NAME(); + + if (stat == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + *stat = {}; + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemCreateEventPropertyObject( + NpUniversalDataSystemEventPropertyObject** new_object) { + PRINT_NAME(); + + if (new_object == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + *new_object = new NpUniversalDataSystemEventPropertyObject; + + LOGF("\t new_object = 0x%016" PRIx64 "\n", reinterpret_cast(*new_object)); + + return 0; +} + +static int KYTY_SYSV_ABI +NpUniversalDataSystemDestroyEventPropertyObject(NpUniversalDataSystemEventPropertyObject* object) { + PRINT_NAME(); + + LOGF("\t object = 0x%016" PRIx64 "\n", reinterpret_cast(object)); + + delete object; + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetString( + NpUniversalDataSystemEventPropertyObject* object, const char* key, const char* value) { + PRINT_NAME(); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = %s\n", + reinterpret_cast(object), key != nullptr ? key : "", + value != nullptr ? value : ""); + + if (object == nullptr || key == nullptr || value == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetInt32( + NpUniversalDataSystemEventPropertyObject* object, const char* key, int32_t value) { + PRINT_NAME(); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = %" PRId32 "\n", + reinterpret_cast(object), key != nullptr ? key : "", value); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, value); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetUInt32( + NpUniversalDataSystemEventPropertyObject* object, const char* key, uint32_t value) { + PRINT_NAME(); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = %" PRIu32 "\n", + reinterpret_cast(object), key, value); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetInt64( + NpUniversalDataSystemEventPropertyObject* object, const char* key, int64_t value) { + PRINT_NAME(); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = %" PRId64 "\n", + reinterpret_cast(object), key, value); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetUInt64( + NpUniversalDataSystemEventPropertyObject* object, const char* key, uint64_t value) { + PRINT_NAME(); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = %" PRIu64 "\n", + reinterpret_cast(object), key, value); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetFloat32( + NpUniversalDataSystemEventPropertyObject* object, const char* key, float value) { + PRINT_NAME(); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = %f\n", + reinterpret_cast(object), key, static_cast(value)); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetFloat64( + NpUniversalDataSystemEventPropertyObject* object, const char* key, double value) { + PRINT_NAME(); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = %f\n", + reinterpret_cast(object), key, value); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetBool( + NpUniversalDataSystemEventPropertyObject* object, const char* key, bool value) { + PRINT_NAME(); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = %d\n", + reinterpret_cast(object), key, value ? 1 : 0); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetBinary( + NpUniversalDataSystemEventPropertyObject* object, const char* key, const void* value, + size_t value_size) { + PRINT_NAME(); + + if (object == nullptr || key == nullptr || (value == nullptr && value_size != 0)) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = 0x%016" PRIx64 "\n" + "\t value_size = %" PRIu64 "\n", + reinterpret_cast(object), key, reinterpret_cast(value), + static_cast(value_size)); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetObject( + NpUniversalDataSystemEventPropertyObject* object, const char* key, + const NpUniversalDataSystemEventPropertyObject* value, + NpUniversalDataSystemEventPropertyObject** value_ptr) { + PRINT_NAME(); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + if (value_ptr != nullptr) { + *value_ptr = + (value != nullptr ? const_cast(value) + : new NpUniversalDataSystemEventPropertyObject); + } + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = 0x%016" PRIx64 "\n" + "\t value_ptr = 0x%016" PRIx64 "\n", + reinterpret_cast(object), key, reinterpret_cast(value), + value_ptr != nullptr ? reinterpret_cast(*value_ptr) : 0); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyObjectSetArray( + NpUniversalDataSystemEventPropertyObject* object, const char* key, + const NpUniversalDataSystemEventPropertyArray* value, + NpUniversalDataSystemEventPropertyArray** value_ptr) { + PRINT_NAME(); + + LOGF("\t object = 0x%016" PRIx64 "\n" + "\t key = %s\n" + "\t value = 0x%016" PRIx64 "\n" + "\t value_ptr = 0x%016" PRIx64 "\n", + reinterpret_cast(object), key != nullptr ? key : "", + reinterpret_cast(value), reinterpret_cast(value_ptr)); + + if (object == nullptr || key == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + object->SetTrophyId(key, std::nullopt); + + if (value_ptr != nullptr) { + *value_ptr = (value != nullptr ? const_cast(value) + : new NpUniversalDataSystemEventPropertyArray); + } + + return 0; +} + +static int KYTY_SYSV_ABI +NpUniversalDataSystemCreateEventPropertyArray(NpUniversalDataSystemEventPropertyArray** new_array) { + PRINT_NAME(); + + if (new_array == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + *new_array = new NpUniversalDataSystemEventPropertyArray; + + LOGF("\t new_array = 0x%016" PRIx64 "\n", reinterpret_cast(*new_array)); + + return 0; +} + +static int KYTY_SYSV_ABI +NpUniversalDataSystemDestroyEventPropertyArray(NpUniversalDataSystemEventPropertyArray* array) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n", reinterpret_cast(array)); + + delete array; + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetString( + NpUniversalDataSystemEventPropertyArray* array, const char* value) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = %s\n", + reinterpret_cast(array), value != nullptr ? value : ""); + + if (array == nullptr || value == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetInt32( + NpUniversalDataSystemEventPropertyArray* array, int32_t value) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = %" PRId32 "\n", + reinterpret_cast(array), value); + + return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetUInt32( + NpUniversalDataSystemEventPropertyArray* array, uint32_t value) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = %" PRIu32 "\n", + reinterpret_cast(array), value); + + return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetInt64( + NpUniversalDataSystemEventPropertyArray* array, int64_t value) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = %" PRId64 "\n", + reinterpret_cast(array), value); + + return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetUInt64( + NpUniversalDataSystemEventPropertyArray* array, uint64_t value) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = %" PRIu64 "\n", + reinterpret_cast(array), value); + + return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetFloat32( + NpUniversalDataSystemEventPropertyArray* array, float value) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = %f\n", + reinterpret_cast(array), static_cast(value)); + + return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetFloat64( + NpUniversalDataSystemEventPropertyArray* array, double value) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = %f\n", + reinterpret_cast(array), value); + + return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetBool( + NpUniversalDataSystemEventPropertyArray* array, bool value) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = %d\n", + reinterpret_cast(array), value ? 1 : 0); + + return (array != nullptr ? 0 : NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT); +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetBinary( + NpUniversalDataSystemEventPropertyArray* array, const void* value, size_t value_size) { + PRINT_NAME(); + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = 0x%016" PRIx64 "\n" + "\t value_size = %" PRIu64 "\n", + reinterpret_cast(array), reinterpret_cast(value), + static_cast(value_size)); + + if (array == nullptr || (value == nullptr && value_size != 0)) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetObject( + NpUniversalDataSystemEventPropertyArray* array, + const NpUniversalDataSystemEventPropertyObject* value, + NpUniversalDataSystemEventPropertyObject** value_ptr) { + PRINT_NAME(); + + if (array == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + if (value_ptr != nullptr) { + *value_ptr = + (value != nullptr ? const_cast(value) + : new NpUniversalDataSystemEventPropertyObject); + } + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = 0x%016" PRIx64 "\n" + "\t value_ptr = 0x%016" PRIx64 "\n", + reinterpret_cast(array), reinterpret_cast(value), + value_ptr != nullptr ? reinterpret_cast(*value_ptr) : 0); + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemEventPropertyArraySetArray( + NpUniversalDataSystemEventPropertyArray* array, + const NpUniversalDataSystemEventPropertyArray* value, + NpUniversalDataSystemEventPropertyArray** value_ptr) { + PRINT_NAME(); + + if (array == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + if (value_ptr != nullptr) { + *value_ptr = (value != nullptr ? const_cast(value) + : new NpUniversalDataSystemEventPropertyArray); + } + + LOGF("\t array = 0x%016" PRIx64 "\n" + "\t value = 0x%016" PRIx64 "\n" + "\t value_ptr = 0x%016" PRIx64 "\n", + reinterpret_cast(array), reinterpret_cast(value), + value_ptr != nullptr ? reinterpret_cast(*value_ptr) : 0); + + return 0; +} + +static int KYTY_SYSV_ABI +NpUniversalDataSystemGetStorageStat(int context, NpUniversalDataSystemStorageStat* stat) { + PRINT_NAME(); + + LOGF("\t context = %d\n" + "\t stat = 0x%016" PRIx64 "\n", + context, reinterpret_cast(stat)); + + if (stat == nullptr) { + return NP_UNIVERSAL_DATA_SYSTEM_ERROR_INVALID_ARGUMENT; + } + + *stat = {}; + + return 0; +} + +static int KYTY_SYSV_ABI NpUniversalDataSystemTerminate() { + PRINT_NAME(); + + return 0; +} + +LIB_DEFINE(InitNet_1_NpUniversalDataSystem) { + LIB_FUNC("sjaobBgqeB4", LibNpUniversalDataSystem::NpUniversalDataSystemInitialize); + LIB_FUNC("5zBnau1uIEo", LibNpUniversalDataSystem::NpUniversalDataSystemCreateContext); + LIB_FUNC("hT0IAEvN+M0", LibNpUniversalDataSystem::NpUniversalDataSystemCreateHandle); + LIB_FUNC("p+GcLqwpL9M", LibNpUniversalDataSystem::NpUniversalDataSystemCreateEvent); + LIB_FUNC("CzkKf7ahIyU", LibNpUniversalDataSystem::NpUniversalDataSystemPostEvent); + LIB_FUNC("AUIHb7jUX3I", LibNpUniversalDataSystem::NpUniversalDataSystemDestroyHandle); + LIB_FUNC("jZCqWFgMehE", LibNpUniversalDataSystem::NpUniversalDataSystemAbortHandle); + LIB_FUNC("wB7IWzGp2v0", LibNpUniversalDataSystem::NpUniversalDataSystemDestroyContext); + LIB_FUNC("su7jW3VDDb4", LibNpUniversalDataSystem::NpUniversalDataSystemGetMemoryStat); + LIB_FUNC("+s14jq-KGYw", LibNpUniversalDataSystem::NpUniversalDataSystemEventEstimateSize); + LIB_FUNC("vj6CQGWtEBg", LibNpUniversalDataSystem::NpUniversalDataSystemEventToString); + LIB_FUNC("wG+84pnNIuo", LibNpUniversalDataSystem::NpUniversalDataSystemDestroyEvent); + LIB_FUNC("tpFJ8LIKvPw", LibNpUniversalDataSystem::NpUniversalDataSystemRegisterContext); + LIB_FUNC("s6W4Zl4Slgk", + LibNpUniversalDataSystem::NpUniversalDataSystemCreateEventPropertyObject); + LIB_FUNC("kKUH0Viib3c", + LibNpUniversalDataSystem::NpUniversalDataSystemDestroyEventPropertyObject); + LIB_FUNC("MfDb+4Nln64", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetString); + LIB_FUNC("YE4dbtbz6OE", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetInt32); + LIB_FUNC("AzD4irAcKE4", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetUInt32); + LIB_FUNC("56QLTqx911s", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetInt64); + LIB_FUNC("xvsP5Yz6FmY", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetUInt64); + LIB_FUNC("lbPlT4+QVcE", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetFloat32); + LIB_FUNC("4Fu8tHW+u-k", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetFloat64); + LIB_FUNC("Fidd8vWgyVE", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetBool); + LIB_FUNC("wAcxBDLHj1M", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetBinary); + LIB_FUNC("74ASEqxSnkM", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetObject); + LIB_FUNC("Wxbg5x3pTXA", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyObjectSetArray); + LIB_FUNC("Hm7qubT3b70", + LibNpUniversalDataSystem::NpUniversalDataSystemCreateEventPropertyArray); + LIB_FUNC("W-0xwY0ZMjw", + LibNpUniversalDataSystem::NpUniversalDataSystemDestroyEventPropertyArray); + LIB_FUNC("4llLk7YJRTE", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetString); + LIB_FUNC("BypQuF113-k", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetInt32); + LIB_FUNC("yMi0xAOpmXM", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetUInt32); + LIB_FUNC("viVXAwmmYrY", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetInt64); + LIB_FUNC("Qo9qR7v5zO4", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetUInt64); + LIB_FUNC("JmgwKm96Lq4", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetFloat32); + LIB_FUNC("sbSYZLR5AiE", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetFloat64); + LIB_FUNC("0+l4QSWCM4E", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetBool); + LIB_FUNC("IEdUCV9j2Cw", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetBinary); + LIB_FUNC("XY14n3jNIpE", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetObject); + LIB_FUNC("rdi9BAfDLq8", + LibNpUniversalDataSystem::NpUniversalDataSystemEventPropertyArraySetArray); + LIB_FUNC("KmN62tT4U8A", LibNpUniversalDataSystem::NpUniversalDataSystemGetStorageStat); + LIB_FUNC("47UAEuQl+iI", LibNpUniversalDataSystem::NpUniversalDataSystemTerminate); +} + +} // namespace LibNpUniversalDataSystem + +} // namespace Libs diff --git a/src/main.cpp b/src/main.cpp index ac3ff6c4d..eb4b2942e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,6 +84,8 @@ static void PrintUsage() { ::printf(" --spirv-debug-printf Enable SPIR-V debug printf.\n"); ::printf( " --readback-linear-images Read back writable linear images on submit.\n"); + ::printf( + " --trophy-notifications Show trophy unlock toasts and play their sound.\n"); ::printf(" --playgo-hack Use the supplied PlayGo stub fallback.\n"); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS ::printf(" --redzone Protect the guest SysV red zone.\n"); @@ -421,6 +423,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); return false; } + } else if (arg == "--trophy-notifications") { + if (!ParseBool(value, options.config.trophy_enabled)) { + ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); + return false; + } } else if (arg == "--keymap") { const auto split = value.find('='); if (split == std::string::npos || split == 0 || split + 1 == value.size()) {