diff options
133 files changed, 4628 insertions, 998 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 04018233f..f18239edb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -88,6 +88,7 @@ add_subdirectory(tests) if (ENABLE_SDL2) add_subdirectory(yuzu_cmd) + add_subdirectory(yuzu_tester) endif() if (ENABLE_QT) diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index 426e38b45..6a5f53a57 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp @@ -51,6 +51,10 @@ void Stream::Stop() { UNIMPLEMENTED(); } +void Stream::SetVolume(float volume) { + game_volume = volume; +} + Stream::State Stream::GetState() const { return state; } @@ -62,8 +66,8 @@ s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { return Core::Timing::usToCycles(us); } -static void VolumeAdjustSamples(std::vector<s16>& samples) { - const float volume{std::clamp(Settings::values.volume, 0.0f, 1.0f)}; +static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { + const float volume{std::clamp(Settings::values.volume - (1.0f - game_volume), 0.0f, 1.0f)}; if (volume == 1.0f) { return; @@ -97,7 +101,7 @@ void Stream::PlayNextBuffer() { active_buffer = queued_buffers.front(); queued_buffers.pop(); - VolumeAdjustSamples(active_buffer->GetSamples()); + VolumeAdjustSamples(active_buffer->GetSamples(), game_volume); sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index 05071243b..8106cea43 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h @@ -61,6 +61,12 @@ public: /// Returns a vector of recently released buffers specified by tag std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count); + void SetVolume(float volume); + + float GetVolume() const { + return game_volume; + } + /// Returns true if the stream is currently playing bool IsPlaying() const { return state == State::Playing; @@ -94,6 +100,7 @@ private: u32 sample_rate; ///< Sample rate of the stream Format format; ///< Format of the stream + float game_volume = 1.0f; ///< The volume the game currently has set ReleaseCallback release_callback; ///< Buffer release callback for the stream State state{State::Stopped}; ///< Playback state of the stream Core::Timing::EventType* release_event{}; ///< Core timing release event for the stream diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp index 5b63f9e81..c2f6cf0f6 100644 --- a/src/common/hex_util.cpp +++ b/src/common/hex_util.cpp @@ -30,13 +30,6 @@ std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) { return out; } -std::string HexVectorToString(const std::vector<u8>& vector, bool upper) { - std::string out; - for (u8 c : vector) - out += fmt::format(upper ? "{:02X}" : "{:02x}", c); - return out; -} - std::array<u8, 16> operator""_array16(const char* str, std::size_t len) { if (len != 32) { LOG_ERROR(Common, diff --git a/src/common/hex_util.h b/src/common/hex_util.h index 68f003cb6..bb4736f96 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -7,6 +7,7 @@ #include <array> #include <cstddef> #include <string> +#include <type_traits> #include <vector> #include <fmt/format.h> #include "common/common_types.h" @@ -30,13 +31,20 @@ std::array<u8, Size> HexStringToArray(std::string_view str) { return out; } -std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true); +template <typename ContiguousContainer> +std::string HexToString(const ContiguousContainer& data, bool upper = true) { + static_assert(std::is_same_v<typename ContiguousContainer::value_type, u8>, + "Underlying type within the contiguous container must be u8."); + + constexpr std::size_t pad_width = 2; -template <std::size_t Size> -std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { std::string out; - for (u8 c : array) + out.reserve(std::size(data) * pad_width); + + for (const u8 c : data) { out += fmt::format(upper ? "{:02X}" : "{:02x}", c); + } + return out; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a2e2e976e..cb77b99ee 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -45,6 +45,8 @@ add_library(core STATIC file_sys/fsmitm_romfsbuild.h file_sys/ips_layer.cpp file_sys/ips_layer.h + file_sys/kernel_executable.cpp + file_sys/kernel_executable.h file_sys/mode.h file_sys/nca_metadata.cpp file_sys/nca_metadata.h @@ -207,8 +209,6 @@ add_library(core STATIC hle/service/apm/apm.h hle/service/apm/interface.cpp hle/service/apm/interface.h - hle/service/arp/arp.cpp - hle/service/arp/arp.h hle/service/audio/audctl.cpp hle/service/audio/audctl.h hle/service/audio/auddbg.cpp @@ -274,6 +274,15 @@ add_library(core STATIC hle/service/friend/friend.h hle/service/friend/interface.cpp hle/service/friend/interface.h + hle/service/glue/arp.cpp + hle/service/glue/arp.h + hle/service/glue/bgtc.cpp + hle/service/glue/bgtc.h + hle/service/glue/errors.h + hle/service/glue/glue.cpp + hle/service/glue/glue.h + hle/service/glue/manager.cpp + hle/service/glue/manager.h hle/service/grc/grc.cpp hle/service/grc/grc.h hle/service/hid/hid.cpp @@ -440,6 +449,8 @@ add_library(core STATIC loader/deconstructed_rom_directory.h loader/elf.cpp loader/elf.h + loader/kip.cpp + loader/kip.h loader/loader.cpp loader/loader.h loader/nax.cpp @@ -459,19 +470,20 @@ add_library(core STATIC memory_setup.h perf_stats.cpp perf_stats.h + reporter.cpp + reporter.h settings.cpp settings.h telemetry_session.cpp telemetry_session.h - tracer/citrace.h - tracer/recorder.cpp - tracer/recorder.h + tools/freezer.cpp + tools/freezer.h ) create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt mbedtls opus unicorn open_source_archives) +target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives) if (ENABLE_WEB_SERVICE) target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) target_link_libraries(core PRIVATE web_service) diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index 2223cbeed..372612c9b 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -2,26 +2,213 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <map> +#include <optional> +#include "common/bit_field.h" #include "common/common_types.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" +#include "core/core.h" +#include "core/loader/loader.h" #include "core/memory.h" namespace Core { -void ARM_Interface::LogBacktrace() const { - VAddr fp = GetReg(29); - VAddr lr = GetReg(30); - const VAddr sp = GetReg(13); - const VAddr pc = GetPC(); - LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc); +namespace { + +constexpr u64 ELF_DYNAMIC_TAG_NULL = 0; +constexpr u64 ELF_DYNAMIC_TAG_STRTAB = 5; +constexpr u64 ELF_DYNAMIC_TAG_SYMTAB = 6; +constexpr u64 ELF_DYNAMIC_TAG_SYMENT = 11; + +enum class ELFSymbolType : u8 { + None = 0, + Object = 1, + Function = 2, + Section = 3, + File = 4, + Common = 5, + TLS = 6, +}; + +enum class ELFSymbolBinding : u8 { + Local = 0, + Global = 1, + Weak = 2, +}; + +enum class ELFSymbolVisibility : u8 { + Default = 0, + Internal = 1, + Hidden = 2, + Protected = 3, +}; + +struct ELFSymbol { + u32 name_index; + union { + u8 info; + + BitField<0, 4, ELFSymbolType> type; + BitField<4, 4, ELFSymbolBinding> binding; + }; + ELFSymbolVisibility visibility; + u16 sh_index; + u64 value; + u64 size; +}; +static_assert(sizeof(ELFSymbol) == 0x18, "ELFSymbol has incorrect size."); + +using Symbols = std::vector<std::pair<ELFSymbol, std::string>>; + +Symbols GetSymbols(VAddr text_offset) { + const auto mod_offset = text_offset + Memory::Read32(text_offset + 4); + + if (mod_offset < text_offset || (mod_offset & 0b11) != 0 || + Memory::Read32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { + return {}; + } + + const auto dynamic_offset = Memory::Read32(mod_offset + 0x4) + mod_offset; + + VAddr string_table_offset{}; + VAddr symbol_table_offset{}; + u64 symbol_entry_size{}; + + VAddr dynamic_index = dynamic_offset; + while (true) { + const auto tag = Memory::Read64(dynamic_index); + const auto value = Memory::Read64(dynamic_index + 0x8); + dynamic_index += 0x10; + + if (tag == ELF_DYNAMIC_TAG_NULL) { + break; + } + + if (tag == ELF_DYNAMIC_TAG_STRTAB) { + string_table_offset = value; + } else if (tag == ELF_DYNAMIC_TAG_SYMTAB) { + symbol_table_offset = value; + } else if (tag == ELF_DYNAMIC_TAG_SYMENT) { + symbol_entry_size = value; + } + } + + if (string_table_offset == 0 || symbol_table_offset == 0 || symbol_entry_size == 0) { + return {}; + } + + const auto string_table_address = text_offset + string_table_offset; + const auto symbol_table_address = text_offset + symbol_table_offset; + + Symbols out; + + VAddr symbol_index = symbol_table_address; + while (symbol_index < string_table_address) { + ELFSymbol symbol{}; + Memory::ReadBlock(symbol_index, &symbol, sizeof(ELFSymbol)); + + VAddr string_offset = string_table_address + symbol.name_index; + std::string name; + for (u8 c = Memory::Read8(string_offset); c != 0; c = Memory::Read8(++string_offset)) { + name += static_cast<char>(c); + } + + symbol_index += symbol_entry_size; + out.push_back({symbol, name}); + } + + return out; +} + +std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_address) { + const auto iter = + std::find_if(symbols.begin(), symbols.end(), [func_address](const auto& pair) { + const auto& [symbol, name] = pair; + const auto end_address = symbol.value + symbol.size; + return func_address >= symbol.value && func_address < end_address; + }); + + if (iter == symbols.end()) { + return std::nullopt; + } + + return iter->second; +} + +} // Anonymous namespace + +constexpr u64 SEGMENT_BASE = 0x7100000000ull; + +std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const { + std::vector<BacktraceEntry> out; + + auto fp = GetReg(29); + auto lr = GetReg(30); + while (true) { - LOG_ERROR(Core_ARM, "{:016X}", lr); + out.push_back({"", 0, lr, 0}); if (!fp) { break; } lr = Memory::Read64(fp + 8) - 4; fp = Memory::Read64(fp); } + + std::map<VAddr, std::string> modules; + auto& loader{System::GetInstance().GetAppLoader()}; + if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) { + return {}; + } + + std::map<std::string, Symbols> symbols; + for (const auto& module : modules) { + symbols.insert_or_assign(module.second, GetSymbols(module.first)); + } + + for (auto& entry : out) { + VAddr base = 0; + for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) { + const auto& module{*iter}; + if (entry.original_address >= module.first) { + entry.module = module.second; + base = module.first; + break; + } + } + + entry.offset = entry.original_address - base; + entry.address = SEGMENT_BASE + entry.offset; + + if (entry.module.empty()) + entry.module = "unknown"; + + const auto symbol_set = symbols.find(entry.module); + if (symbol_set != symbols.end()) { + const auto symbol = GetSymbolName(symbol_set->second, entry.offset); + if (symbol.has_value()) { + // TODO(DarkLordZach): Add demangling of symbol names. + entry.name = *symbol; + } + } + } + + return out; +} + +void ARM_Interface::LogBacktrace() const { + const VAddr sp = GetReg(13); + const VAddr pc = GetPC(); + LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc); + LOG_ERROR(Core_ARM, "{:20}{:20}{:20}{:20}{}", "Module Name", "Address", "Original Address", + "Offset", "Symbol"); + LOG_ERROR(Core_ARM, ""); + + const auto backtrace = GetBacktrace(); + for (const auto& entry : backtrace) { + LOG_ERROR(Core_ARM, "{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address, + entry.original_address, entry.offset, entry.name); + } } + } // namespace Core diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 978b1518f..c6691a8e1 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <vector> #include "common/common_types.h" namespace Common { @@ -152,6 +153,16 @@ public: /// Prepare core for thread reschedule (if needed to correctly handle state) virtual void PrepareReschedule() = 0; + struct BacktraceEntry { + std::string module; + u64 address; + u64 original_address; + u64 offset; + std::string name; + }; + + std::vector<BacktraceEntry> GetBacktrace() const; + /// fp (= r29) points to the last frame record. /// Note that this is the frame record for the *previous* frame, not the current one. /// Note we need to subtract 4 from our last read to get the proper address diff --git a/src/core/core.cpp b/src/core/core.cpp index ff0721079..262411db8 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -25,19 +25,47 @@ #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/glue/manager.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" #include "core/perf_stats.h" +#include "core/reporter.h" #include "core/settings.h" #include "core/telemetry_session.h" +#include "core/tools/freezer.h" #include "file_sys/cheat_engine.h" +#include "file_sys/patch_manager.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" namespace Core { +namespace { + +FileSys::StorageId GetStorageIdForFrontendSlot( + std::optional<FileSys::ContentProviderUnionSlot> slot) { + if (!slot.has_value()) { + return FileSys::StorageId::None; + } + + switch (*slot) { + case FileSys::ContentProviderUnionSlot::UserNAND: + return FileSys::StorageId::NandUser; + case FileSys::ContentProviderUnionSlot::SysNAND: + return FileSys::StorageId::NandSystem; + case FileSys::ContentProviderUnionSlot::SDMC: + return FileSys::StorageId::SdCard; + case FileSys::ContentProviderUnionSlot::FrontendManual: + return FileSys::StorageId::Host; + default: + return FileSys::StorageId::None; + } +} + +} // Anonymous namespace + /*static*/ System System::s_instance; FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, @@ -74,7 +102,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, return vfs->OpenFile(path, FileSys::Mode::Read); } struct System::Impl { - explicit Impl(System& system) : kernel{system}, cpu_core_manager{system} {} + explicit Impl(System& system) : kernel{system}, cpu_core_manager{system}, reporter{system} {} Cpu& CurrentCpuCore() { return cpu_core_manager.GetCurrentCore(); @@ -109,6 +137,9 @@ struct System::Impl { /// Create default implementations of applets if one is not provided. applet_manager.SetDefaultAppletsIfMissing(); + /// Reset all glue registrations + arp_manager.ResetAll(); + telemetry_session = std::make_unique<Core::TelemetrySession>(); service_manager = std::make_shared<Service::SM::ServiceManager>(); @@ -150,7 +181,8 @@ struct System::Impl { } telemetry_session->AddInitialInfo(*app_loader); - auto main_process = Kernel::Process::Create(system, "main"); + auto main_process = + Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland); const auto [load_result, load_parameters] = app_loader->Load(*main_process); if (load_result != Loader::ResultStatus::Success) { LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result)); @@ -159,6 +191,7 @@ struct System::Impl { return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) + static_cast<u32>(load_result)); } + AddGlueRegistrationForProcess(*app_loader, *main_process); kernel.MakeCurrentProcess(main_process.get()); // Main process has been loaded and been made current. @@ -217,6 +250,31 @@ struct System::Impl { return app_loader->ReadTitle(out); } + void AddGlueRegistrationForProcess(Loader::AppLoader& loader, Kernel::Process& process) { + std::vector<u8> nacp_data; + FileSys::NACP nacp; + if (loader.ReadControlData(nacp) == Loader::ResultStatus::Success) { + nacp_data = nacp.GetRawBytes(); + } else { + nacp_data.resize(sizeof(FileSys::RawNACP)); + } + + Service::Glue::ApplicationLaunchProperty launch{}; + launch.title_id = process.GetTitleID(); + + FileSys::PatchManager pm{launch.title_id}; + launch.version = pm.GetGameVersion().value_or(0); + + // TODO(DarkLordZach): When FSController/Game Card Support is added, if + // current_process_game_card use correct StorageId + launch.base_game_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry( + launch.title_id, FileSys::ContentRecordType::Program)); + launch.update_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry( + FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program)); + + arp_manager.Register(launch.title_id, launch, std::move(nacp_data)); + } + void SetStatus(ResultStatus new_status, const char* details = nullptr) { status = new_status; if (details) { @@ -243,16 +301,22 @@ struct System::Impl { bool is_powered_on = false; std::unique_ptr<FileSys::CheatEngine> cheat_engine; + std::unique_ptr<Tools::Freezer> memory_freezer; /// Frontend applets Service::AM::Applets::AppletManager applet_manager; + /// Glue services + Service::Glue::ARPManager arp_manager; + /// Service manager std::shared_ptr<Service::SM::ServiceManager> service_manager; /// Telemetry session for this emulation session std::unique_ptr<Core::TelemetrySession> telemetry_session; + Reporter reporter; + ResultStatus status = ResultStatus::Success; std::string status_details = ""; @@ -492,6 +556,18 @@ void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) { impl->content_provider->ClearSlot(slot); } +const Reporter& System::GetReporter() const { + return impl->reporter; +} + +Service::Glue::ARPManager& System::GetARPManager() { + return impl->arp_manager; +} + +const Service::Glue::ARPManager& System::GetARPManager() const { + return impl->arp_manager; +} + System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { return impl->Init(*this, emu_window); } diff --git a/src/core/core.h b/src/core/core.h index 20959de54..70adb7af9 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -8,6 +8,7 @@ #include <memory> #include <string> +#include <map> #include "common/common_types.h" #include "core/file_sys/vfs_types.h" #include "core/hle/kernel/object.h" @@ -42,6 +43,10 @@ struct AppletFrontendSet; class AppletManager; } // namespace AM::Applets +namespace Glue { +class ARPManager; +} + namespace SM { class ServiceManager; } // namespace SM @@ -68,6 +73,7 @@ class Cpu; class ExclusiveMonitor; class FrameLimiter; class PerfStats; +class Reporter; class TelemetrySession; struct PerfStatsResults; @@ -284,6 +290,12 @@ public: void ClearContentProvider(FileSys::ContentProviderUnionSlot slot); + const Reporter& GetReporter() const; + + Service::Glue::ARPManager& GetARPManager(); + + const Service::Glue::ARPManager& GetARPManager() const; + private: System(); diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index dc006e2bb..6dd633363 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -572,7 +572,7 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, << "# If you are experiencing issues involving keys, it may help to delete this file\n"; } - file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key)); + file << fmt::format("\n{} = {}", keyname, Common::HexToString(key)); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title); } @@ -583,7 +583,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { Key128 rights_id; std::memcpy(rights_id.data(), &field2, sizeof(u64)); std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64)); - WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key); + WriteKeyToFile(KeyCategory::Title, Common::HexToString(rights_id), key); } auto category = KeyCategory::Standard; diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp index ed0775444..01a969be9 100644 --- a/src/core/crypto/partition_data_manager.cpp +++ b/src/core/crypto/partition_data_manager.cpp @@ -22,8 +22,10 @@ #include "core/crypto/key_manager.h" #include "core/crypto/partition_data_manager.h" #include "core/crypto/xts_encryption_layer.h" +#include "core/file_sys/kernel_executable.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs_vector.h" using namespace Common; @@ -45,36 +47,6 @@ struct Package2Header { }; static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size."); -struct INIHeader { - u32_le magic; - u32_le size; - u32_le process_count; - INSERT_PADDING_BYTES(4); -}; -static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size."); - -struct SectionHeader { - u32_le offset; - u32_le size_decompressed; - u32_le size_compressed; - u32_le attribute; -}; -static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size."); - -struct KIPHeader { - u32_le magic; - std::array<char, 12> name; - u64_le title_id; - u32_le category; - u8 priority; - u8 core; - INSERT_PADDING_BYTES(1); - u8 flags; - std::array<SectionHeader, 6> sections; - std::array<u32, 0x20> capabilities; -}; -static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size."); - const std::array<SHA256Hash, 0x10> source_hashes{ "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source @@ -170,65 +142,6 @@ const std::array<SHA256Hash, 0x20> master_key_hashes{ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F }; -static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) { - const auto data_size = in.size() - 0xC; - - u32 compressed_size{}; - u32 init_index{}; - u32 additional_size{}; - std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32)); - std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32)); - std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32)); - - std::vector<u8> out(in.size() + additional_size); - - if (compressed_size == in.size()) - std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size); - else - std::memcpy(out.data(), in.data(), compressed_size); - - auto index = in.size() - init_index; - auto out_index = out.size(); - - while (out_index > 0) { - --index; - auto control = in[index]; - for (size_t i = 0; i < 8; ++i) { - if ((control & 0x80) > 0) { - ASSERT(index >= 2); - index -= 2; - u64 segment_offset = in[index] | in[index + 1] << 8; - u64 segment_size = ((segment_offset >> 12) & 0xF) + 3; - segment_offset &= 0xFFF; - segment_offset += 3; - - if (out_index < segment_size) - segment_size = out_index; - - ASSERT(out_index >= segment_size); - - out_index -= segment_size; - - for (size_t j = 0; j < segment_size; ++j) { - ASSERT(out_index + j + segment_offset < out.size()); - out[out_index + j] = out[out_index + j + segment_offset]; - } - } else { - ASSERT(out_index >= 1); - --out_index; - --index; - out[out_index] = in[index]; - } - - control <<= 1; - if (out_index == 0) - return out; - } - } - - return out; -} - static u8 CalculateMaxKeyblobSourceHash() { for (s8 i = 0x1F; i >= 0; --i) { if (keyblob_source_hashes[i] != SHA256Hash{}) @@ -478,37 +391,22 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()}); cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt); - INIHeader ini; - std::memcpy(&ini, c.data(), sizeof(INIHeader)); - if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1')) + const auto ini_file = std::make_shared<FileSys::VectorVfsFile>(c); + const FileSys::INI ini{ini_file}; + if (ini.GetStatus() != Loader::ResultStatus::Success) return; - u64 offset = sizeof(INIHeader); - for (size_t i = 0; i < ini.process_count; ++i) { - KIPHeader kip; - std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader)); - if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1')) + for (const auto& kip : ini.GetKIPs()) { + if (kip.GetStatus() != Loader::ResultStatus::Success) return; - const auto name = - Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size()); - - if (name != "FS" && name != "spl") { - offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + - kip.sections[1].size_compressed + kip.sections[2].size_compressed; + if (kip.GetName() != "FS" && kip.GetName() != "spl") { continue; } - const u64 initial_offset = sizeof(KIPHeader) + offset; - const auto text_begin = c.cbegin() + initial_offset; - const auto text_end = text_begin + kip.sections[0].size_compressed; - const std::vector<u8> text = DecompressBLZ({text_begin, text_end}); - - const auto rodata_end = text_end + kip.sections[1].size_compressed; - const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end}); - - const auto data_end = rodata_end + kip.sections[2].size_compressed; - const std::vector<u8> data = DecompressBLZ({rodata_end, data_end}); + const auto& text = kip.GetTextSection(); + const auto& rodata = kip.GetRODataSection(); + const auto& data = kip.GetDataSection(); std::vector<u8> out; out.reserve(text.size() + rodata.size() + data.size()); @@ -516,12 +414,9 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa out.insert(out.end(), rodata.begin(), rodata.end()); out.insert(out.end(), data.begin(), data.end()); - offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + - kip.sections[1].size_compressed + kip.sections[2].size_compressed; - - if (name == "FS") + if (kip.GetName() == "FS") package2_fs[static_cast<size_t>(type)] = std::move(out); - else if (name == "spl") + else if (kip.GetName() == "spl") package2_spl[static_cast<size_t>(type)] = std::move(out); } } diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 2c145bd09..626ed0042 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -18,11 +18,16 @@ namespace FileSys { -constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"}; +constexpr std::array partition_names{ + "update", + "normal", + "secure", + "logo", +}; XCI::XCI(VirtualFile file_) : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, - partitions(0x4) { + partitions(partition_names.size()) { if (file->ReadObject(&header) != sizeof(GamecardHeader)) { status = Loader::ResultStatus::ErrorBadXCIHeader; return; @@ -43,23 +48,24 @@ XCI::XCI(VirtualFile file_) for (XCIPartition partition : {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { - auto raw = main_hfs.GetFile(partition_names[static_cast<std::size_t>(partition)]); - if (raw != nullptr) - partitions[static_cast<std::size_t>(partition)] = - std::make_shared<PartitionFilesystem>(raw); + const auto partition_idx = static_cast<std::size_t>(partition); + auto raw = main_hfs.GetFile(partition_names[partition_idx]); + + if (raw != nullptr) { + partitions[partition_idx] = std::make_shared<PartitionFilesystem>(std::move(raw)); + } } secure_partition = std::make_shared<NSP>( main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)])); - const auto secure_ncas = secure_partition->GetNCAsCollapsed(); - std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas)); - + ncas = secure_partition->GetNCAsCollapsed(); program = secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program); program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID()); - if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) + if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) { program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; + } auto result = AddNCAFromPartition(XCIPartition::Update); if (result != Loader::ResultStatus::Success) { @@ -147,8 +153,9 @@ std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { auto nca = GetNCAByType(type); - if (nca != nullptr) + if (nca != nullptr) { return nca->GetBaseFile(); + } return nullptr; } @@ -169,17 +176,22 @@ VirtualDir XCI::GetParentDirectory() const { } Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { - if (partitions[static_cast<std::size_t>(part)] == nullptr) { + const auto partition_index = static_cast<std::size_t>(part); + const auto& partition = partitions[partition_index]; + + if (partition == nullptr) { return Loader::ResultStatus::ErrorXCIMissingPartition; } - for (const VirtualFile& file : partitions[static_cast<std::size_t>(part)]->GetFiles()) { - if (file->GetExtension() != "nca") + for (const VirtualFile& file : partition->GetFiles()) { + if (file->GetExtension() != "nca") { continue; + } + auto nca = std::make_shared<NCA>(file, nullptr, 0, keys); - // TODO(DarkLordZach): Add proper Rev1+ Support - if (nca->IsUpdate()) + if (nca->IsUpdate()) { continue; + } if (nca->GetType() == NCAContentType::Program) { program_nca_status = nca->GetStatus(); } @@ -188,7 +200,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { } else { const u16 error_id = static_cast<u16>(nca->GetStatus()); LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})", - partition_names[static_cast<std::size_t>(part)], nca->GetName(), error_id, + partition_names[partition_index], nca->GetName(), error_id, nca->GetStatus()); } } diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 5aa3b600b..ce5c69b41 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -452,13 +452,13 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s switch (s_header.raw.header.crypto_type) { case NCASectionCryptoType::NONE: - LOG_DEBUG(Crypto, "called with mode=NONE"); + LOG_TRACE(Crypto, "called with mode=NONE"); return in; case NCASectionCryptoType::CTR: // During normal BKTR decryption, this entire function is skipped. This is for the metadata, // which uses the same CTR as usual. case NCASectionCryptoType::BKTR: - LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); + LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); { std::optional<Core::Crypto::Key128> key = {}; if (has_rights_id) { diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 04da30825..f155a1341 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -87,6 +87,10 @@ u64 NACP::GetDefaultJournalSaveSize() const { return raw.user_account_save_data_journal_size; } +bool NACP::GetUserAccountSwitchLock() const { + return raw.user_account_switch_lock != 0; +} + u32 NACP::GetSupportedLanguages() const { return raw.supported_languages; } diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 1be34ed55..2d8c251ac 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -30,7 +30,8 @@ struct RawNACP { std::array<LanguageEntry, 16> language_entries; std::array<u8, 0x25> isbn; u8 startup_user_account; - INSERT_PADDING_BYTES(2); + u8 user_account_switch_lock; + u8 addon_content_registration_type; u32_le application_attribute; u32_le supported_languages; u32_le parental_control; @@ -111,6 +112,7 @@ public: u64 GetDefaultJournalSaveSize() const; u32 GetSupportedLanguages() const; std::vector<u8> GetRawBytes() const; + bool GetUserAccountSwitchLock() const; private: RawNACP raw{}; diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index 485c4913a..a08a70efd 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -287,7 +287,6 @@ void IPSwitchCompiler::Parse() { } else { // hex replacement const auto value = patch_line.substr(9); - replace.reserve(value.size() / 2); replace = Common::HexStringToVector(value, is_little_endian); } @@ -295,7 +294,7 @@ void IPSwitchCompiler::Parse() { LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} " "with byte string '{}'", - patch_text->GetName(), offset, Common::HexVectorToString(replace)); + patch_text->GetName(), offset, Common::HexToString(replace)); } patch.records.insert_or_assign(offset, std::move(replace)); diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp new file mode 100644 index 000000000..371300684 --- /dev/null +++ b/src/core/file_sys/kernel_executable.cpp @@ -0,0 +1,228 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/string_util.h" +#include "core/file_sys/kernel_executable.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +constexpr u32 INI_MAX_KIPS = 0x50; + +namespace { +bool DecompressBLZ(std::vector<u8>& data) { + if (data.size() < 0xC) + return {}; + + const auto data_size = data.size() - 0xC; + + u32 compressed_size{}; + u32 init_index{}; + u32 additional_size{}; + std::memcpy(&compressed_size, data.data() + data_size, sizeof(u32)); + std::memcpy(&init_index, data.data() + data_size + 0x4, sizeof(u32)); + std::memcpy(&additional_size, data.data() + data_size + 0x8, sizeof(u32)); + + const auto start_offset = data.size() - compressed_size; + data.resize(compressed_size + additional_size + start_offset); + + std::size_t index = compressed_size - init_index; + std::size_t out_index = compressed_size + additional_size; + + while (out_index > 0) { + --index; + auto control = data[index + start_offset]; + for (size_t i = 0; i < 8; ++i) { + if (((control << i) & 0x80) > 0) { + if (index < 2) { + return false; + } + index -= 2; + std::size_t segment_offset = + data[index + start_offset] | data[index + start_offset + 1] << 8; + std::size_t segment_size = ((segment_offset >> 12) & 0xF) + 3; + segment_offset &= 0xFFF; + segment_offset += 3; + + if (out_index < segment_size) + segment_size = out_index; + + if (out_index < segment_size) { + return false; + } + + out_index -= segment_size; + + for (size_t j = 0; j < segment_size; ++j) { + if (out_index + j + segment_offset + start_offset >= data.size()) { + return false; + } + data[out_index + j + start_offset] = + data[out_index + j + segment_offset + start_offset]; + } + } else { + if (out_index < 1) { + return false; + } + --out_index; + --index; + data[out_index + start_offset] = data[index + start_offset]; + } + + if (out_index == 0) + break; + } + } + + return true; +} +} // Anonymous namespace + +KIP::KIP(const VirtualFile& file) : status(Loader::ResultStatus::Success) { + if (file == nullptr) { + status = Loader::ResultStatus::ErrorNullFile; + return; + } + + if (file->GetSize() < sizeof(KIPHeader) || file->ReadObject(&header) != sizeof(KIPHeader)) { + status = Loader::ResultStatus::ErrorBadKIPHeader; + return; + } + + if (header.magic != Common::MakeMagic('K', 'I', 'P', '1')) { + status = Loader::ResultStatus::ErrorBadKIPHeader; + return; + } + + u64 offset = sizeof(KIPHeader); + for (std::size_t i = 0; i < header.sections.size(); ++i) { + auto compressed = file->ReadBytes(header.sections[i].compressed_size, offset); + offset += header.sections[i].compressed_size; + + if (header.sections[i].compressed_size == 0 && header.sections[i].decompressed_size != 0) { + decompressed_sections[i] = std::vector<u8>(header.sections[i].decompressed_size); + } else if (header.sections[i].compressed_size == header.sections[i].decompressed_size) { + decompressed_sections[i] = std::move(compressed); + } else { + decompressed_sections[i] = compressed; + if (!DecompressBLZ(decompressed_sections[i])) { + status = Loader::ResultStatus::ErrorBLZDecompressionFailed; + return; + } + } + } +} + +Loader::ResultStatus KIP::GetStatus() const { + return status; +} + +std::string KIP::GetName() const { + return Common::StringFromFixedZeroTerminatedBuffer(header.name.data(), header.name.size()); +} + +u64 KIP::GetTitleID() const { + return header.title_id; +} + +std::vector<u8> KIP::GetSectionDecompressed(u8 index) const { + return decompressed_sections[index]; +} + +bool KIP::Is64Bit() const { + return (header.flags & 0x8) != 0; +} + +bool KIP::Is39BitAddressSpace() const { + return (header.flags & 0x10) != 0; +} + +bool KIP::IsService() const { + return (header.flags & 0x20) != 0; +} + +std::vector<u32> KIP::GetKernelCapabilities() const { + return std::vector<u32>(header.capabilities.begin(), header.capabilities.end()); +} + +s32 KIP::GetMainThreadPriority() const { + return header.main_thread_priority; +} + +u32 KIP::GetMainThreadStackSize() const { + return header.sections[1].attribute; +} + +u32 KIP::GetMainThreadCpuCore() const { + return header.default_core; +} + +const std::vector<u8>& KIP::GetTextSection() const { + return decompressed_sections[0]; +} + +const std::vector<u8>& KIP::GetRODataSection() const { + return decompressed_sections[1]; +} + +const std::vector<u8>& KIP::GetDataSection() const { + return decompressed_sections[2]; +} + +u32 KIP::GetTextOffset() const { + return header.sections[0].offset; +} + +u32 KIP::GetRODataOffset() const { + return header.sections[1].offset; +} + +u32 KIP::GetDataOffset() const { + return header.sections[2].offset; +} + +u32 KIP::GetBSSSize() const { + return header.sections[3].decompressed_size; +} + +u32 KIP::GetBSSOffset() const { + return header.sections[3].offset; +} + +INI::INI(const VirtualFile& file) : status(Loader::ResultStatus::Success) { + if (file->GetSize() < sizeof(INIHeader) || file->ReadObject(&header) != sizeof(INIHeader)) { + status = Loader::ResultStatus::ErrorBadINIHeader; + return; + } + + if (header.magic != Common::MakeMagic('I', 'N', 'I', '1')) { + status = Loader::ResultStatus::ErrorBadINIHeader; + return; + } + + if (header.kip_count > INI_MAX_KIPS) { + status = Loader::ResultStatus::ErrorINITooManyKIPs; + return; + } + + u64 offset = sizeof(INIHeader); + for (std::size_t i = 0; i < header.kip_count; ++i) { + const auto kip_file = + std::make_shared<OffsetVfsFile>(file, file->GetSize() - offset, offset); + KIP kip(kip_file); + if (kip.GetStatus() == Loader::ResultStatus::Success) { + kips.push_back(std::move(kip)); + } + } +} + +Loader::ResultStatus INI::GetStatus() const { + return status; +} + +const std::vector<KIP>& INI::GetKIPs() const { + return kips; +} + +} // namespace FileSys diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h new file mode 100644 index 000000000..324a57384 --- /dev/null +++ b/src/core/file_sys/kernel_executable.h @@ -0,0 +1,99 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/swap.h" +#include "core/file_sys/vfs_types.h" +#include "core/loader/loader.h" + +namespace FileSys { + +struct KIPSectionHeader { + u32_le offset; + u32_le decompressed_size; + u32_le compressed_size; + u32_le attribute; +}; +static_assert(sizeof(KIPSectionHeader) == 0x10, "KIPSectionHeader has incorrect size."); + +struct KIPHeader { + u32_le magic; + std::array<char, 0xC> name; + u64_le title_id; + u32_le process_category; + u8 main_thread_priority; + u8 default_core; + INSERT_PADDING_BYTES(1); + u8 flags; + std::array<KIPSectionHeader, 6> sections; + std::array<u32, 0x20> capabilities; +}; +static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size."); + +struct INIHeader { + u32_le magic; + u32_le size; + u32_le kip_count; + INSERT_PADDING_BYTES(0x4); +}; +static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size."); + +// Kernel Internal Process +class KIP { +public: + explicit KIP(const VirtualFile& file); + + Loader::ResultStatus GetStatus() const; + + std::string GetName() const; + u64 GetTitleID() const; + std::vector<u8> GetSectionDecompressed(u8 index) const; + + // Executable Flags + bool Is64Bit() const; + bool Is39BitAddressSpace() const; + bool IsService() const; + + std::vector<u32> GetKernelCapabilities() const; + + s32 GetMainThreadPriority() const; + u32 GetMainThreadStackSize() const; + u32 GetMainThreadCpuCore() const; + + const std::vector<u8>& GetTextSection() const; + const std::vector<u8>& GetRODataSection() const; + const std::vector<u8>& GetDataSection() const; + + u32 GetTextOffset() const; + u32 GetRODataOffset() const; + u32 GetDataOffset() const; + + u32 GetBSSSize() const; + u32 GetBSSOffset() const; + +private: + Loader::ResultStatus status; + + KIPHeader header{}; + std::array<std::vector<u8>, 6> decompressed_sections; +}; + +class INI { +public: + explicit INI(const VirtualFile& file); + + Loader::ResultStatus GetStatus() const; + + const std::vector<KIP>& GetKIPs() const; + +private: + Loader::ResultStatus status; + + INIHeader header{}; + std::vector<KIP> kips; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 78dbadee3..a8f80e2c6 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -142,7 +142,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD if (!compiler.IsValid()) continue; - auto this_build_id = Common::HexArrayToString(compiler.GetBuildID()); + auto this_build_id = Common::HexToString(compiler.GetBuildID()); this_build_id = this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1); @@ -168,7 +168,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st return nso; } - const auto build_id_raw = Common::HexArrayToString(header.build_id); + const auto build_id_raw = Common::HexToString(header.build_id); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); if (Settings::values.dump_nso) { @@ -219,7 +219,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st } bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { - const auto build_id_raw = Common::HexArrayToString(build_id_); + const auto build_id_raw = Common::HexToString(build_id_); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); @@ -235,7 +235,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_, const VirtualDir& base_path, bool upper) { - const auto build_id_raw = Common::HexArrayToString(build_id_, upper); + const auto build_id_raw = Common::HexToString(build_id_, upper); const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); @@ -493,6 +493,16 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam return out; } +std::optional<u32> PatchManager::GetGameVersion() const { + const auto& installed = Core::System::GetInstance().GetContentProvider(); + const auto update_tid = GetUpdateTitleID(title_id); + if (installed.HasEntry(update_tid, ContentRecordType::Program)) { + return installed.GetEntryVersion(update_tid); + } + + return installed.GetEntryVersion(title_id); +} + std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { const auto& installed = Core::System::GetInstance().GetContentProvider(); diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 769f8c6f0..a363c6577 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -66,8 +66,13 @@ public: std::map<std::string, std::string, std::less<>> GetPatchVersionNames( VirtualFile update_raw = nullptr) const; - // Given title_id of the program, attempts to get the control data of the update and parse it, - // falling back to the base control data. + // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails, + // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be + // std::nullopt + std::optional<u32> GetGameVersion() const; + + // Given title_id of the program, attempts to get the control data of the update and parse + // it, falling back to the base control data. std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const; // Version of GetControlMetadata that takes an arbitrary NCA diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index d863253f8..eb76174c5 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -51,6 +51,21 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { return Loader::ResultStatus::Success; } +void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, + u8 main_thread_prio, u8 main_thread_core, + u32 main_thread_stack_size, u64 title_id, + u64 filesystem_permissions, + KernelCapabilityDescriptors capabilities) { + npdm_header.has_64_bit_instructions.Assign(is_64_bit); + npdm_header.address_space_type.Assign(address_space); + npdm_header.main_thread_priority = main_thread_prio; + npdm_header.main_thread_cpu = main_thread_core; + npdm_header.main_stack_size = main_thread_stack_size; + aci_header.title_id = title_id; + aci_file_access.permissions = filesystem_permissions; + aci_kernel_capabilities = std ::move(capabilities); +} + bool ProgramMetadata::Is64BitProgram() const { return npdm_header.has_64_bit_instructions; } diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 7de5b9cf9..43bf2820a 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -46,6 +46,11 @@ public: Loader::ResultStatus Load(VirtualFile file); + // Load from parameters instead of NPDM file, used for KIP + void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, u8 main_thread_prio, + u8 main_thread_core, u32 main_thread_stack_size, u64 title_id, + u64 filesystem_permissions, KernelCapabilityDescriptors capabilities); + bool Is64BitProgram() const; ProgramAddressSpaceType GetAddressSpaceType() const; u8 GetMainThreadPriority() const; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 3946ff871..4608490e0 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -53,13 +53,14 @@ static bool FollowsNcaIdFormat(std::string_view name) { static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper, bool within_two_digit) { - if (!within_two_digit) - return fmt::format("/{}.nca", Common::HexArrayToString(nca_id, second_hex_upper)); + if (!within_two_digit) { + return fmt::format("/{}.nca", Common::HexToString(nca_id, second_hex_upper)); + } Core::Crypto::SHA256Hash hash{}; mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); return fmt::format("/000000{:02X}/{}.nca", hash[0], - Common::HexArrayToString(nca_id, second_hex_upper)); + Common::HexToString(nca_id, second_hex_upper)); } static std::string GetCNMTName(TitleType type, u64 title_id) { @@ -376,10 +377,11 @@ std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter( } static std::shared_ptr<NCA> GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) { - const auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false))); - if (file == nullptr) + auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexToString(id, false))); + if (file == nullptr) { return nullptr; - return std::make_shared<NCA>(file); + } + return std::make_shared<NCA>(std::move(file)); } InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_exists, @@ -643,6 +645,20 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnion return out; } +std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry( + u64 title_id, ContentRecordType type) const { + const auto iter = + std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) { + return provider.second != nullptr && provider.second->HasEntry(title_id, type); + }); + + if (iter == providers.end()) { + return std::nullopt; + } + + return iter->first; +} + ManualContentProvider::~ManualContentProvider() = default; void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type, diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index ec9052653..4398d63e1 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -199,6 +199,9 @@ public: std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {}, std::optional<u64> title_id = {}) const; + std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id, + ContentRecordType type) const; + private: std::map<ContentProviderUnionSlot, ContentProvider*> providers; }; diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index c69caae0f..d0428a457 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -235,16 +235,18 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { const auto section0 = nca->GetSubdirectories()[0]; for (const auto& inner_file : section0->GetFiles()) { - if (inner_file->GetExtension() != "cnmt") + if (inner_file->GetExtension() != "cnmt") { continue; + } const CNMT cnmt(inner_file); auto& ncas_title = ncas[cnmt.GetTitleID()]; ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca; for (const auto& rec : cnmt.GetContentRecords()) { - const auto id_string = Common::HexArrayToString(rec.nca_id, false); - const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); + const auto id_string = Common::HexToString(rec.nca_id, false); + auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); + if (next_file == nullptr) { LOG_WARNING(Service_FS, "NCA with ID {}.nca is listed in content metadata, but cannot " @@ -253,9 +255,10 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { continue; } - auto next_nca = std::make_shared<NCA>(next_file, nullptr, 0, keys); - if (next_nca->GetType() == NCAContentType::Program) + auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0, keys); + if (next_nca->GetType() == NCAContentType::Program) { program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); + } if (next_nca->GetStatus() == Loader::ResultStatus::Success || (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && (cnmt.GetTitleID() & 0x800) != 0)) { diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index eec51c64e..4bc5cb2ee 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -66,7 +66,7 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) Core::Crypto::SHA256Hash hash{}; mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], - Common::HexArrayToString(nca_id, false))); + Common::HexToString(nca_id, false))); } NAX::~NAX() = default; diff --git a/src/core/frontend/applets/general_frontend.cpp b/src/core/frontend/applets/general_frontend.cpp index b974f2289..c30b36de7 100644 --- a/src/core/frontend/applets/general_frontend.cpp +++ b/src/core/frontend/applets/general_frontend.cpp @@ -7,9 +7,38 @@ namespace Core::Frontend { +ParentalControlsApplet::~ParentalControlsApplet() = default; + +DefaultParentalControlsApplet::~DefaultParentalControlsApplet() = default; + +void DefaultParentalControlsApplet::VerifyPIN(std::function<void(bool)> finished, + bool suspend_future_verification_temporarily) { + LOG_INFO(Service_AM, + "Application requested frontend to verify PIN (normal), " + "suspend_future_verification_temporarily={}, verifying as correct.", + suspend_future_verification_temporarily); + finished(true); +} + +void DefaultParentalControlsApplet::VerifyPINForSettings(std::function<void(bool)> finished) { + LOG_INFO(Service_AM, + "Application requested frontend to verify PIN (settings), verifying as correct."); + finished(true); +} + +void DefaultParentalControlsApplet::RegisterPIN(std::function<void()> finished) { + LOG_INFO(Service_AM, "Application requested frontend to register new PIN"); + finished(); +} + +void DefaultParentalControlsApplet::ChangePIN(std::function<void()> finished) { + LOG_INFO(Service_AM, "Application requested frontend to change PIN to new value"); + finished(); +} + PhotoViewerApplet::~PhotoViewerApplet() = default; -DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() {} +DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() = default; void DefaultPhotoViewerApplet::ShowPhotosForApplication(u64 title_id, std::function<void()> finished) const { @@ -24,4 +53,72 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con finished(); } +ECommerceApplet::~ECommerceApplet() = default; + +DefaultECommerceApplet::~DefaultECommerceApplet() = default; + +void DefaultECommerceApplet::ShowApplicationInformation( + std::function<void()> finished, u64 title_id, std::optional<u128> user_id, + std::optional<bool> full_display, std::optional<std::string> extra_parameter) { + const auto value = user_id.value_or(u128{}); + LOG_INFO(Service_AM, + "Application requested frontend show application information for EShop, " + "title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}", + title_id, value[1], value[0], + full_display.has_value() ? fmt::format("{}", *full_display) : "null", + extra_parameter.value_or("null")); + finished(); +} + +void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id, + std::optional<bool> full_display) { + const auto value = user_id.value_or(u128{}); + LOG_INFO(Service_AM, + "Application requested frontend show add on content list for EShop, " + "title_id={:016X}, user_id={:016X}{:016X}, full_display={}", + title_id, value[1], value[0], + full_display.has_value() ? fmt::format("{}", *full_display) : "null"); + finished(); +} + +void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id) { + const auto value = user_id.value_or(u128{}); + LOG_INFO(Service_AM, + "Application requested frontend show subscription list for EShop, title_id={:016X}, " + "user_id={:016X}{:016X}", + title_id, value[1], value[0]); + finished(); +} + +void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id) { + const auto value = user_id.value_or(u128{}); + LOG_INFO( + Service_AM, + "Application requested frontend show consumable item list for EShop, title_id={:016X}, " + "user_id={:016X}{:016X}", + title_id, value[1], value[0]); + finished(); +} + +void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id, + bool full_display) { + LOG_INFO(Service_AM, + "Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, " + "full_display={}", + user_id[1], user_id[0], full_display); + finished(); +} + +void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id, + bool full_display) { + LOG_INFO(Service_AM, + "Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, " + "full_display={}", + user_id[1], user_id[0], full_display); + finished(); +} + } // namespace Core::Frontend diff --git a/src/core/frontend/applets/general_frontend.h b/src/core/frontend/applets/general_frontend.h index d4506c999..4b63f828e 100644 --- a/src/core/frontend/applets/general_frontend.h +++ b/src/core/frontend/applets/general_frontend.h @@ -5,10 +5,43 @@ #pragma once #include <functional> +#include <optional> #include "common/common_types.h" namespace Core::Frontend { +class ParentalControlsApplet { +public: + virtual ~ParentalControlsApplet(); + + // Prompts the user to enter a PIN and calls the callback with whether or not it matches the + // correct PIN. If the bool is passed, and the PIN was recently entered correctly, the frontend + // should not prompt and simply return true. + virtual void VerifyPIN(std::function<void(bool)> finished, + bool suspend_future_verification_temporarily) = 0; + + // Prompts the user to enter a PIN and calls the callback for correctness. Frontends can + // optionally alert the user that this is to change parental controls settings. + virtual void VerifyPINForSettings(std::function<void(bool)> finished) = 0; + + // Prompts the user to create a new PIN for pctl and stores it with the service. + virtual void RegisterPIN(std::function<void()> finished) = 0; + + // Prompts the user to verify the current PIN and then store a new one into pctl. + virtual void ChangePIN(std::function<void()> finished) = 0; +}; + +class DefaultParentalControlsApplet final : public ParentalControlsApplet { +public: + ~DefaultParentalControlsApplet() override; + + void VerifyPIN(std::function<void(bool)> finished, + bool suspend_future_verification_temporarily) override; + void VerifyPINForSettings(std::function<void(bool)> finished) override; + void RegisterPIN(std::function<void()> finished) override; + void ChangePIN(std::function<void()> finished) override; +}; + class PhotoViewerApplet { public: virtual ~PhotoViewerApplet(); @@ -25,4 +58,55 @@ public: void ShowAllPhotos(std::function<void()> finished) const override; }; +class ECommerceApplet { +public: + virtual ~ECommerceApplet(); + + // Shows a page with application icons, description, name, and price. + virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id = {}, + std::optional<bool> full_display = {}, + std::optional<std::string> extra_parameter = {}) = 0; + + // Shows a page with all of the add on content available for a game, with name, description, and + // price. + virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id = {}, + std::optional<bool> full_display = {}) = 0; + + // Shows a page with all of the subscriptions (recurring payments) for a game, with name, + // description, price, and renewal period. + virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id = {}) = 0; + + // Shows a page with a list of any additional game related purchasable items (DLC, + // subscriptions, etc) for a particular game, with name, description, type, and price. + virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id = {}) = 0; + + // Shows the home page of the shop. + virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0; + + // Shows the user settings page of the shop. + virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0; +}; + +class DefaultECommerceApplet : public ECommerceApplet { +public: + ~DefaultECommerceApplet() override; + + void ShowApplicationInformation(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id, std::optional<bool> full_display, + std::optional<std::string> extra_parameter) override; + void ShowAddOnContentList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id, + std::optional<bool> full_display) override; + void ShowSubscriptionList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id) override; + void ShowConsumableItemList(std::function<void()> finished, u64 title_id, + std::optional<u128> user_id) override; + void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override; + void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override; +}; + } // namespace Core::Frontend diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp index 3a3d3d0bf..528295ffc 100644 --- a/src/core/frontend/applets/web_browser.cpp +++ b/src/core/frontend/applets/web_browser.cpp @@ -11,9 +11,9 @@ WebBrowserApplet::~WebBrowserApplet() = default; DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default; -void DefaultWebBrowserApplet::OpenPage(std::string_view filename, - std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) { +void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename, + std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) { LOG_INFO(Service_AM, "(STUBBED) called - No suitable web browser implementation found to open website page " "at '{}'!", diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h index f952856af..110e33bc4 100644 --- a/src/core/frontend/applets/web_browser.h +++ b/src/core/frontend/applets/web_browser.h @@ -13,16 +13,16 @@ class WebBrowserApplet { public: virtual ~WebBrowserApplet(); - virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) = 0; + virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) = 0; }; class DefaultWebBrowserApplet final : public WebBrowserApplet { public: ~DefaultWebBrowserApplet() override; - void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) override; + void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) override; }; } // namespace Core::Frontend diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 757e5f21f..799e5e0d8 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -99,7 +99,8 @@ struct KernelCore::Impl { void Shutdown() { next_object_id = 0; - next_process_id = Process::ProcessIDMin; + next_kernel_process_id = Process::InitialKIPIDMin; + next_user_process_id = Process::ProcessIDMin; next_thread_id = 1; process_list.clear(); @@ -132,7 +133,8 @@ struct KernelCore::Impl { } std::atomic<u32> next_object_id{0}; - std::atomic<u64> next_process_id{Process::ProcessIDMin}; + std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin}; + std::atomic<u64> next_user_process_id{Process::ProcessIDMin}; std::atomic<u64> next_thread_id{1}; // Lists all processes that exist in the current session. @@ -226,8 +228,12 @@ u64 KernelCore::CreateNewThreadID() { return impl->next_thread_id++; } -u64 KernelCore::CreateNewProcessID() { - return impl->next_process_id++; +u64 KernelCore::CreateNewKernelProcessID() { + return impl->next_kernel_process_id++; +} + +u64 KernelCore::CreateNewUserProcessID() { + return impl->next_user_process_id++; } Core::Timing::EventType* KernelCore::ThreadWakeupCallbackEventType() const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 6b8738599..0cc44ee76 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -96,7 +96,10 @@ private: u32 CreateNewObjectID(); /// Creates a new process ID, incrementing the internal process ID counter; - u64 CreateNewProcessID(); + u64 CreateNewKernelProcessID(); + + /// Creates a new process ID, incrementing the internal process ID counter; + u64 CreateNewUserProcessID(); /// Creates a new thread ID, incrementing the internal thread ID counter. u64 CreateNewThreadID(); diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 2b81a8d4f..7cfc513a1 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -48,7 +48,8 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) { } } // Anonymous namespace -SharedPtr<Process> Process::Create(Core::System& system, std::string name) { +SharedPtr<Process> Process::Create(Core::System& system, std::string name, + Process::ProcessType type) { auto& kernel = system.Kernel(); SharedPtr<Process> process(new Process(system)); @@ -56,7 +57,8 @@ SharedPtr<Process> Process::Create(Core::System& system, std::string name) { process->resource_limit = kernel.GetSystemResourceLimit(); process->status = ProcessStatus::Created; process->program_id = 0; - process->process_id = kernel.CreateNewProcessID(); + process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID() + : kernel.CreateNewUserProcessID(); process->capabilities.InitializeForMetadatalessProcess(); std::mt19937 rng(Settings::values.rng_seed.value_or(0)); diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 29e016983..248fd3840 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -73,9 +73,15 @@ public: ProcessIDMax = 0xFFFFFFFFFFFFFFFF, }; + // Used to determine how process IDs are assigned. + enum class ProcessType { + KernelInternal, + Userland, + }; + static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4; - static SharedPtr<Process> Create(Core::System& system, std::string name); + static SharedPtr<Process> Create(Core::System& system, std::string name, ProcessType type); std::string GetTypeName() const override { return "Process"; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index f9c606bc5..de6363ff2 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -38,6 +38,7 @@ #include "core/hle/result.h" #include "core/hle/service/service.h" #include "core/memory.h" +#include "core/reporter.h" namespace Kernel { namespace { @@ -594,6 +595,7 @@ struct BreakReason { static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { BreakReason break_reason{reason}; bool has_dumped_buffer{}; + std::vector<u8> debug_buffer; const auto handle_debug_buffer = [&](VAddr addr, u64 sz) { if (sz == 0 || addr == 0 || has_dumped_buffer) { @@ -605,7 +607,7 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", Memory::Read32(addr)); } else { // We don't know what's in here so we'll hexdump it - std::vector<u8> debug_buffer(sz); + debug_buffer.resize(sz); Memory::ReadBlock(addr, debug_buffer.data(), sz); std::string hexdump; for (std::size_t i = 0; i < debug_buffer.size(); i++) { @@ -664,6 +666,10 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { break; } + system.GetReporter().SaveSvcBreakReport( + static_cast<u32>(break_reason.break_type.Value()), break_reason.signal_debugger, info1, + info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); + if (!break_reason.signal_debugger) { LOG_CRITICAL( Debug_Emulated, diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 6d6980aba..c929c2a52 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -68,9 +68,7 @@ VMManager::VMManager(Core::System& system) : system{system} { Reset(FileSys::ProgramAddressSpaceType::Is39Bit); } -VMManager::~VMManager() { - Reset(FileSys::ProgramAddressSpaceType::Is39Bit); -} +VMManager::~VMManager() = default; void VMManager::Reset(FileSys::ProgramAddressSpaceType type) { Clear(); diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index cb66e344b..0cd8158df 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -12,13 +12,17 @@ #include "common/swap.h" #include "core/constants.h" #include "core/core_timing.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/acc/acc.h" #include "core/hle/service/acc/acc_aa.h" #include "core/hle/service/acc/acc_su.h" #include "core/hle/service/acc/acc_u0.h" #include "core/hle/service/acc/acc_u1.h" #include "core/hle/service/acc/profile_manager.h" +#include "core/loader/loader.h" namespace Service::Account { @@ -90,7 +94,7 @@ private: LOG_WARNING(Service_ACC, "Failed to load user provided image! Falling back to built-in backup..."); ctx.WriteBuffer(Core::Constants::ACCOUNT_BACKUP_JPEG); - rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()); + rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); return; } @@ -112,9 +116,9 @@ private: if (!image.IsOpen()) { LOG_WARNING(Service_ACC, "Failed to load user provided image! Falling back to built-in backup..."); - rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()); + rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); } else { - rb.Push<u32>(SanitizeJPEGSize(image.GetSize())); + rb.Push(SanitizeJPEGSize(image.GetSize())); } } @@ -213,7 +217,7 @@ void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestCon rb.Push(profile_manager->CanSystemRegisterUser()); } -void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) { +void Module::Interface::InitializeApplicationInfoOld(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_ACC, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -226,6 +230,31 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo rb.PushIpcInterface<IManagerForApplication>(); } +void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + FileSys::NACP nacp; + const auto res = system.GetAppLoader().ReadControlData(nacp); + + bool is_locked = false; + + if (res != Loader::ResultStatus::Success) { + FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; + auto nacp_unique = pm.GetControlMetadata().first; + + if (nacp_unique != nullptr) { + is_locked = nacp_unique->GetUserAccountSwitchLock(); + } else { + LOG_ERROR(Service_ACC, "nacp_unique is null!"); + } + } else { + is_locked = nacp.GetUserAccountSwitchLock(); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(is_locked); +} + void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ACC, "called"); // A u8 is passed into this function which we can safely ignore. It's to determine if we have @@ -251,19 +280,25 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex } Module::Interface::Interface(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager, const char* name) + std::shared_ptr<ProfileManager> profile_manager, Core::System& system, + const char* name) : ServiceFramework(name), module(std::move(module)), - profile_manager(std::move(profile_manager)) {} + profile_manager(std::move(profile_manager)), system(system) {} Module::Interface::~Interface() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(Core::System& system) { auto module = std::make_shared<Module>(); auto profile_manager = std::make_shared<ProfileManager>(); - std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager); - std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager); - std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager); - std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager); + + std::make_shared<ACC_AA>(module, profile_manager, system) + ->InstallAsService(system.ServiceManager()); + std::make_shared<ACC_SU>(module, profile_manager, system) + ->InstallAsService(system.ServiceManager()); + std::make_shared<ACC_U0>(module, profile_manager, system) + ->InstallAsService(system.ServiceManager()); + std::make_shared<ACC_U1>(module, profile_manager, system) + ->InstallAsService(system.ServiceManager()); } } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 89b2104fa..350f123a0 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -15,7 +15,8 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager, const char* name); + std::shared_ptr<ProfileManager> profile_manager, Core::System& system, + const char* name); ~Interface() override; void GetUserCount(Kernel::HLERequestContext& ctx); @@ -24,18 +25,20 @@ public: void ListOpenUsers(Kernel::HLERequestContext& ctx); void GetLastOpenedUser(Kernel::HLERequestContext& ctx); void GetProfile(Kernel::HLERequestContext& ctx); - void InitializeApplicationInfo(Kernel::HLERequestContext& ctx); + void InitializeApplicationInfoOld(Kernel::HLERequestContext& ctx); void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx); void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx); void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx); + void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> module; std::shared_ptr<ProfileManager> profile_manager; + Core::System& system; }; }; /// Registers all ACC services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp index e84d9f7cf..3bac6bcd1 100644 --- a/src/core/hle/service/acc/acc_aa.cpp +++ b/src/core/hle/service/acc/acc_aa.cpp @@ -6,8 +6,9 @@ namespace Service::Account { -ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) - : Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") { +ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system) + : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:aa") { static const FunctionInfo functions[] = { {0, nullptr, "EnsureCacheAsync"}, {1, nullptr, "LoadCache"}, diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h index 9edb0421b..932c04890 100644 --- a/src/core/hle/service/acc/acc_aa.h +++ b/src/core/hle/service/acc/acc_aa.h @@ -10,8 +10,8 @@ namespace Service::Account { class ACC_AA final : public Module::Interface { public: - explicit ACC_AA(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager); + explicit ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system); ~ACC_AA() override; }; diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index d66233cad..1b7ec3ed0 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -6,8 +6,9 @@ namespace Service::Account { -ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) - : Module::Interface(std::move(module), std::move(profile_manager), "acc:su") { +ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system) + : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:su") { // clang-format off static const FunctionInfo functions[] = { {0, &ACC_SU::GetUserCount, "GetUserCount"}, diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h index fcced063a..0a700d9bf 100644 --- a/src/core/hle/service/acc/acc_su.h +++ b/src/core/hle/service/acc/acc_su.h @@ -10,8 +10,8 @@ namespace Service::Account { class ACC_SU final : public Module::Interface { public: - explicit ACC_SU(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager); + explicit ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system); ~ACC_SU() override; }; diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index 182f7c7e5..2f239e8c0 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -6,8 +6,9 @@ namespace Service::Account { -ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) - : Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") { +ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system) + : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u0") { // clang-format off static const FunctionInfo functions[] = { {0, &ACC_U0::GetUserCount, "GetUserCount"}, @@ -21,7 +22,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {99, nullptr, "DebugActivateOpenContextRetention"}, - {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, + {100, &ACC_U0::InitializeApplicationInfoOld, "InitializeApplicationInfoOld"}, {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, {102, nullptr, "AuthenticateApplicationAsync"}, {103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, @@ -32,7 +33,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {131, nullptr, "ListOpenContextStoredUsers"}, {140, nullptr, "InitializeApplicationInfo"}, {141, nullptr, "ListQualifiedUsers"}, - {150, nullptr, "IsUserAccountSwitchLocked"}, + {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, }; // clang-format on diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h index a1290e0bd..3bd9c3164 100644 --- a/src/core/hle/service/acc/acc_u0.h +++ b/src/core/hle/service/acc/acc_u0.h @@ -10,8 +10,8 @@ namespace Service::Account { class ACC_U0 final : public Module::Interface { public: - explicit ACC_U0(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager); + explicit ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system); ~ACC_U0() override; }; diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index 2dd17d935..6520b3968 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -6,8 +6,9 @@ namespace Service::Account { -ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) - : Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") { +ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system) + : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u1") { // clang-format off static const FunctionInfo functions[] = { {0, &ACC_U1::GetUserCount, "GetUserCount"}, diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h index 9e79daee3..829f8a744 100644 --- a/src/core/hle/service/acc/acc_u1.h +++ b/src/core/hle/service/acc/acc_u1.h @@ -10,8 +10,8 @@ namespace Service::Account { class ACC_U1 final : public Module::Interface { public: - explicit ACC_U1(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager); + explicit ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system); ~ACC_U1() override; }; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3f201c821..33cebb48b 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -271,7 +271,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger {71, nullptr, "GetCurrentIlluminanceEx"}, {80, nullptr, "SetWirelessPriorityMode"}, {90, nullptr, "GetAccumulatedSuspendedTickValue"}, - {91, nullptr, "GetAccumulatedSuspendedTickChangedEvent"}, + {91, &ISelfController::GetAccumulatedSuspendedTickChangedEvent, "GetAccumulatedSuspendedTickChangedEvent"}, {100, nullptr, "SetAlbumImageTakenNotificationEnabled"}, {1000, nullptr, "GetDebugStorageChannel"}, }; @@ -282,6 +282,11 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger auto& kernel = Core::System::GetInstance().Kernel(); launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, "ISelfController:LaunchableEvent"); + + // TODO(ogniK): Figure out where, when and why this event gets signalled + accumulated_suspended_tick_changed_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Manual, "ISelfController:AccumulatedSuspendedTickChangedEvent"); + accumulated_suspended_tick_changed_event.writable->Signal(); // Is signalled on creation } ISelfController::~ISelfController() = default; @@ -444,6 +449,17 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c rb.Push<u32>(idle_time_detection_extension); } +void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx) { + // The implementation of this function is fine as is, the reason we're labelling it as stubbed + // is because we're currently unsure when and where accumulated_suspended_tick_changed_event is + // actually signalled for the time being. + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable); +} + AppletMessageQueue::AppletMessageQueue() { auto& kernel = Core::System::GetInstance().Kernel(); on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, @@ -871,7 +887,9 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") { +ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id) + : ServiceFramework("ILibraryAppletCreator"), + current_process_title_id(current_process_title_id) { static const FunctionInfo functions[] = { {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, {1, nullptr, "TerminateAllLibraryApplets"}, @@ -894,7 +912,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) static_cast<u32>(applet_id), applet_mode); const auto& applet_manager{Core::System::GetInstance().GetAppletManager()}; - const auto applet = applet_manager.GetApplet(applet_id); + const auto applet = applet_manager.GetApplet(applet_id, current_process_title_id); if (applet == nullptr) { LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id)); @@ -1218,13 +1236,13 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { } void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr<NVFlinger::NVFlinger> nvflinger) { + std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { auto message_queue = std::make_shared<AppletMessageQueue>(); message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on // game boot - std::make_shared<AppletAE>(nvflinger, message_queue)->InstallAsService(service_manager); - std::make_shared<AppletOE>(nvflinger, message_queue)->InstallAsService(service_manager); + std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager); + std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager); std::make_shared<IdleSys>()->InstallAsService(service_manager); std::make_shared<OMM>()->InstallAsService(service_manager); std::make_shared<SPSM>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 991b7d47c..4ea609d23 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -133,9 +133,12 @@ private: void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); + void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx); std::shared_ptr<NVFlinger::NVFlinger> nvflinger; Kernel::EventPair launchable_event; + Kernel::EventPair accumulated_suspended_tick_changed_event; + u32 idle_time_detection_extension = 0; u64 num_fatal_sections_entered = 0; }; @@ -198,13 +201,15 @@ private: class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { public: - ILibraryAppletCreator(); + ILibraryAppletCreator(u64 current_process_title_id); ~ILibraryAppletCreator() override; private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); + + u64 current_process_title_id; }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { @@ -261,7 +266,7 @@ public: /// Registers all AM services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr<NVFlinger::NVFlinger> nvflinger); + std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system); } // namespace AM } // namespace Service diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index 488add8e7..fe5beb8f9 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -13,9 +14,10 @@ namespace Service::AM { class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> { public: explicit ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue) + std::shared_ptr<AppletMessageQueue> msg_queue, + Core::System& system) : ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)) { + msg_queue(std::move(msg_queue)), system(system) { // clang-format off static const FunctionInfo functions[] = { {0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -96,7 +98,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(); + rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { @@ -109,14 +111,15 @@ private: std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; + Core::System& system; }; class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> { public: explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue) + std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system) : ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)) { + msg_queue(std::move(msg_queue)), system(system) { // clang-format off static const FunctionInfo functions[] = { {0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -191,7 +194,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(); + rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); } void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) { @@ -219,6 +222,7 @@ private: } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; + Core::System& system; }; void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) { @@ -226,7 +230,7 @@ void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue); + rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue, system); } void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) { @@ -234,7 +238,7 @@ void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue); + rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system); } void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) { @@ -242,13 +246,13 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue); + rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system); } AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue) + std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system) : ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)) { + msg_queue(std::move(msg_queue)), system(system) { // clang-format off static const FunctionInfo functions[] = { {100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"}, diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h index 902db2665..9e006cd9d 100644 --- a/src/core/hle/service/am/applet_ae.h +++ b/src/core/hle/service/am/applet_ae.h @@ -18,7 +18,7 @@ namespace AM { class AppletAE final : public ServiceFramework<AppletAE> { public: explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue); + std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system); ~AppletAE() override; const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const; @@ -30,6 +30,7 @@ private: std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; + Core::System& system; }; } // namespace AM diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index d3a0a1568..6e255fe95 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -13,9 +14,9 @@ namespace Service::AM { class IApplicationProxy final : public ServiceFramework<IApplicationProxy> { public: explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue) + std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system) : ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)) { + msg_queue(std::move(msg_queue)), system(system) { // clang-format off static const FunctionInfo functions[] = { {0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -87,7 +88,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(); + rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { @@ -100,6 +101,7 @@ private: std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; + Core::System& system; }; void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { @@ -107,13 +109,13 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue); + rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue, system); } AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue) + std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system) : ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)) { + msg_queue(std::move(msg_queue)), system(system) { static const FunctionInfo functions[] = { {0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"}, }; diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h index bbd0108ef..22c05419d 100644 --- a/src/core/hle/service/am/applet_oe.h +++ b/src/core/hle/service/am/applet_oe.h @@ -18,7 +18,7 @@ namespace AM { class AppletOE final : public ServiceFramework<AppletOE> { public: explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue); + std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system); ~AppletOE() override; const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const; @@ -28,6 +28,7 @@ private: std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; + Core::System& system; }; } // namespace AM diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 14fa92318..6bdba2468 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -35,12 +35,28 @@ AppletDataBroker::AppletDataBroker() { AppletDataBroker::~AppletDataBroker() = default; +AppletDataBroker::RawChannelData AppletDataBroker::PeekDataToAppletForDebug() const { + std::vector<std::vector<u8>> out_normal; + + for (const auto& storage : in_channel) { + out_normal.push_back(storage->GetData()); + } + + std::vector<std::vector<u8>> out_interactive; + + for (const auto& storage : in_interactive_channel) { + out_interactive.push_back(storage->GetData()); + } + + return {std::move(out_normal), std::move(out_interactive)}; +} + std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() { if (out_channel.empty()) return nullptr; auto out = std::move(out_channel.front()); - out_channel.pop(); + out_channel.pop_front(); return out; } @@ -49,7 +65,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() { return nullptr; auto out = std::move(in_channel.front()); - in_channel.pop(); + in_channel.pop_front(); return out; } @@ -58,7 +74,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() { return nullptr; auto out = std::move(out_interactive_channel.front()); - out_interactive_channel.pop(); + out_interactive_channel.pop_front(); return out; } @@ -67,25 +83,25 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() { return nullptr; auto out = std::move(in_interactive_channel.front()); - in_interactive_channel.pop(); + in_interactive_channel.pop_front(); return out; } void AppletDataBroker::PushNormalDataFromGame(IStorage storage) { - in_channel.push(std::make_unique<IStorage>(storage)); + in_channel.push_back(std::make_unique<IStorage>(storage)); } void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) { - out_channel.push(std::make_unique<IStorage>(storage)); + out_channel.push_back(std::make_unique<IStorage>(storage)); pop_out_data_event.writable->Signal(); } void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) { - in_interactive_channel.push(std::make_unique<IStorage>(storage)); + in_interactive_channel.push_back(std::make_unique<IStorage>(storage)); } void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) { - out_interactive_channel.push(std::make_unique<IStorage>(storage)); + out_interactive_channel.push_back(std::make_unique<IStorage>(storage)); pop_interactive_out_data_event.writable->Signal(); } @@ -123,12 +139,14 @@ void Applet::Initialize() { AppletFrontendSet::AppletFrontendSet() = default; -AppletFrontendSet::AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer, - ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser) - : error{std::move(error)}, photo_viewer{std::move(photo_viewer)}, profile_select{std::move( - profile_select)}, - software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)} {} +AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, + PhotoViewer photo_viewer, ProfileSelect profile_select, + SoftwareKeyboard software_keyboard, WebBrowser web_browser, + ECommerceApplet e_commerce) + : parental_controls{std::move(parental_controls)}, error{std::move(error)}, + photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)}, + software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)}, + e_commerce{std::move(e_commerce)} {} AppletFrontendSet::~AppletFrontendSet() = default; @@ -141,6 +159,8 @@ AppletManager::AppletManager() = default; AppletManager::~AppletManager() = default; void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { + if (set.parental_controls != nullptr) + frontend.parental_controls = std::move(set.parental_controls); if (set.error != nullptr) frontend.error = std::move(set.error); if (set.photo_viewer != nullptr) @@ -151,17 +171,21 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { frontend.software_keyboard = std::move(set.software_keyboard); if (set.web_browser != nullptr) frontend.web_browser = std::move(set.web_browser); + if (set.e_commerce != nullptr) + frontend.e_commerce = std::move(set.e_commerce); } void AppletManager::SetDefaultAppletFrontendSet() { - frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>(); - frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>(); - frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>(); - frontend.software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>(); - frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>(); + ClearAll(); + SetDefaultAppletsIfMissing(); } void AppletManager::SetDefaultAppletsIfMissing() { + if (frontend.parental_controls == nullptr) { + frontend.parental_controls = + std::make_unique<Core::Frontend::DefaultParentalControlsApplet>(); + } + if (frontend.error == nullptr) { frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>(); } @@ -182,14 +206,20 @@ void AppletManager::SetDefaultAppletsIfMissing() { if (frontend.web_browser == nullptr) { frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>(); } + + if (frontend.e_commerce == nullptr) { + frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>(); + } } void AppletManager::ClearAll() { frontend = {}; } -std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const { +std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const { switch (id) { + case AppletId::Auth: + return std::make_shared<Auth>(*frontend.parental_controls); case AppletId::Error: return std::make_shared<Error>(*frontend.error); case AppletId::ProfileSelect: @@ -198,13 +228,16 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const { return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard); case AppletId::PhotoViewer: return std::make_shared<PhotoViewer>(*frontend.photo_viewer); + case AppletId::LibAppletShop: + return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id, + frontend.e_commerce.get()); case AppletId::LibAppletOff: - return std::make_shared<WebBrowser>(*frontend.web_browser); + return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id); default: UNIMPLEMENTED_MSG( "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", static_cast<u8>(id)); - return std::make_shared<StubApplet>(); + return std::make_shared<StubApplet>(id); } } diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index b46e10a4a..adc973dad 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -13,7 +13,9 @@ union ResultCode; namespace Core::Frontend { +class ECommerceApplet; class ErrorApplet; +class ParentalControlsApplet; class PhotoViewerApplet; class ProfileSelectApplet; class SoftwareKeyboardApplet; @@ -54,6 +56,14 @@ public: AppletDataBroker(); ~AppletDataBroker(); + struct RawChannelData { + std::vector<std::vector<u8>> normal; + std::vector<std::vector<u8>> interactive; + }; + + // Retrieves but does not pop the data sent to applet. + RawChannelData PeekDataToAppletForDebug() const; + std::unique_ptr<IStorage> PopNormalDataToGame(); std::unique_ptr<IStorage> PopNormalDataToApplet(); @@ -76,16 +86,16 @@ private: // Queues are named from applet's perspective // PopNormalDataToApplet and PushNormalDataFromGame - std::queue<std::unique_ptr<IStorage>> in_channel; + std::deque<std::unique_ptr<IStorage>> in_channel; // PopNormalDataToGame and PushNormalDataFromApplet - std::queue<std::unique_ptr<IStorage>> out_channel; + std::deque<std::unique_ptr<IStorage>> out_channel; // PopInteractiveDataToApplet and PushInteractiveDataFromGame - std::queue<std::unique_ptr<IStorage>> in_interactive_channel; + std::deque<std::unique_ptr<IStorage>> in_interactive_channel; // PopInteractiveDataToGame and PushInteractiveDataFromApplet - std::queue<std::unique_ptr<IStorage>> out_interactive_channel; + std::deque<std::unique_ptr<IStorage>> out_interactive_channel; Kernel::EventPair state_changed_event; @@ -137,15 +147,19 @@ protected: }; struct AppletFrontendSet { + using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>; using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>; using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>; using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>; using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>; using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>; + using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>; AppletFrontendSet(); - AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer, ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser); + AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, + PhotoViewer photo_viewer, ProfileSelect profile_select, + SoftwareKeyboard software_keyboard, WebBrowser web_browser, + ECommerceApplet e_commerce); ~AppletFrontendSet(); AppletFrontendSet(const AppletFrontendSet&) = delete; @@ -154,11 +168,13 @@ struct AppletFrontendSet { AppletFrontendSet(AppletFrontendSet&&) noexcept; AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept; + ParentalControlsApplet parental_controls; ErrorApplet error; PhotoViewer photo_viewer; ProfileSelect profile_select; SoftwareKeyboard software_keyboard; WebBrowser web_browser; + ECommerceApplet e_commerce; }; class AppletManager { @@ -171,7 +187,7 @@ public: void SetDefaultAppletsIfMissing(); void ClearAll(); - std::shared_ptr<Applet> GetApplet(AppletId id) const; + std::shared_ptr<Applet> GetApplet(AppletId id, u64 current_process_title_id) const; private: AppletFrontendSet frontend; diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp index 04774bedc..af3a900f8 100644 --- a/src/core/hle/service/am/applets/error.cpp +++ b/src/core/hle/service/am/applets/error.cpp @@ -9,8 +9,10 @@ #include "common/string_util.h" #include "core/core.h" #include "core/frontend/applets/error.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/error.h" +#include "core/reporter.h" namespace Service::AM::Applets { @@ -143,9 +145,12 @@ void Error::Execute() { } const auto callback = [this] { DisplayCompleted(); }; + const auto title_id = Core::CurrentProcess()->GetTitleID(); + const auto& reporter{Core::System::GetInstance().GetReporter()}; switch (mode) { case ErrorAppletMode::ShowError: + reporter.SaveErrorReport(title_id, error_code); frontend.ShowError(error_code, callback); break; case ErrorAppletMode::ShowSystemError: @@ -156,14 +161,18 @@ void Error::Execute() { const auto& detail_text = system ? args->system_error.detail_text : args->application_error.detail_text; - frontend.ShowCustomErrorText( - error_code, - Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size()), - Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size()), - callback); + const auto main_text_string = + Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size()); + const auto detail_text_string = + Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size()); + + reporter.SaveErrorReport(title_id, error_code, main_text_string, detail_text_string); + frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback); break; } case ErrorAppletMode::ShowErrorRecord: + reporter.SaveErrorReport(title_id, error_code, + fmt::format("{:016X}", args->error_record.posix_time)); frontend.ShowErrorWithTimestamp( error_code, std::chrono::seconds{args->error_record.posix_time}, callback); break; diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp index c591b9ac2..e0def8dff 100644 --- a/src/core/hle/service/am/applets/general_backend.cpp +++ b/src/core/hle/service/am/applets/general_backend.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <string> +#include <string_view> #include "common/assert.h" #include "common/hex_util.h" @@ -13,27 +13,144 @@ #include "core/hle/result.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/general_backend.h" +#include "core/reporter.h" namespace Service::AM::Applets { -static void LogCurrentStorage(AppletDataBroker& broker, std::string prefix) { +constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221}; + +static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) { std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet(); for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) { const auto data = storage->GetData(); LOG_INFO(Service_AM, - "called (STUBBED), during {} recieved normal data with size={:08X}, data={}", - prefix, data.size(), Common::HexVectorToString(data)); + "called (STUBBED), during {} received normal data with size={:08X}, data={}", + prefix, data.size(), Common::HexToString(data)); } storage = broker.PopInteractiveDataToApplet(); for (; storage != nullptr; storage = broker.PopInteractiveDataToApplet()) { const auto data = storage->GetData(); LOG_INFO(Service_AM, - "called (STUBBED), during {} recieved interactive data with size={:08X}, data={}", - prefix, data.size(), Common::HexVectorToString(data)); + "called (STUBBED), during {} received interactive data with size={:08X}, data={}", + prefix, data.size(), Common::HexToString(data)); } } +Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {} + +Auth::~Auth() = default; + +void Auth::Initialize() { + Applet::Initialize(); + complete = false; + + const auto storage = broker.PopNormalDataToApplet(); + ASSERT(storage != nullptr); + const auto data = storage->GetData(); + ASSERT(data.size() >= 0xC); + + struct Arg { + INSERT_PADDING_BYTES(4); + AuthAppletType type; + u8 arg0; + u8 arg1; + u8 arg2; + INSERT_PADDING_BYTES(1); + }; + static_assert(sizeof(Arg) == 0xC, "Arg (AuthApplet) has incorrect size."); + + Arg arg{}; + std::memcpy(&arg, data.data(), sizeof(Arg)); + + type = arg.type; + arg0 = arg.arg0; + arg1 = arg.arg1; + arg2 = arg.arg2; +} + +bool Auth::TransactionComplete() const { + return complete; +} + +ResultCode Auth::GetStatus() const { + return successful ? RESULT_SUCCESS : ERROR_INVALID_PIN; +} + +void Auth::ExecuteInteractive() { + UNREACHABLE_MSG("Unexpected interactive applet data."); +} + +void Auth::Execute() { + if (complete) { + return; + } + + const auto unimplemented_log = [this] { + UNIMPLEMENTED_MSG("Unimplemented Auth applet type for type={:08X}, arg0={:02X}, " + "arg1={:02X}, arg2={:02X}", + static_cast<u32>(type), arg0, arg1, arg2); + }; + + switch (type) { + case AuthAppletType::ShowParentalAuthentication: { + const auto callback = [this](bool successful) { AuthFinished(successful); }; + + if (arg0 == 1 && arg1 == 0 && arg2 == 1) { + // ShowAuthenticatorForConfiguration + frontend.VerifyPINForSettings(callback); + } else if (arg1 == 0 && arg2 == 0) { + // ShowParentalAuthentication(bool) + frontend.VerifyPIN(callback, static_cast<bool>(arg0)); + } else { + unimplemented_log(); + } + break; + } + case AuthAppletType::RegisterParentalPasscode: { + const auto callback = [this] { AuthFinished(true); }; + + if (arg0 == 0 && arg1 == 0 && arg2 == 0) { + // RegisterParentalPasscode + frontend.RegisterPIN(callback); + } else { + unimplemented_log(); + } + break; + } + case AuthAppletType::ChangeParentalPasscode: { + const auto callback = [this] { AuthFinished(true); }; + + if (arg0 == 0 && arg1 == 0 && arg2 == 0) { + // ChangeParentalPasscode + frontend.ChangePIN(callback); + } else { + unimplemented_log(); + } + break; + } + default: + unimplemented_log(); + } +} + +void Auth::AuthFinished(bool successful) { + this->successful = successful; + + struct Return { + ResultCode result_code; + }; + static_assert(sizeof(Return) == 0x4, "Return (AuthApplet) has incorrect size."); + + Return return_{GetStatus()}; + + std::vector<u8> out(sizeof(Return)); + std::memcpy(out.data(), &return_, sizeof(Return)); + + broker.PushNormalDataFromApplet(IStorage{out}); + broker.SignalStateChanged(); +} + PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {} PhotoViewer::~PhotoViewer() = default; @@ -83,13 +200,20 @@ void PhotoViewer::ViewFinished() { broker.SignalStateChanged(); } -StubApplet::StubApplet() = default; +StubApplet::StubApplet(AppletId id) : id(id) {} StubApplet::~StubApplet() = default; void StubApplet::Initialize() { LOG_WARNING(Service_AM, "called (STUBBED)"); Applet::Initialize(); + + const auto data = broker.PeekDataToAppletForDebug(); + Core::System::GetInstance().GetReporter().SaveUnimplementedAppletReport( + static_cast<u32>(id), common_args.arguments_version, common_args.library_version, + common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, + data.normal, data.interactive); + LogCurrentStorage(broker, "Initialize"); } diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h index 2dd255d7c..0da252044 100644 --- a/src/core/hle/service/am/applets/general_backend.h +++ b/src/core/hle/service/am/applets/general_backend.h @@ -8,6 +8,36 @@ namespace Service::AM::Applets { +enum class AuthAppletType : u32 { + ShowParentalAuthentication, + RegisterParentalPasscode, + ChangeParentalPasscode, +}; + +class Auth final : public Applet { +public: + explicit Auth(Core::Frontend::ParentalControlsApplet& frontend); + ~Auth() override; + + void Initialize() override; + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void AuthFinished(bool successful = true); + +private: + Core::Frontend::ParentalControlsApplet& frontend; + bool complete = false; + bool successful = false; + + AuthAppletType type = AuthAppletType::ShowParentalAuthentication; + u8 arg0 = 0; + u8 arg1 = 0; + u8 arg2 = 0; +}; + enum class PhotoViewerAppletMode : u8 { CurrentApp = 0, AllApps = 1, @@ -34,7 +64,7 @@ private: class StubApplet final : public Applet { public: - StubApplet(); + explicit StubApplet(AppletId id); ~StubApplet() override; void Initialize() override; @@ -43,6 +73,9 @@ public: ResultCode GetStatus() const override; void ExecuteInteractive() override; void Execute() override; + +private: + AppletId id; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index 7878f5136..2762e0653 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -19,7 +19,9 @@ #include "core/file_sys/nca_metadata.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/vfs_types.h" +#include "core/frontend/applets/general_frontend.h" #include "core/frontend/applets/web_browser.h" #include "core/hle/kernel/process.h" #include "core/hle/service/am/applets/web_browser.h" @@ -28,74 +30,187 @@ namespace Service::AM::Applets { -// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not -// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant, -// but some may be worth an implementation. -constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6; +enum class WebArgTLVType : u16 { + InitialURL = 0x1, + ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name. + CallbackURL = 0x3, + CallbackableURL = 0x4, + ApplicationID = 0x5, + DocumentPath = 0x6, + DocumentKind = 0x7, + SystemDataID = 0x8, + ShareStartPage = 0x9, + Whitelist = 0xA, + News = 0xB, + UserID = 0xE, + AlbumEntry0 = 0xF, + ScreenShotEnabled = 0x10, + EcClientCertEnabled = 0x11, + Unk12 = 0x12, + PlayReportEnabled = 0x13, + Unk14 = 0x14, + Unk15 = 0x15, + BootDisplayKind = 0x17, + BackgroundKind = 0x18, + FooterEnabled = 0x19, + PointerEnabled = 0x1A, + LeftStickMode = 0x1B, + KeyRepeatFrame1 = 0x1C, + KeyRepeatFrame2 = 0x1D, + BootAsMediaPlayerInv = 0x1E, + DisplayUrlKind = 0x1F, + BootAsMediaPlayer = 0x21, + ShopJumpEnabled = 0x22, + MediaAutoPlayEnabled = 0x23, + LobbyParameter = 0x24, + ApplicationAlbumEntry = 0x26, + JsExtensionEnabled = 0x27, + AdditionalCommentText = 0x28, + TouchEnabledOnContents = 0x29, + UserAgentAdditionalString = 0x2A, + AdditionalMediaData0 = 0x2B, + MediaPlayerAutoCloseEnabled = 0x2C, + PageCacheEnabled = 0x2D, + WebAudioEnabled = 0x2E, + Unk2F = 0x2F, + YouTubeVideoWhitelist = 0x31, + FooterFixedKind = 0x32, + PageFadeEnabled = 0x33, + MediaCreatorApplicationRatingAge = 0x34, + BootLoadingIconEnabled = 0x35, + PageScrollIndicationEnabled = 0x36, + MediaPlayerSpeedControlEnabled = 0x37, + AlbumEntry1 = 0x38, + AlbumEntry2 = 0x39, + AlbumEntry3 = 0x3A, + AdditionalMediaData1 = 0x3B, + AdditionalMediaData2 = 0x3C, + AdditionalMediaData3 = 0x3D, + BootFooterButton = 0x3E, + OverrideWebAudioVolume = 0x3F, + OverrideMediaAudioVolume = 0x40, + BootMode = 0x41, + WebSessionEnabled = 0x42, +}; + +enum class ShimKind : u32 { + Shop = 1, + Login = 2, + Offline = 3, + Share = 4, + Web = 5, + Wifi = 6, + Lobby = 7, +}; + +enum class ShopWebTarget { + ApplicationInfo, + AddOnContentList, + SubscriptionList, + ConsumableItemList, + Home, + Settings, +}; + +namespace { -struct WebBufferHeader { +constexpr std::size_t SHIM_KIND_COUNT = 0x8; + +struct WebArgHeader { u16 count; - INSERT_PADDING_BYTES(6); + INSERT_PADDING_BYTES(2); + ShimKind kind; }; -static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size."); +static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size."); -struct WebArgumentHeader { - u16 type; +struct WebArgTLV { + WebArgTLVType type; u16 size; u32 offset; }; -static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size."); +static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size."); -struct WebArgumentResult { +struct WebCommonReturnValue { u32 result_code; + INSERT_PADDING_BYTES(0x4); std::array<char, 0x1000> last_url; u64 last_url_size; }; -static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size."); - -static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) { - WebBufferHeader header; - ASSERT(sizeof(WebBufferHeader) <= data.size()); - std::memcpy(&header, data.data(), sizeof(WebBufferHeader)); - - u64 offset = sizeof(WebBufferHeader); - for (u16 i = 0; i < header.count; ++i) { - WebArgumentHeader arg; - ASSERT(offset + sizeof(WebArgumentHeader) <= data.size()); - std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader)); - offset += sizeof(WebArgumentHeader); - - if (arg.type == type) { - std::vector<u8> out(arg.size); - offset += arg.offset; - ASSERT(offset + arg.size <= data.size()); - std::memcpy(out.data(), data.data() + offset, out.size()); +static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); + +struct WebWifiPageArg { + INSERT_PADDING_BYTES(4); + std::array<char, 0x100> connection_test_url; + std::array<char, 0x400> initial_url; + std::array<u8, 0x10> nifm_network_uuid; + u32 nifm_requirement; +}; +static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size."); + +struct WebWifiReturnValue { + INSERT_PADDING_BYTES(4); + u32 result; +}; +static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size."); + +enum class OfflineWebSource : u32 { + OfflineHtmlPage = 0x1, + ApplicationLegalInformation = 0x2, + SystemDataPage = 0x3, +}; + +std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) { + if (arg.size() < sizeof(WebArgHeader)) + return {}; + + WebArgHeader header{}; + std::memcpy(&header, arg.data(), sizeof(WebArgHeader)); + + std::map<WebArgTLVType, std::vector<u8>> out; + u64 offset = sizeof(WebArgHeader); + for (std::size_t i = 0; i < header.count; ++i) { + if (arg.size() < (offset + sizeof(WebArgTLV))) return out; - } - offset += arg.offset + arg.size; - } + WebArgTLV tlv{}; + std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV)); + offset += sizeof(WebArgTLV); - return {}; -} + offset += tlv.offset; + if (arg.size() < (offset + tlv.size)) + return out; + + std::vector<u8> data(tlv.size); + std::memcpy(data.data(), arg.data() + offset, tlv.size); + offset += tlv.size; -static FileSys::VirtualFile GetManualRomFS() { - auto& loader{Core::System::GetInstance().GetAppLoader()}; + out.insert_or_assign(tlv.type, data); + } - FileSys::VirtualFile out; - if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success) - return out; + return out; +} +FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) { const auto& installed{Core::System::GetInstance().GetContentProvider()}; - const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(), - FileSys::ContentRecordType::Manual); + const auto res = installed.GetEntry(title_id, type); - if (res != nullptr) + if (res != nullptr) { return res->GetRomFS(); + } + + if (type == FileSys::ContentRecordType::Data) { + return FileSys::SystemArchive::SynthesizeSystemArchive(title_id); + } + return nullptr; } -WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend) : frontend(frontend) {} +} // Anonymous namespace + +WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, + Core::Frontend::ECommerceApplet* frontend_e_commerce) + : frontend(frontend), frontend_e_commerce(frontend_e_commerce), + current_process_title_id(current_process_title_id) {} WebBrowser::~WebBrowser() = default; @@ -111,24 +226,12 @@ void WebBrowser::Initialize() { ASSERT(web_arg_storage != nullptr); const auto& web_arg = web_arg_storage->GetData(); - const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE); - filename = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(url_data.data()), url_data.size()); + ASSERT(web_arg.size() >= 0x8); + std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind)); - temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + - "web_applet_manual", - FileUtil::DirectorySeparator::PlatformDefault); - FileUtil::DeleteDirRecursively(temporary_dir); + args = GetWebArguments(web_arg); - manual_romfs = GetManualRomFS(); - if (manual_romfs == nullptr) { - status = ResultCode(-1); - LOG_ERROR(Service_AM, "Failed to find manual for current process!"); - } - - filename = - FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename, - FileUtil::DirectorySeparator::PlatformDefault); + InitializeInternal(); } bool WebBrowser::TransactionComplete() const { @@ -144,24 +247,25 @@ void WebBrowser::ExecuteInteractive() { } void WebBrowser::Execute() { - if (complete) + if (complete) { return; + } if (status != RESULT_SUCCESS) { complete = true; return; } - frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); + ExecuteInternal(); } void WebBrowser::UnpackRomFS() { if (unpacked) return; - ASSERT(manual_romfs != nullptr); + ASSERT(offline_romfs != nullptr); const auto dir = - FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard); + FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); const auto& vfs{Core::System::GetInstance().GetFilesystem()}; const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); FileSys::VfsRawCopyD(dir, temp_dir); @@ -172,17 +276,275 @@ void WebBrowser::UnpackRomFS() { void WebBrowser::Finalize() { complete = true; - WebArgumentResult out{}; + WebCommonReturnValue out{}; out.result_code = 0; out.last_url_size = 0; - std::vector<u8> data(sizeof(WebArgumentResult)); - std::memcpy(data.data(), &out, sizeof(WebArgumentResult)); + std::vector<u8> data(sizeof(WebCommonReturnValue)); + std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue)); broker.PushNormalDataFromApplet(IStorage{data}); broker.SignalStateChanged(); + if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) { + FileUtil::DeleteDirRecursively(temporary_dir); + } +} + +void WebBrowser::InitializeInternal() { + using WebAppletInitializer = void (WebBrowser::*)(); + + constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{ + nullptr, &WebBrowser::InitializeShop, + nullptr, &WebBrowser::InitializeOffline, + nullptr, nullptr, + nullptr, nullptr, + }; + + const auto index = static_cast<u32>(kind); + + if (index > functions.size() || functions[index] == nullptr) { + LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index); + return; + } + + const auto function = functions[index]; + (this->*function)(); +} + +void WebBrowser::ExecuteInternal() { + using WebAppletExecutor = void (WebBrowser::*)(); + + constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{ + nullptr, &WebBrowser::ExecuteShop, + nullptr, &WebBrowser::ExecuteOffline, + nullptr, nullptr, + nullptr, nullptr, + }; + + const auto index = static_cast<u32>(kind); + + if (index > functions.size() || functions[index] == nullptr) { + LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index); + return; + } + + const auto function = functions[index]; + (this->*function)(); +} + +void WebBrowser::InitializeShop() { + if (frontend_e_commerce == nullptr) { + LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!"); + status = ResultCode(-1); + return; + } + + const auto user_id_data = args.find(WebArgTLVType::UserID); + + user_id = std::nullopt; + if (user_id_data != args.end()) { + user_id = u128{}; + std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128)); + } + + const auto url = args.find(WebArgTLVType::ShopArgumentsURL); + + if (url == args.end()) { + LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!"); + status = ResultCode(-1); + return; + } + + std::vector<std::string> split_query; + Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(url->second.data()), url->second.size()), + '?', split_query); + + // 2 -> Main URL '?' Query Parameters + // Less is missing info, More is malformed + if (split_query.size() != 2) { + LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed"); + status = ResultCode(-1); + return; + } + + std::vector<std::string> queries; + Common::SplitString(split_query[1], '&', queries); + + const auto split_single_query = + [](const std::string& in) -> std::pair<std::string, std::string> { + const auto index = in.find('='); + if (index == std::string::npos || index == in.size() - 1) { + return {in, ""}; + } + + return {in.substr(0, index), in.substr(index + 1)}; + }; + + std::transform(queries.begin(), queries.end(), + std::inserter(shop_query, std::next(shop_query.begin())), split_single_query); + + const auto scene = shop_query.find("scene"); + + if (scene == shop_query.end()) { + LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!"); + status = ResultCode(-1); + return; + } + + const std::map<std::string, ShopWebTarget, std::less<>> target_map{ + {"product_detail", ShopWebTarget::ApplicationInfo}, + {"aocs", ShopWebTarget::AddOnContentList}, + {"subscriptions", ShopWebTarget::SubscriptionList}, + {"consumption", ShopWebTarget::ConsumableItemList}, + {"settings", ShopWebTarget::Settings}, + {"top", ShopWebTarget::Home}, + }; + + const auto target = target_map.find(scene->second); + if (target == target_map.end()) { + LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second); + status = ResultCode(-1); + return; + } + + shop_web_target = target->second; + + const auto title_id_data = shop_query.find("dst_app_id"); + if (title_id_data != shop_query.end()) { + title_id = std::stoull(title_id_data->second, nullptr, 0x10); + } + + const auto mode_data = shop_query.find("mode"); + if (mode_data != shop_query.end()) { + shop_full_display = mode_data->second == "full"; + } +} + +void WebBrowser::InitializeOffline() { + if (args.find(WebArgTLVType::DocumentPath) == args.end() || + args.find(WebArgTLVType::DocumentKind) == args.end() || + args.find(WebArgTLVType::ApplicationID) == args.end()) { + status = ResultCode(-1); + LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!"); + } + + const auto url_data = args[WebArgTLVType::DocumentPath]; + filename = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(url_data.data()), url_data.size()); + + OfflineWebSource source; + ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4); + std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource)); + + constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{ + "manual", + "legal", + "system", + }; + + temporary_dir = + FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" + + WEB_SOURCE_NAMES[static_cast<u32>(source) - 1], + FileUtil::DirectorySeparator::PlatformDefault); FileUtil::DeleteDirRecursively(temporary_dir); + + u64 title_id = 0; // 0 corresponds to current process + ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8); + std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64)); + FileSys::ContentRecordType type = FileSys::ContentRecordType::Data; + + switch (source) { + case OfflineWebSource::OfflineHtmlPage: + // While there is an AppID TLV field, in official SW this is always ignored. + title_id = 0; + type = FileSys::ContentRecordType::Manual; + break; + case OfflineWebSource::ApplicationLegalInformation: + type = FileSys::ContentRecordType::Legal; + break; + case OfflineWebSource::SystemDataPage: + type = FileSys::ContentRecordType::Data; + break; + } + + if (title_id == 0) { + title_id = current_process_title_id; + } + + offline_romfs = GetApplicationRomFS(title_id, type); + if (offline_romfs == nullptr) { + status = ResultCode(-1); + LOG_ERROR(Service_AM, "Failed to find offline data for request!"); + } + + std::string path_additional_directory; + if (source == OfflineWebSource::OfflineHtmlPage) { + path_additional_directory = std::string(DIR_SEP).append("html-document"); + } + + filename = + FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename, + FileUtil::DirectorySeparator::PlatformDefault); +} + +void WebBrowser::ExecuteShop() { + const auto callback = [this]() { Finalize(); }; + + const auto check_optional_parameter = [this](const auto& p) { + if (!p.has_value()) { + LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!"); + status = ResultCode(-1); + return false; + } + + return true; + }; + + switch (shop_web_target) { + case ShopWebTarget::ApplicationInfo: + if (!check_optional_parameter(title_id)) + return; + frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id, + shop_full_display, shop_extra_parameter); + break; + case ShopWebTarget::AddOnContentList: + if (!check_optional_parameter(title_id)) + return; + frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display); + break; + case ShopWebTarget::ConsumableItemList: + if (!check_optional_parameter(title_id)) + return; + frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id); + break; + case ShopWebTarget::Home: + if (!check_optional_parameter(user_id)) + return; + if (!check_optional_parameter(shop_full_display)) + return; + frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display); + break; + case ShopWebTarget::Settings: + if (!check_optional_parameter(user_id)) + return; + if (!check_optional_parameter(shop_full_display)) + return; + frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display); + break; + case ShopWebTarget::SubscriptionList: + if (!check_optional_parameter(title_id)) + return; + frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id); + break; + default: + UNREACHABLE(); + } +} + +void WebBrowser::ExecuteOffline() { + frontend.OpenPageLocal(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); } } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h index 7e0f34c7d..870f57b64 100644 --- a/src/core/hle/service/am/applets/web_browser.h +++ b/src/core/hle/service/am/applets/web_browser.h @@ -4,15 +4,22 @@ #pragma once +#include <map> #include "core/file_sys/vfs_types.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applets.h" namespace Service::AM::Applets { +enum class ShimKind : u32; +enum class ShopWebTarget; +enum class WebArgTLVType : u16; + class WebBrowser final : public Applet { public: - WebBrowser(Core::Frontend::WebBrowserApplet& frontend); + WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, + Core::Frontend::ECommerceApplet* frontend_e_commerce = nullptr); + ~WebBrowser() override; void Initialize() override; @@ -32,15 +39,41 @@ public: void Finalize(); private: + void InitializeInternal(); + void ExecuteInternal(); + + // Specific initializers for the types of web applets + void InitializeShop(); + void InitializeOffline(); + + // Specific executors for the types of web applets + void ExecuteShop(); + void ExecuteOffline(); + Core::Frontend::WebBrowserApplet& frontend; + // Extra frontends for specialized functions + Core::Frontend::ECommerceApplet* frontend_e_commerce; + bool complete = false; bool unpacked = false; ResultCode status = RESULT_SUCCESS; - FileSys::VirtualFile manual_romfs; + u64 current_process_title_id; + + ShimKind kind; + std::map<WebArgTLVType, std::vector<u8>> args; + + FileSys::VirtualFile offline_romfs; std::string temporary_dir; std::string filename; + + ShopWebTarget shop_web_target; + std::map<std::string, std::string, std::less<>> shop_query; + std::optional<u64> title_id = 0; + std::optional<u128> user_id; + std::optional<bool> shop_full_display; + std::string shop_extra_parameter; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/arp/arp.cpp b/src/core/hle/service/arp/arp.cpp deleted file mode 100644 index e675b0188..000000000 --- a/src/core/hle/service/arp/arp.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> - -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/service/arp/arp.h" -#include "core/hle/service/service.h" -#include "core/hle/service/sm/sm.h" - -namespace Service::ARP { - -class ARP_R final : public ServiceFramework<ARP_R> { -public: - explicit ARP_R() : ServiceFramework{"arp:r"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "GetApplicationLaunchProperty"}, - {1, nullptr, "GetApplicationLaunchPropertyWithApplicationId"}, - {2, nullptr, "GetApplicationControlProperty"}, - {3, nullptr, "GetApplicationControlPropertyWithApplicationId"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; - -class IRegistrar final : public ServiceFramework<IRegistrar> { -public: - explicit IRegistrar() : ServiceFramework{"IRegistrar"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "Issue"}, - {1, nullptr, "SetApplicationLaunchProperty"}, - {2, nullptr, "SetApplicationControlProperty"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; - -class ARP_W final : public ServiceFramework<ARP_W> { -public: - explicit ARP_W() : ServiceFramework{"arp:w"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"}, - {1, nullptr, "DeleteProperties"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void AcquireRegistrar(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_ARP, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IRegistrar>(); - } -}; - -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<ARP_R>()->InstallAsService(sm); - std::make_shared<ARP_W>()->InstallAsService(sm); -} - -} // namespace Service::ARP diff --git a/src/core/hle/service/arp/arp.h b/src/core/hle/service/arp/arp.h deleted file mode 100644 index 9d100187c..000000000 --- a/src/core/hle/service/arp/arp.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -namespace Service::SM { -class ServiceManager; -} - -namespace Service::ARP { - -/// Registers all ARP services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); - -} // namespace Service::ARP diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 6ba41b20a..7db6eb08d 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -58,8 +58,8 @@ public: {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, {10, nullptr, "GetAudioOutPlayedSampleCount"}, {11, nullptr, "FlushAudioOutBuffers"}, - {12, nullptr, "SetAudioOutVolume"}, - {13, nullptr, "GetAudioOutVolume"}, + {12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"}, + {13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"}, }; // clang-format on RegisterHandlers(functions); @@ -183,6 +183,25 @@ private: rb.Push(static_cast<u32>(stream->GetQueueSize())); } + void SetAudioOutVolume(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const float volume = rp.Pop<float>(); + LOG_DEBUG(Service_Audio, "called, volume={}", volume); + + stream->SetVolume(volume); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetAudioOutVolume(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(stream->GetVolume()); + } + AudioCore::AudioOut& audio_core; AudioCore::StreamPtr stream; std::string device_name; diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index 2c229bcad..fe49c2161 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -16,6 +16,7 @@ #include "core/hle/service/fatal/fatal.h" #include "core/hle/service/fatal/fatal_p.h" #include "core/hle/service/fatal/fatal_u.h" +#include "core/reporter.h" namespace Service::Fatal { @@ -100,27 +101,10 @@ static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) { LOG_ERROR(Service_Fatal, "{}", crash_report); - const std::string crashreport_dir = - FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "crash_logs"; - - if (!FileUtil::CreateFullPath(crashreport_dir)) { - LOG_ERROR( - Service_Fatal, - "Unable to create crash report directory. Possible log directory permissions issue."); - return; - } - - const std::time_t t = std::time(nullptr); - const std::string crashreport_filename = - fmt::format("{}/{:016x}-{:%F-%H%M%S}.log", crashreport_dir, title_id, *std::localtime(&t)); - - auto file = FileUtil::IOFile(crashreport_filename, "wb"); - if (file.IsOpen()) { - file.WriteString(crash_report); - LOG_ERROR(Service_Fatal, "Saving error report to {}", crashreport_filename); - } else { - LOG_ERROR(Service_Fatal, "Failed to save error report to {}", crashreport_filename); - } + Core::System::GetInstance().GetReporter().SaveCrashReport( + title_id, error_code, info.set_flags, info.program_entry_point, info.sp, info.pc, + info.pstate, info.afsr0, info.afsr1, info.esr, info.far, info.registers, info.backtrace, + info.backtrace_size, info.ArchAsString(), info.unk10); } static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) { diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp new file mode 100644 index 000000000..b591ce31b --- /dev/null +++ b/src/core/hle/service/glue/arp.cpp @@ -0,0 +1,297 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> + +#include "common/logging/log.h" +#include "core/file_sys/control_metadata.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/glue/arp.h" +#include "core/hle/service/glue/errors.h" +#include "core/hle/service/glue/manager.h" +#include "core/hle/service/service.h" + +namespace Service::Glue { + +namespace { +std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 process_id) { + const auto& list = system.Kernel().GetProcessList(); + const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) { + return process->GetProcessID() == process_id; + }); + + if (iter == list.end()) { + return std::nullopt; + } + + return (*iter)->GetTitleID(); +} +} // Anonymous namespace + +ARP_R::ARP_R(const Core::System& system, const ARPManager& manager) + : ServiceFramework{"arp:r"}, system(system), manager(manager) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"}, + {1, &ARP_R::GetApplicationLaunchPropertyWithApplicationId, "GetApplicationLaunchPropertyWithApplicationId"}, + {2, &ARP_R::GetApplicationControlProperty, "GetApplicationControlProperty"}, + {3, &ARP_R::GetApplicationControlPropertyWithApplicationId, "GetApplicationControlPropertyWithApplicationId"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +ARP_R::~ARP_R() = default; + +void ARP_R::GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id); + + const auto title_id = GetTitleIDForProcessID(system, process_id); + if (!title_id.has_value()) { + LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NOT_REGISTERED); + return; + } + + const auto res = manager.GetLaunchProperty(*title_id); + + if (res.Failed()) { + LOG_ERROR(Service_ARP, "Failed to get launch property!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(*res); +} + +void ARP_R::GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id); + + const auto res = manager.GetLaunchProperty(title_id); + + if (res.Failed()) { + LOG_ERROR(Service_ARP, "Failed to get launch property!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(*res); +} + +void ARP_R::GetApplicationControlProperty(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id); + + const auto title_id = GetTitleIDForProcessID(system, process_id); + if (!title_id.has_value()) { + LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NOT_REGISTERED); + return; + } + + const auto res = manager.GetControlProperty(*title_id); + + if (res.Failed()) { + LOG_ERROR(Service_ARP, "Failed to get control property!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + ctx.WriteBuffer(*res); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void ARP_R::GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id); + + const auto res = manager.GetControlProperty(title_id); + + if (res.Failed()) { + LOG_ERROR(Service_ARP, "Failed to get control property!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + ctx.WriteBuffer(*res); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +class IRegistrar final : public ServiceFramework<IRegistrar> { + friend class ARP_W; + +public: + explicit IRegistrar( + std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issuer) + : ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IRegistrar::Issue, "Issue"}, + {1, &IRegistrar::SetApplicationLaunchProperty, "SetApplicationLaunchProperty"}, + {2, &IRegistrar::SetApplicationControlProperty, "SetApplicationControlProperty"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Issue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id); + + if (process_id == 0) { + LOG_ERROR(Service_ARP, "Must have non-zero process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_PROCESS_ID); + return; + } + + if (issued) { + LOG_ERROR(Service_ARP, + "Attempted to issue registrar, but registrar is already issued!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_ACCESS); + return; + } + + issue_process_id(process_id, launch, std::move(control)); + issued = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ARP, "called"); + + if (issued) { + LOG_ERROR( + Service_ARP, + "Attempted to set application launch property, but registrar is already issued!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_ACCESS); + return; + } + + IPC::RequestParser rp{ctx}; + launch = rp.PopRaw<ApplicationLaunchProperty>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetApplicationControlProperty(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ARP, "called"); + + if (issued) { + LOG_ERROR( + Service_ARP, + "Attempted to set application control property, but registrar is already issued!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_ACCESS); + return; + } + + control = ctx.ReadBuffer(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issue_process_id; + bool issued = false; + ApplicationLaunchProperty launch; + std::vector<u8> control; +}; + +ARP_W::ARP_W(const Core::System& system, ARPManager& manager) + : ServiceFramework{"arp:w"}, system(system), manager(manager) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"}, + {1, &ARP_W::DeleteProperties, "DeleteProperties"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +ARP_W::~ARP_W() = default; + +void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ARP, "called"); + + registrar = std::make_shared<IRegistrar>( + [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) { + const auto res = GetTitleIDForProcessID(system, process_id); + if (!res.has_value()) { + return ERR_NOT_REGISTERED; + } + + return manager.Register(*res, launch, std::move(control)); + }); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(registrar); +} + +void ARP_W::DeleteProperties(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id); + + if (process_id == 0) { + LOG_ERROR(Service_ARP, "Must have non-zero process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_PROCESS_ID); + return; + } + + const auto title_id = GetTitleIDForProcessID(system, process_id); + + if (!title_id.has_value()) { + LOG_ERROR(Service_ARP, "No title ID for process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NOT_REGISTERED); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(manager.Unregister(*title_id)); +} + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h new file mode 100644 index 000000000..d5f8a7e7a --- /dev/null +++ b/src/core/hle/service/glue/arp.h @@ -0,0 +1,43 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Glue { + +class ARPManager; +class IRegistrar; + +class ARP_R final : public ServiceFramework<ARP_R> { +public: + explicit ARP_R(const Core::System& system, const ARPManager& manager); + ~ARP_R() override; + +private: + void GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx); + void GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx); + void GetApplicationControlProperty(Kernel::HLERequestContext& ctx); + void GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx); + + const Core::System& system; + const ARPManager& manager; +}; + +class ARP_W final : public ServiceFramework<ARP_W> { +public: + explicit ARP_W(const Core::System& system, ARPManager& manager); + ~ARP_W() override; + +private: + void AcquireRegistrar(Kernel::HLERequestContext& ctx); + void DeleteProperties(Kernel::HLERequestContext& ctx); + + const Core::System& system; + ARPManager& manager; + std::shared_ptr<IRegistrar> registrar; +}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp new file mode 100644 index 000000000..cd89d088f --- /dev/null +++ b/src/core/hle/service/glue/bgtc.cpp @@ -0,0 +1,50 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/glue/bgtc.h" + +namespace Service::Glue { + +BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "NotifyTaskStarting"}, + {2, nullptr, "NotifyTaskFinished"}, + {3, nullptr, "GetTriggerEvent"}, + {4, nullptr, "IsInHalfAwake"}, + {5, nullptr, "NotifyClientName"}, + {6, nullptr, "IsInFullAwake"}, + {11, nullptr, "ScheduleTask"}, + {12, nullptr, "GetScheduledTaskInterval"}, + {13, nullptr, "UnscheduleTask"}, + {14, nullptr, "GetScheduleEvent"}, + {15, nullptr, "SchedulePeriodicTask"}, + {101, nullptr, "GetOperationMode"}, + {102, nullptr, "WillDisconnectNetworkWhenEnteringSleep"}, + {103, nullptr, "WillStayHalfAwakeInsteadSleep"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +BGTC_T::~BGTC_T() = default; + +BGTC_SC::BGTC_SC() : ServiceFramework{"bgtc:sc"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "GetState"}, + {2, nullptr, "GetStateChangedEvent"}, + {3, nullptr, "NotifyEnteringHalfAwake"}, + {4, nullptr, "NotifyLeavingHalfAwake"}, + {5, nullptr, "SetIsUsingSleepUnsupportedDevices"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +BGTC_SC::~BGTC_SC() = default; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h new file mode 100644 index 000000000..81844f03e --- /dev/null +++ b/src/core/hle/service/glue/bgtc.h @@ -0,0 +1,23 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Glue { + +class BGTC_T final : public ServiceFramework<BGTC_T> { +public: + BGTC_T(); + ~BGTC_T() override; +}; + +class BGTC_SC final : public ServiceFramework<BGTC_SC> { +public: + BGTC_SC(); + ~BGTC_SC() override; +}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/errors.h b/src/core/hle/service/glue/errors.h new file mode 100644 index 000000000..c2874c585 --- /dev/null +++ b/src/core/hle/service/glue/errors.h @@ -0,0 +1,16 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Service::Glue { + +constexpr ResultCode ERR_INVALID_RESOURCE{ErrorModule::ARP, 0x1E}; +constexpr ResultCode ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 0x1F}; +constexpr ResultCode ERR_INVALID_ACCESS{ErrorModule::ARP, 0x2A}; +constexpr ResultCode ERR_NOT_REGISTERED{ErrorModule::ARP, 0x66}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp new file mode 100644 index 000000000..c728e815c --- /dev/null +++ b/src/core/hle/service/glue/glue.cpp @@ -0,0 +1,25 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "core/core.h" +#include "core/hle/service/glue/arp.h" +#include "core/hle/service/glue/bgtc.h" +#include "core/hle/service/glue/glue.h" + +namespace Service::Glue { + +void InstallInterfaces(Core::System& system) { + // ARP + std::make_shared<ARP_R>(system, system.GetARPManager()) + ->InstallAsService(system.ServiceManager()); + std::make_shared<ARP_W>(system, system.GetARPManager()) + ->InstallAsService(system.ServiceManager()); + + // BackGround Task Controller + std::make_shared<BGTC_T>()->InstallAsService(system.ServiceManager()); + std::make_shared<BGTC_SC>()->InstallAsService(system.ServiceManager()); +} + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/glue.h b/src/core/hle/service/glue/glue.h new file mode 100644 index 000000000..112cd238b --- /dev/null +++ b/src/core/hle/service/glue/glue.h @@ -0,0 +1,16 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Core { +class System; +} // namespace Core + +namespace Service::Glue { + +/// Registers all Glue services with the specified service manager. +void InstallInterfaces(Core::System& system); + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/manager.cpp b/src/core/hle/service/glue/manager.cpp new file mode 100644 index 000000000..6da52d2d6 --- /dev/null +++ b/src/core/hle/service/glue/manager.cpp @@ -0,0 +1,78 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/glue/errors.h" +#include "core/hle/service/glue/manager.h" + +namespace Service::Glue { + +struct ARPManager::MapEntry { + ApplicationLaunchProperty launch; + std::vector<u8> control; +}; + +ARPManager::ARPManager() = default; + +ARPManager::~ARPManager() = default; + +ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const { + if (title_id == 0) { + return ERR_INVALID_PROCESS_ID; + } + + const auto iter = entries.find(title_id); + if (iter == entries.end()) { + return ERR_NOT_REGISTERED; + } + + return MakeResult<ApplicationLaunchProperty>(iter->second.launch); +} + +ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const { + if (title_id == 0) { + return ERR_INVALID_PROCESS_ID; + } + + const auto iter = entries.find(title_id); + if (iter == entries.end()) { + return ERR_NOT_REGISTERED; + } + + return MakeResult<std::vector<u8>>(iter->second.control); +} + +ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch, + std::vector<u8> control) { + if (title_id == 0) { + return ERR_INVALID_PROCESS_ID; + } + + const auto iter = entries.find(title_id); + if (iter != entries.end()) { + return ERR_INVALID_ACCESS; + } + + entries.insert_or_assign(title_id, MapEntry{launch, std::move(control)}); + return RESULT_SUCCESS; +} + +ResultCode ARPManager::Unregister(u64 title_id) { + if (title_id == 0) { + return ERR_INVALID_PROCESS_ID; + } + + const auto iter = entries.find(title_id); + if (iter == entries.end()) { + return ERR_NOT_REGISTERED; + } + + entries.erase(iter); + return RESULT_SUCCESS; +} + +void ARPManager::ResetAll() { + entries.clear(); +} + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/manager.h b/src/core/hle/service/glue/manager.h new file mode 100644 index 000000000..a7f5ce3ee --- /dev/null +++ b/src/core/hle/service/glue/manager.h @@ -0,0 +1,63 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <map> +#include <vector> +#include "common/common_types.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/romfs_factory.h" +#include "core/hle/result.h" + +namespace Service::Glue { + +struct ApplicationLaunchProperty { + u64 title_id; + u32 version; + FileSys::StorageId base_game_storage_id; + FileSys::StorageId update_storage_id; + u8 program_index; + u8 reserved; +}; +static_assert(sizeof(ApplicationLaunchProperty) == 0x10, + "ApplicationLaunchProperty has incorrect size."); + +// A class to manage state related to the arp:w and arp:r services, specifically the registration +// and unregistration of launch and control properties. +class ARPManager { +public: + ARPManager(); + ~ARPManager(); + + // Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was + // previously registered, otherwise ERR_NOT_REGISTERED if it was never registered or + // ERR_INVALID_PROCESS_ID if the title ID is 0. + ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const; + + // Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to + // the provided title ID if it was previously registered, otherwise ERR_NOT_REGISTERED if it was + // never registered or ERR_INVALID_PROCESS_ID if the title ID is 0. + ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const; + + // Adds a new entry to the internal database with the provided parameters, returning + // ERR_INVALID_ACCESS if attempting to re-register a title ID without an intermediate Unregister + // step, and ERR_INVALID_PROCESS_ID if the title ID is 0. + ResultCode Register(u64 title_id, ApplicationLaunchProperty launch, std::vector<u8> control); + + // Removes the registration for the provided title ID from the database, returning + // ERR_NOT_REGISTERED if it doesn't exist in the database and ERR_INVALID_PROCESS_ID if the + // title ID is 0. + ResultCode Unregister(u64 title_id); + + // Removes all entries from the database, always succeeds. Should only be used when resetting + // system state. + void ResetAll(); + +private: + struct MapEntry; + std::map<u64, MapEntry> entries; +}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 5af925515..b839303ac 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -310,7 +310,7 @@ public: if (!IsValidNROHash(hash)) { LOG_ERROR(Service_LDR, "NRO hash is not present in any currently loaded NRRs (hash={})!", - Common::HexArrayToString(hash)); + Common::HexToString(hash)); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_MISSING_NRR_HASH); return; diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index e4fcee9f8..7e134f5c1 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -2,10 +2,18 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <json.hpp> +#include "common/file_util.h" +#include "common/hex_util.h" #include "common/logging/log.h" +#include "common/scm_rev.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/prepo/prepo.h" #include "core/hle/service/service.h" +#include "core/reporter.h" +#include "core/settings.h" namespace Service::PlayReport { @@ -40,8 +48,21 @@ public: private: void SaveReportWithUserOld(Kernel::HLERequestContext& ctx) { - // TODO(ogniK): Do we want to add play report? - LOG_WARNING(Service_PREPO, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto user_id = rp.PopRaw<u128>(); + const auto process_id = rp.PopRaw<u64>(); + + const auto data1 = ctx.ReadBuffer(0); + const auto data2 = ctx.ReadBuffer(1); + + LOG_DEBUG( + Service_PREPO, + "called, user_id={:016X}{:016X}, unk1={:016X}, data1_size={:016X}, data2_size={:016X}", + user_id[1], user_id[0], process_id, data1.size(), data2.size()); + + const auto& reporter{Core::System::GetInstance().GetReporter()}; + reporter.SavePlayReport(Core::CurrentProcess()->GetTitleID(), process_id, {data1, data2}, + user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 00806b0ed..ec9d755b7 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -19,7 +19,6 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/aoc/aoc_u.h" #include "core/hle/service/apm/apm.h" -#include "core/hle/service/arp/arp.h" #include "core/hle/service/audio/audio.h" #include "core/hle/service/bcat/module.h" #include "core/hle/service/bpc/bpc.h" @@ -33,6 +32,7 @@ #include "core/hle/service/fgm/fgm.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/friend/friend.h" +#include "core/hle/service/glue/glue.h" #include "core/hle/service/grc/grc.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/lbl/lbl.h" @@ -68,6 +68,7 @@ #include "core/hle/service/usb/usb.h" #include "core/hle/service/vi/vi.h" #include "core/hle/service/wlan/wlan.h" +#include "core/reporter.h" namespace Service { @@ -148,6 +149,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext } buf.push_back('}'); + Core::System::GetInstance().GetReporter().SaveUnimplementedFunctionReport( + ctx, ctx.GetCommand(), function_name, service_name); UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf)); } @@ -200,11 +203,10 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system, SM::ServiceManager::InstallInterfaces(sm); - Account::InstallInterfaces(*sm); - AM::InstallInterfaces(*sm, nv_flinger); + Account::InstallInterfaces(system); + AM::InstallInterfaces(*sm, nv_flinger, system); AOC::InstallInterfaces(*sm); APM::InstallInterfaces(*sm); - ARP::InstallInterfaces(*sm); Audio::InstallInterfaces(*sm); BCAT::InstallInterfaces(*sm); BPC::InstallInterfaces(*sm); @@ -218,6 +220,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system, FGM::InstallInterfaces(*sm); FileSystem::InstallInterfaces(*sm, vfs); Friend::InstallInterfaces(*sm); + Glue::InstallInterfaces(system); GRC::InstallInterfaces(*sm); HID::InstallInterfaces(*sm); LBL::InstallInterfaces(*sm); diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 10b13fb1d..f9e88be2b 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -141,6 +141,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect const FileSys::PatchManager pm(metadata.GetTitleID()); // Load NSO modules + modules.clear(); const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); VAddr next_load_addr = base_address; for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", @@ -159,6 +160,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } next_load_addr = *tentative_next_load_addr; + modules.insert_or_assign(load_addr, module); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); // Register module with GDBStub GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); @@ -212,4 +214,13 @@ bool AppLoader_DeconstructedRomDirectory::IsRomFSUpdatable() const { return false; } +ResultStatus AppLoader_DeconstructedRomDirectory::ReadNSOModules(Modules& modules) { + if (!is_loaded) { + return ResultStatus::ErrorNotInitialized; + } + + modules = this->modules; + return ResultStatus::Success; +} + } // namespace Loader diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index 1a65c16a4..1c0a354a4 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -45,6 +45,8 @@ public: ResultStatus ReadTitle(std::string& title) override; bool IsRomFSUpdatable() const override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: FileSys::ProgramMetadata metadata; FileSys::VirtualFile romfs; @@ -54,6 +56,8 @@ private: std::string name; u64 title_id{}; bool override_update; + + Modules modules; }; } // namespace Loader diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp new file mode 100644 index 000000000..70051c13a --- /dev/null +++ b/src/core/loader/kip.cpp @@ -0,0 +1,102 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/kernel_executable.h" +#include "core/file_sys/program_metadata.h" +#include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/code_set.h" +#include "core/hle/kernel/process.h" +#include "core/loader/kip.h" + +namespace Loader { + +namespace { +constexpr u32 PageAlignSize(u32 size) { + return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; +} +} // Anonymous namespace + +AppLoader_KIP::AppLoader_KIP(FileSys::VirtualFile file_) + : AppLoader(std::move(file_)), kip(std::make_unique<FileSys::KIP>(file)) {} + +AppLoader_KIP::~AppLoader_KIP() = default; + +FileType AppLoader_KIP::IdentifyType(const FileSys::VirtualFile& file) { + u32_le magic{}; + if (file->GetSize() < sizeof(u32) || file->ReadObject(&magic) != sizeof(u32)) { + return FileType::Error; + } + + if (magic == Common::MakeMagic('K', 'I', 'P', '1')) { + return FileType::KIP; + } + + return FileType::Error; +} + +FileType AppLoader_KIP::GetFileType() const { + return (kip != nullptr && kip->GetStatus() == ResultStatus::Success) ? FileType::KIP + : FileType::Error; +} + +AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) { + if (is_loaded) { + return {ResultStatus::ErrorAlreadyLoaded, {}}; + } + + if (kip == nullptr) { + return {ResultStatus::ErrorNullFile, {}}; + } + + if (kip->GetStatus() != ResultStatus::Success) { + return {kip->GetStatus(), {}}; + } + + const auto get_kip_address_space_type = [](const auto& kip) { + return kip.Is64Bit() + ? (kip.Is39BitAddressSpace() ? FileSys::ProgramAddressSpaceType::Is39Bit + : FileSys::ProgramAddressSpaceType::Is36Bit) + : FileSys::ProgramAddressSpaceType::Is32Bit; + }; + + const auto address_space = get_kip_address_space_type(*kip); + + FileSys::ProgramMetadata metadata; + metadata.LoadManual(kip->Is64Bit(), address_space, kip->GetMainThreadPriority(), + kip->GetMainThreadCpuCore(), kip->GetMainThreadStackSize(), + kip->GetTitleID(), 0xFFFFFFFFFFFFFFFF, kip->GetKernelCapabilities()); + + const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); + Kernel::CodeSet codeset; + std::vector<u8> program_image; + + const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment, + const std::vector<u8>& data, u32 offset) { + segment.addr = offset; + segment.offset = offset; + segment.size = PageAlignSize(static_cast<u32>(data.size())); + program_image.resize(offset); + program_image.insert(program_image.end(), data.begin(), data.end()); + }; + + load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset()); + load_segment(codeset.RODataSegment(), kip->GetRODataSection(), kip->GetRODataOffset()); + load_segment(codeset.DataSegment(), kip->GetDataSection(), kip->GetDataOffset()); + + program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize()); + codeset.DataSegment().size += kip->GetBSSSize(); + + GDBStub::RegisterModule(kip->GetName(), base_address, base_address + program_image.size()); + + codeset.memory = std::move(program_image); + process.LoadModule(std::move(codeset), base_address); + + LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address); + + is_loaded = true; + return {ResultStatus::Success, + LoadParameters{kip->GetMainThreadPriority(), kip->GetMainThreadStackSize()}}; +} + +} // namespace Loader diff --git a/src/core/loader/kip.h b/src/core/loader/kip.h new file mode 100644 index 000000000..12ca40269 --- /dev/null +++ b/src/core/loader/kip.h @@ -0,0 +1,35 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/loader/loader.h" + +namespace FileSys { +class KIP; +} + +namespace Loader { + +class AppLoader_KIP final : public AppLoader { +public: + explicit AppLoader_KIP(FileSys::VirtualFile file); + ~AppLoader_KIP() override; + + /** + * Returns the type of the file + * @param file std::shared_ptr<VfsFile> open file + * @return FileType found, or FileType::Error if this loader doesn't know it + */ + static FileType IdentifyType(const FileSys::VirtualFile& file); + + FileType GetFileType() const override; + + LoadResult Load(Kernel::Process& process) override; + +private: + std::unique_ptr<FileSys::KIP> kip; +}; + +} // namespace Loader diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index d8cc30959..59ca7091a 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -11,6 +11,7 @@ #include "core/hle/kernel/process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/elf.h" +#include "core/loader/kip.h" #include "core/loader/nax.h" #include "core/loader/nca.h" #include "core/loader/nro.h" @@ -36,6 +37,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { CHECK_TYPE(XCI) CHECK_TYPE(NAX) CHECK_TYPE(NSP) + CHECK_TYPE(KIP) #undef CHECK_TYPE @@ -63,6 +65,8 @@ FileType GuessFromFilename(const std::string& name) { return FileType::XCI; if (extension == "nsp") return FileType::NSP; + if (extension == "kip") + return FileType::KIP; return FileType::Unknown; } @@ -83,6 +87,8 @@ std::string GetFileTypeString(FileType type) { return "NAX"; case FileType::NSP: return "NSP"; + case FileType::KIP: + return "KIP"; case FileType::DeconstructedRomDirectory: return "Directory"; case FileType::Error: @@ -93,7 +99,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 62> RESULT_MESSAGES{ +constexpr std::array<const char*, 66> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -156,6 +162,10 @@ constexpr std::array<const char*, 62> RESULT_MESSAGES{ "The BKTR-type NCA has a bad Subsection bucket.", "The BKTR-type NCA is missing the base RomFS.", "The NSP or XCI does not contain an update in addition to the base game.", + "The KIP file has a bad header.", + "The KIP BLZ decompression of the section failed unexpectedly.", + "The INI file has a bad header.", + "The INI file contains more than the maximum allowable number of KIP files.", }; std::ostream& operator<<(std::ostream& os, ResultStatus status) { @@ -205,6 +215,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT case FileType::NSP: return std::make_unique<AppLoader_NSP>(std::move(file)); + // NX KIP (Kernel Internal Process) file format + case FileType::KIP: + return std::make_unique<AppLoader_KIP>(std::move(file)); + // NX deconstructed ROM directory. case FileType::DeconstructedRomDirectory: return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 869406b75..227ecc704 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -37,6 +37,7 @@ enum class FileType { NSP, XCI, NAX, + KIP, DeconstructedRomDirectory, }; @@ -124,6 +125,10 @@ enum class ResultStatus : u16 { ErrorBadSubsectionBuckets, ErrorMissingBKTRBaseRomFS, ErrorNoPackedUpdate, + ErrorBadKIPHeader, + ErrorBLZDecompressionFailed, + ErrorBadINIHeader, + ErrorINITooManyKIPs, }; std::ostream& operator<<(std::ostream& os, ResultStatus status); @@ -267,6 +272,12 @@ public: return ResultStatus::ErrorNotImplemented; } + using Modules = std::map<VAddr, std::string>; + + virtual ResultStatus ReadNSOModules(Modules& modules) { + return ResultStatus::ErrorNotImplemented; + } + protected: FileSys::VirtualFile file; bool is_loaded = false; diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index 34efef09a..a152981a0 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -94,4 +94,8 @@ ResultStatus AppLoader_NAX::ReadLogo(std::vector<u8>& buffer) { return nca_loader->ReadLogo(buffer); } +ResultStatus AppLoader_NAX::ReadNSOModules(Modules& modules) { + return nca_loader->ReadNSOModules(modules); +} + } // namespace Loader diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index 00f1659c1..eaec9bf58 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -42,6 +42,8 @@ public: ResultStatus ReadBanner(std::vector<u8>& buffer) override; ResultStatus ReadLogo(std::vector<u8>& buffer) override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: std::unique_ptr<FileSys::NAX> nax; std::unique_ptr<AppLoader_NCA> nca_loader; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index b3f8f1083..0f65fb637 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -105,4 +105,13 @@ ResultStatus AppLoader_NCA::ReadLogo(std::vector<u8>& buffer) { buffer = logo->GetFile("NintendoLogo.png")->ReadAllBytes(); return ResultStatus::Success; } + +ResultStatus AppLoader_NCA::ReadNSOModules(Modules& modules) { + if (directory_loader == nullptr) { + return ResultStatus::ErrorNotInitialized; + } + + return directory_loader->ReadNSOModules(modules); +} + } // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 94f0ed677..e47dc0e47 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -42,6 +42,8 @@ public: ResultStatus ReadBanner(std::vector<u8>& buffer) override; ResultStatus ReadLogo(std::vector<u8>& buffer) override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: std::unique_ptr<FileSys::NCA> nca; std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 62c090353..29311404a 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -152,8 +152,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, auto& system = Core::System::GetInstance(); const auto cheats = pm->CreateCheatList(system, nso_header.build_id); if (!cheats.empty()) { - system.RegisterCheatList(cheats, Common::HexArrayToString(nso_header.build_id), - load_base, load_base + program_image.size()); + system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base, + load_base + program_image.size()); } } @@ -172,11 +172,15 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } + modules.clear(); + // Load module const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); if (!LoadModule(process, *file, base_address, true)) { return {ResultStatus::ErrorLoadingNSO, {}}; } + + modules.insert_or_assign(base_address, file->GetName()); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address); is_loaded = true; @@ -184,4 +188,9 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { LoadParameters{Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE}}; } +ResultStatus AppLoader_NSO::ReadNSOModules(Modules& modules) { + modules = this->modules; + return ResultStatus::Success; +} + } // namespace Loader diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index fdce9191c..58cbe162d 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -85,6 +85,11 @@ public: std::optional<FileSys::PatchManager> pm = {}); LoadResult Load(Kernel::Process& process) override; + + ResultStatus ReadNSOModules(Modules& modules) override; + +private: + Modules modules; }; } // namespace Loader diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index ad56bbb38..3a22ec2c6 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -183,4 +183,8 @@ ResultStatus AppLoader_NSP::ReadLogo(std::vector<u8>& buffer) { return secondary_loader->ReadLogo(buffer); } +ResultStatus AppLoader_NSP::ReadNSOModules(Modules& modules) { + return secondary_loader->ReadNSOModules(modules); +} + } // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 85e870bdf..868b028d3 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -49,6 +49,8 @@ public: ResultStatus ReadBanner(std::vector<u8>& buffer) override; ResultStatus ReadLogo(std::vector<u8>& buffer) override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: std::unique_ptr<FileSys::NSP> nsp; std::unique_ptr<AppLoader> secondary_loader; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 1e285a053..a5c4d3688 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -149,4 +149,8 @@ ResultStatus AppLoader_XCI::ReadLogo(std::vector<u8>& buffer) { return nca_loader->ReadLogo(buffer); } +ResultStatus AppLoader_XCI::ReadNSOModules(Modules& modules) { + return nca_loader->ReadNSOModules(modules); +} + } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index ae7145b14..618ae2f47 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -49,6 +49,8 @@ public: ResultStatus ReadBanner(std::vector<u8>& buffer) override; ResultStatus ReadLogo(std::vector<u8>& buffer) override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: std::unique_ptr<FileSys::XCI> xci; std::unique_ptr<AppLoader_NCA> nca_loader; diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp new file mode 100644 index 000000000..774022569 --- /dev/null +++ b/src/core/reporter.cpp @@ -0,0 +1,353 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <fstream> +#include <json.hpp> +#include "common/file_util.h" +#include "common/hex_util.h" +#include "common/scm_rev.h" +#include "core/arm/arm_interface.h" +#include "core/core.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/process.h" +#include "core/hle/result.h" +#include "core/reporter.h" +#include "core/settings.h" +#include "fmt/time.h" + +namespace { + +std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { + return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir), + type, title_id, timestamp); +} + +std::string GetTimestamp() { + const auto time = std::time(nullptr); + return fmt::format("{:%FT%H-%M-%S}", *std::localtime(&time)); +} + +using namespace nlohmann; + +void SaveToFile(const json& json, const std::string& filename) { + if (!FileUtil::CreateFullPath(filename)) + LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename); + + std::ofstream file( + FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault)); + file << std::setw(4) << json << std::endl; +} + +json GetYuzuVersionData() { + return { + {"scm_rev", std::string(Common::g_scm_rev)}, + {"scm_branch", std::string(Common::g_scm_branch)}, + {"scm_desc", std::string(Common::g_scm_desc)}, + {"build_name", std::string(Common::g_build_name)}, + {"build_date", std::string(Common::g_build_date)}, + {"build_fullname", std::string(Common::g_build_fullname)}, + {"build_version", std::string(Common::g_build_version)}, + {"shader_cache_version", std::string(Common::g_shader_cache_version)}, + }; +} + +json GetReportCommonData(u64 title_id, ResultCode result, const std::string& timestamp, + std::optional<u128> user_id = {}) { + auto out = json{ + {"title_id", fmt::format("{:016X}", title_id)}, + {"result_raw", fmt::format("{:08X}", result.raw)}, + {"result_module", fmt::format("{:08X}", static_cast<u32>(result.module.Value()))}, + {"result_description", fmt::format("{:08X}", result.description.Value())}, + {"timestamp", timestamp}, + }; + if (user_id.has_value()) + out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]); + return out; +} + +json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64 sp, u64 pc, + u64 pstate, std::array<u64, 31> registers, + std::optional<std::array<u64, 32>> backtrace = {}) { + auto out = json{ + {"entry_point", fmt::format("{:016X}", entry_point)}, + {"sp", fmt::format("{:016X}", sp)}, + {"pc", fmt::format("{:016X}", pc)}, + {"pstate", fmt::format("{:016X}", pstate)}, + {"architecture", architecture}, + }; + + auto registers_out = json::object(); + for (std::size_t i = 0; i < registers.size(); ++i) { + registers_out[fmt::format("X{:02d}", i)] = fmt::format("{:016X}", registers[i]); + } + + out["registers"] = std::move(registers_out); + + if (backtrace.has_value()) { + auto backtrace_out = json::array(); + for (const auto& entry : *backtrace) { + backtrace_out.push_back(fmt::format("{:016X}", entry)); + } + out["backtrace"] = std::move(backtrace_out); + } + + return out; +} + +json GetProcessorStateDataAuto(Core::System& system) { + const auto* process{system.CurrentProcess()}; + const auto& vm_manager{process->VMManager()}; + auto& arm{system.CurrentArmInterface()}; + + Core::ARM_Interface::ThreadContext context{}; + arm.SaveContext(context); + + return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", + vm_manager.GetCodeRegionBaseAddress(), context.sp, context.pc, + context.pstate, context.cpu_registers); +} + +json GetBacktraceData(Core::System& system) { + auto out = json::array(); + const auto& backtrace{system.CurrentArmInterface().GetBacktrace()}; + for (const auto& entry : backtrace) { + out.push_back({ + {"module", entry.module}, + {"address", fmt::format("{:016X}", entry.address)}, + {"original_address", fmt::format("{:016X}", entry.original_address)}, + {"offset", fmt::format("{:016X}", entry.offset)}, + {"symbol_name", entry.name}, + }); + } + + return out; +} + +json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& system) { + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp); + out["processor_state"] = GetProcessorStateDataAuto(system); + out["backtrace"] = GetBacktraceData(system); + + return out; +} + +template <bool read_value, typename DescriptorType> +json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer) { + auto buffer_out = json::array(); + for (const auto& desc : buffer) { + auto entry = json{ + {"address", fmt::format("{:016X}", desc.Address())}, + {"size", fmt::format("{:016X}", desc.Size())}, + }; + + if constexpr (read_value) { + std::vector<u8> data(desc.Size()); + Memory::ReadBlock(desc.Address(), data.data(), desc.Size()); + entry["data"] = Common::HexToString(data); + } + + buffer_out.push_back(std::move(entry)); + } + + return buffer_out; +} + +json GetHLERequestContextData(Kernel::HLERequestContext& ctx) { + json out; + + auto cmd_buf = json::array(); + for (std::size_t i = 0; i < IPC::COMMAND_BUFFER_LENGTH; ++i) { + cmd_buf.push_back(fmt::format("{:08X}", ctx.CommandBuffer()[i])); + } + + out["command_buffer"] = std::move(cmd_buf); + + out["buffer_descriptor_a"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorA()); + out["buffer_descriptor_b"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorB()); + out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC()); + out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX()); + + return std::move(out); +} + +} // Anonymous namespace + +namespace Core { + +Reporter::Reporter(Core::System& system) : system(system) {} + +Reporter::~Reporter() = default; + +void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, + u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far, + const std::array<u64, 31>& registers, + const std::array<u64, 32>& backtrace, u32 backtrace_size, + const std::string& arch, u32 unk10) const { + if (!IsReportingEnabled()) + return; + + const auto timestamp = GetTimestamp(); + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, result, timestamp); + + auto proc_out = GetProcessorStateData(arch, entry_point, sp, pc, pstate, registers, backtrace); + proc_out["set_flags"] = fmt::format("{:016X}", set_flags); + proc_out["afsr0"] = fmt::format("{:016X}", afsr0); + proc_out["afsr1"] = fmt::format("{:016X}", afsr1); + proc_out["esr"] = fmt::format("{:016X}", esr); + proc_out["far"] = fmt::format("{:016X}", far); + proc_out["backtrace_size"] = fmt::format("{:08X}", backtrace_size); + proc_out["unknown_10"] = fmt::format("{:08X}", unk10); + + out["processor_state"] = std::move(proc_out); + + SaveToFile(std::move(out), GetPath("crash_report", title_id, timestamp)); +} + +void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2, + std::optional<std::vector<u8>> resolved_buffer) const { + if (!IsReportingEnabled()) + return; + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + auto out = GetFullDataAuto(timestamp, title_id, system); + + auto break_out = json{ + {"type", fmt::format("{:08X}", type)}, + {"signal_debugger", fmt::format("{}", signal_debugger)}, + {"info1", fmt::format("{:016X}", info1)}, + {"info2", fmt::format("{:016X}", info2)}, + }; + + if (resolved_buffer.has_value()) { + break_out["debug_buffer"] = Common::HexToString(*resolved_buffer); + } + + out["svc_break"] = std::move(break_out); + + SaveToFile(std::move(out), GetPath("svc_break_report", title_id, timestamp)); +} + +void Reporter::SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id, + const std::string& name, + const std::string& service_name) const { + if (!IsReportingEnabled()) + return; + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + auto out = GetFullDataAuto(timestamp, title_id, system); + + auto function_out = GetHLERequestContextData(ctx); + function_out["command_id"] = command_id; + function_out["function_name"] = name; + function_out["service_name"] = service_name; + + out["function"] = std::move(function_out); + + SaveToFile(std::move(out), GetPath("unimpl_func_report", title_id, timestamp)); +} + +void Reporter::SaveUnimplementedAppletReport( + u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color, + bool startup_sound, u64 system_tick, std::vector<std::vector<u8>> normal_channel, + std::vector<std::vector<u8>> interactive_channel) const { + if (!IsReportingEnabled()) + return; + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + auto out = GetFullDataAuto(timestamp, title_id, system); + + out["applet_common_args"] = { + {"applet_id", fmt::format("{:02X}", applet_id)}, + {"common_args_version", fmt::format("{:08X}", common_args_version)}, + {"library_version", fmt::format("{:08X}", library_version)}, + {"theme_color", fmt::format("{:08X}", theme_color)}, + {"startup_sound", fmt::format("{}", startup_sound)}, + {"system_tick", fmt::format("{:016X}", system_tick)}, + }; + + auto normal_out = json::array(); + for (const auto& data : normal_channel) { + normal_out.push_back(Common::HexToString(data)); + } + + auto interactive_out = json::array(); + for (const auto& data : interactive_channel) { + interactive_out.push_back(Common::HexToString(data)); + } + + out["applet_normal_data"] = std::move(normal_out); + out["applet_interactive_data"] = std::move(interactive_out); + + SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp)); +} + +void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data, + std::optional<u128> user_id) const { + if (!IsReportingEnabled()) + return; + + const auto timestamp = GetTimestamp(); + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp, user_id); + + auto data_out = json::array(); + for (const auto& d : data) { + data_out.push_back(Common::HexToString(d)); + } + + out["play_report_process_id"] = fmt::format("{:016X}", process_id); + out["play_report_data"] = std::move(data_out); + + SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp)); +} + +void Reporter::SaveErrorReport(u64 title_id, ResultCode result, + std::optional<std::string> custom_text_main, + std::optional<std::string> custom_text_detail) const { + if (!IsReportingEnabled()) + return; + + const auto timestamp = GetTimestamp(); + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, result, timestamp); + out["processor_state"] = GetProcessorStateDataAuto(system); + out["backtrace"] = GetBacktraceData(system); + + out["error_custom_text"] = { + {"main", *custom_text_main}, + {"detail", *custom_text_detail}, + }; + + SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp)); +} + +void Reporter::SaveUserReport() const { + if (!IsReportingEnabled()) + return; + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + + SaveToFile(GetFullDataAuto(timestamp, title_id, system), + GetPath("user_report", title_id, timestamp)); +} + +bool Reporter::IsReportingEnabled() const { + return Settings::values.reporting_services; +} + +} // namespace Core diff --git a/src/core/reporter.h b/src/core/reporter.h new file mode 100644 index 000000000..3de19c0f7 --- /dev/null +++ b/src/core/reporter.h @@ -0,0 +1,56 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <optional> +#include <vector> +#include "common/common_types.h" + +union ResultCode; + +namespace Kernel { +class HLERequestContext; +} // namespace Kernel + +namespace Core { + +class Reporter { +public: + explicit Reporter(Core::System& system); + ~Reporter(); + + void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp, + u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far, + const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace, + u32 backtrace_size, const std::string& arch, u32 unk10) const; + + void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2, + std::optional<std::vector<u8>> resolved_buffer = {}) const; + + void SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id, + const std::string& name, + const std::string& service_name) const; + + void SaveUnimplementedAppletReport(u32 applet_id, u32 common_args_version, u32 library_version, + u32 theme_color, bool startup_sound, u64 system_tick, + std::vector<std::vector<u8>> normal_channel, + std::vector<std::vector<u8>> interactive_channel) const; + + void SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data, + std::optional<u128> user_id = {}) const; + + void SaveErrorReport(u64 title_id, ResultCode result, + std::optional<std::string> custom_text_main = {}, + std::optional<std::string> custom_text_detail = {}) const; + + void SaveUserReport() const; + +private: + bool IsReportingEnabled() const; + + Core::System& system; +}; + +} // namespace Core diff --git a/src/core/settings.h b/src/core/settings.h index b84390745..e2ffcaaf7 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -415,6 +415,7 @@ struct Values { std::string program_args; bool dump_exefs; bool dump_nso; + bool reporting_services; // WebService bool enable_telemetry; diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp new file mode 100644 index 000000000..17f050068 --- /dev/null +++ b/src/core/tools/freezer.cpp @@ -0,0 +1,188 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/memory.h" +#include "core/tools/freezer.h" + +namespace Tools { + +namespace { + +constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); + +u64 MemoryReadWidth(u32 width, VAddr addr) { + switch (width) { + case 1: + return Memory::Read8(addr); + case 2: + return Memory::Read16(addr); + case 4: + return Memory::Read32(addr); + case 8: + return Memory::Read64(addr); + default: + UNREACHABLE(); + return 0; + } +} + +void MemoryWriteWidth(u32 width, VAddr addr, u64 value) { + switch (width) { + case 1: + Memory::Write8(addr, static_cast<u8>(value)); + break; + case 2: + Memory::Write16(addr, static_cast<u16>(value)); + break; + case 4: + Memory::Write32(addr, static_cast<u32>(value)); + break; + case 8: + Memory::Write64(addr, value); + break; + default: + UNREACHABLE(); + } +} + +} // Anonymous namespace + +Freezer::Freezer(Core::Timing::CoreTiming& core_timing) : core_timing(core_timing) { + event = core_timing.RegisterEvent( + "MemoryFreezer::FrameCallback", + [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); +} + +Freezer::~Freezer() { + core_timing.UnscheduleEvent(event, 0); +} + +void Freezer::SetActive(bool active) { + if (!this->active.exchange(active)) { + FillEntryReads(); + core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); + LOG_DEBUG(Common_Memory, "Memory freezer activated!"); + } else { + LOG_DEBUG(Common_Memory, "Memory freezer deactivated!"); + } +} + +bool Freezer::IsActive() const { + return active.load(std::memory_order_relaxed); +} + +void Freezer::Clear() { + std::lock_guard lock{entries_mutex}; + + LOG_DEBUG(Common_Memory, "Clearing all frozen memory values."); + + entries.clear(); +} + +u64 Freezer::Freeze(VAddr address, u32 width) { + std::lock_guard lock{entries_mutex}; + + const auto current_value = MemoryReadWidth(width, address); + entries.push_back({address, width, current_value}); + + LOG_DEBUG(Common_Memory, + "Freezing memory for address={:016X}, width={:02X}, current_value={:016X}", address, + width, current_value); + + return current_value; +} + +void Freezer::Unfreeze(VAddr address) { + std::lock_guard lock{entries_mutex}; + + LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address); + + entries.erase( + std::remove_if(entries.begin(), entries.end(), + [&address](const Entry& entry) { return entry.address == address; }), + entries.end()); +} + +bool Freezer::IsFrozen(VAddr address) const { + std::lock_guard lock{entries_mutex}; + + return std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { + return entry.address == address; + }) != entries.end(); +} + +void Freezer::SetFrozenValue(VAddr address, u64 value) { + std::lock_guard lock{entries_mutex}; + + const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { + return entry.address == address; + }); + + if (iter == entries.end()) { + LOG_ERROR(Common_Memory, + "Tried to set freeze value for address={:016X} that is not frozen!", address); + return; + } + + LOG_DEBUG(Common_Memory, + "Manually overridden freeze value for address={:016X}, width={:02X} to value={:016X}", + iter->address, iter->width, value); + iter->value = value; +} + +std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const { + std::lock_guard lock{entries_mutex}; + + const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { + return entry.address == address; + }); + + if (iter == entries.end()) { + return std::nullopt; + } + + return *iter; +} + +std::vector<Freezer::Entry> Freezer::GetEntries() const { + std::lock_guard lock{entries_mutex}; + + return entries; +} + +void Freezer::FrameCallback(u64 userdata, s64 cycles_late) { + if (!IsActive()) { + LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events."); + return; + } + + std::lock_guard lock{entries_mutex}; + + for (const auto& entry : entries) { + LOG_DEBUG(Common_Memory, + "Enforcing memory freeze at address={:016X}, value={:016X}, width={:02X}", + entry.address, entry.value, entry.width); + MemoryWriteWidth(entry.width, entry.address, entry.value); + } + + core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - cycles_late, event); +} + +void Freezer::FillEntryReads() { + std::lock_guard lock{entries_mutex}; + + LOG_DEBUG(Common_Memory, "Updating memory freeze entries to current values."); + + for (auto& entry : entries) { + entry.value = MemoryReadWidth(entry.width, entry.address); + } +} + +} // namespace Tools diff --git a/src/core/tools/freezer.h b/src/core/tools/freezer.h new file mode 100644 index 000000000..b58de5472 --- /dev/null +++ b/src/core/tools/freezer.h @@ -0,0 +1,82 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <mutex> +#include <optional> +#include <vector> +#include "common/common_types.h" + +namespace Core::Timing { +class CoreTiming; +struct EventType; +} // namespace Core::Timing + +namespace Tools { + +/** + * This class allows the user to prevent an application from writing new values to certain memory + * locations. This has a variety of uses when attempting to reverse a game. + * + * One example could be a cheat to prevent Mario from taking damage in SMO. One could freeze the + * memory address that the game uses to store Mario's health so when he takes damage (and the game + * tries to write the new health value to memory), the value won't change. + */ +class Freezer { +public: + struct Entry { + VAddr address; + u32 width; + u64 value; + }; + + explicit Freezer(Core::Timing::CoreTiming& core_timing); + ~Freezer(); + + // Enables or disables the entire memory freezer. + void SetActive(bool active); + + // Returns whether or not the freezer is active. + bool IsActive() const; + + // Removes all entries from the freezer. + void Clear(); + + // Freezes a value to its current memory address. The value the memory is kept at will be the + // value that is read during this function. Width can be 1, 2, 4, or 8 (in bytes). + u64 Freeze(VAddr address, u32 width); + + // Unfreezes the memory value at address. If the address isn't frozen, this is a no-op. + void Unfreeze(VAddr address); + + // Returns whether or not the address is frozen. + bool IsFrozen(VAddr address) const; + + // Sets the value that address should be frozen to. This doesn't change the width set by using + // Freeze(). If the value isn't frozen, this will not freeze it and is thus a no-op. + void SetFrozenValue(VAddr address, u64 value); + + // Returns the entry corresponding to the address if the address is frozen, otherwise + // std::nullopt. + std::optional<Entry> GetEntry(VAddr address) const; + + // Returns all the entries in the freezer, an empty vector means nothing is frozen. + std::vector<Entry> GetEntries() const; + +private: + void FrameCallback(u64 userdata, s64 cycles_late); + void FillEntryReads(); + + std::atomic_bool active{false}; + + mutable std::mutex entries_mutex; + std::vector<Entry> entries; + + Core::Timing::EventType* event; + Core::Timing::CoreTiming& core_timing; +}; + +} // namespace Tools diff --git a/src/core/tracer/citrace.h b/src/core/tracer/citrace.h deleted file mode 100644 index 21fdc127a..000000000 --- a/src/core/tracer/citrace.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" - -namespace CiTrace { - -// NOTE: Things are stored in little-endian - -#pragma pack(1) - -struct CTHeader { - static const char* ExpectedMagicWord() { - return "CiTr"; - } - - static u32 ExpectedVersion() { - return 1; - } - - char magic[4]; - u32 version; - u32 header_size; - - struct { - // NOTE: Register range sizes are technically hardware-constants, but the actual limits - // aren't known. Hence we store the presumed limits along the offsets. - // Sizes are given in u32 units. - u32 gpu_registers; - u32 gpu_registers_size; - u32 lcd_registers; - u32 lcd_registers_size; - u32 pica_registers; - u32 pica_registers_size; - u32 default_attributes; - u32 default_attributes_size; - u32 vs_program_binary; - u32 vs_program_binary_size; - u32 vs_swizzle_data; - u32 vs_swizzle_data_size; - u32 vs_float_uniforms; - u32 vs_float_uniforms_size; - u32 gs_program_binary; - u32 gs_program_binary_size; - u32 gs_swizzle_data; - u32 gs_swizzle_data_size; - u32 gs_float_uniforms; - u32 gs_float_uniforms_size; - - // Other things we might want to store here: - // - Initial framebuffer data, maybe even a full copy of FCRAM/VRAM - // - Lookup tables for fragment lighting - // - Lookup tables for procedural textures - } initial_state_offsets; - - u32 stream_offset; - u32 stream_size; -}; - -enum CTStreamElementType : u32 { - FrameMarker = 0xE1, - MemoryLoad = 0xE2, - RegisterWrite = 0xE3, -}; - -struct CTMemoryLoad { - u32 file_offset; - u32 size; - u32 physical_address; - u32 pad; -}; - -struct CTRegisterWrite { - u32 physical_address; - - enum : u32 { - SIZE_8 = 0xD1, - SIZE_16 = 0xD2, - SIZE_32 = 0xD3, - SIZE_64 = 0xD4, - } size; - - // TODO: Make it clearer which bits of this member are used for sizes other than 32 bits - u64 value; -}; - -struct CTStreamElement { - CTStreamElementType type; - - union { - CTMemoryLoad memory_load; - CTRegisterWrite register_write; - }; -}; - -#pragma pack() -} // namespace CiTrace diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp deleted file mode 100644 index 73cacb47f..000000000 --- a/src/core/tracer/recorder.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstring> -#include "common/assert.h" -#include "common/file_util.h" -#include "common/logging/log.h" -#include "core/tracer/recorder.h" - -namespace CiTrace { - -Recorder::Recorder(const InitialState& initial_state) : initial_state(initial_state) {} - -void Recorder::Finish(const std::string& filename) { - // Setup CiTrace header - CTHeader header; - std::memcpy(header.magic, CTHeader::ExpectedMagicWord(), 4); - header.version = CTHeader::ExpectedVersion(); - header.header_size = sizeof(CTHeader); - - // Calculate file offsets - auto& initial = header.initial_state_offsets; - - initial.gpu_registers_size = static_cast<u32>(initial_state.gpu_registers.size()); - initial.lcd_registers_size = static_cast<u32>(initial_state.lcd_registers.size()); - initial.pica_registers_size = static_cast<u32>(initial_state.pica_registers.size()); - initial.default_attributes_size = static_cast<u32>(initial_state.default_attributes.size()); - initial.vs_program_binary_size = static_cast<u32>(initial_state.vs_program_binary.size()); - initial.vs_swizzle_data_size = static_cast<u32>(initial_state.vs_swizzle_data.size()); - initial.vs_float_uniforms_size = static_cast<u32>(initial_state.vs_float_uniforms.size()); - initial.gs_program_binary_size = static_cast<u32>(initial_state.gs_program_binary.size()); - initial.gs_swizzle_data_size = static_cast<u32>(initial_state.gs_swizzle_data.size()); - initial.gs_float_uniforms_size = static_cast<u32>(initial_state.gs_float_uniforms.size()); - header.stream_size = static_cast<u32>(stream.size()); - - initial.gpu_registers = sizeof(header); - initial.lcd_registers = initial.gpu_registers + initial.gpu_registers_size * sizeof(u32); - initial.pica_registers = initial.lcd_registers + initial.lcd_registers_size * sizeof(u32); - ; - initial.default_attributes = initial.pica_registers + initial.pica_registers_size * sizeof(u32); - initial.vs_program_binary = - initial.default_attributes + initial.default_attributes_size * sizeof(u32); - initial.vs_swizzle_data = - initial.vs_program_binary + initial.vs_program_binary_size * sizeof(u32); - initial.vs_float_uniforms = - initial.vs_swizzle_data + initial.vs_swizzle_data_size * sizeof(u32); - initial.gs_program_binary = - initial.vs_float_uniforms + initial.vs_float_uniforms_size * sizeof(u32); - initial.gs_swizzle_data = - initial.gs_program_binary + initial.gs_program_binary_size * sizeof(u32); - initial.gs_float_uniforms = - initial.gs_swizzle_data + initial.gs_swizzle_data_size * sizeof(u32); - header.stream_offset = initial.gs_float_uniforms + initial.gs_float_uniforms_size * sizeof(u32); - - // Iterate through stream elements, update relevant stream element data - for (auto& stream_element : stream) { - switch (stream_element.data.type) { - case MemoryLoad: { - auto& file_offset = memory_regions[stream_element.hash]; - if (!stream_element.uses_existing_data) { - file_offset = header.stream_offset; - } - stream_element.data.memory_load.file_offset = file_offset; - break; - } - - default: - // Other commands don't use any extra data - DEBUG_ASSERT(stream_element.extra_data.size() == 0); - break; - } - header.stream_offset += static_cast<u32>(stream_element.extra_data.size()); - } - - try { - // Open file and write header - FileUtil::IOFile file(filename, "wb"); - std::size_t written = file.WriteObject(header); - if (written != 1 || file.Tell() != initial.gpu_registers) - throw "Failed to write header"; - - // Write initial state - written = - file.WriteArray(initial_state.gpu_registers.data(), initial_state.gpu_registers.size()); - if (written != initial_state.gpu_registers.size() || file.Tell() != initial.lcd_registers) - throw "Failed to write GPU registers"; - - written = - file.WriteArray(initial_state.lcd_registers.data(), initial_state.lcd_registers.size()); - if (written != initial_state.lcd_registers.size() || file.Tell() != initial.pica_registers) - throw "Failed to write LCD registers"; - - written = file.WriteArray(initial_state.pica_registers.data(), - initial_state.pica_registers.size()); - if (written != initial_state.pica_registers.size() || - file.Tell() != initial.default_attributes) - throw "Failed to write Pica registers"; - - written = file.WriteArray(initial_state.default_attributes.data(), - initial_state.default_attributes.size()); - if (written != initial_state.default_attributes.size() || - file.Tell() != initial.vs_program_binary) - throw "Failed to write default vertex attributes"; - - written = file.WriteArray(initial_state.vs_program_binary.data(), - initial_state.vs_program_binary.size()); - if (written != initial_state.vs_program_binary.size() || - file.Tell() != initial.vs_swizzle_data) - throw "Failed to write vertex shader program binary"; - - written = file.WriteArray(initial_state.vs_swizzle_data.data(), - initial_state.vs_swizzle_data.size()); - if (written != initial_state.vs_swizzle_data.size() || - file.Tell() != initial.vs_float_uniforms) - throw "Failed to write vertex shader swizzle data"; - - written = file.WriteArray(initial_state.vs_float_uniforms.data(), - initial_state.vs_float_uniforms.size()); - if (written != initial_state.vs_float_uniforms.size() || - file.Tell() != initial.gs_program_binary) - throw "Failed to write vertex shader float uniforms"; - - written = file.WriteArray(initial_state.gs_program_binary.data(), - initial_state.gs_program_binary.size()); - if (written != initial_state.gs_program_binary.size() || - file.Tell() != initial.gs_swizzle_data) - throw "Failed to write geomtry shader program binary"; - - written = file.WriteArray(initial_state.gs_swizzle_data.data(), - initial_state.gs_swizzle_data.size()); - if (written != initial_state.gs_swizzle_data.size() || - file.Tell() != initial.gs_float_uniforms) - throw "Failed to write geometry shader swizzle data"; - - written = file.WriteArray(initial_state.gs_float_uniforms.data(), - initial_state.gs_float_uniforms.size()); - if (written != initial_state.gs_float_uniforms.size() || - file.Tell() != initial.gs_float_uniforms + sizeof(u32) * initial.gs_float_uniforms_size) - throw "Failed to write geometry shader float uniforms"; - - // Iterate through stream elements, write "extra data" - for (const auto& stream_element : stream) { - if (stream_element.extra_data.size() == 0) - continue; - - written = - file.WriteBytes(stream_element.extra_data.data(), stream_element.extra_data.size()); - if (written != stream_element.extra_data.size()) - throw "Failed to write extra data"; - } - - if (file.Tell() != header.stream_offset) - throw "Unexpected end of extra data"; - - // Write actual stream elements - for (const auto& stream_element : stream) { - if (1 != file.WriteObject(stream_element.data)) - throw "Failed to write stream element"; - } - } catch (const char* str) { - LOG_ERROR(HW_GPU, "Writing CiTrace file failed: {}", str); - } -} - -void Recorder::FrameFinished() { - stream.push_back({{FrameMarker}}); -} - -void Recorder::MemoryAccessed(const u8* data, u32 size, u32 physical_address) { - StreamElement element = {{MemoryLoad}}; - element.data.memory_load.size = size; - element.data.memory_load.physical_address = physical_address; - - // Compute hash over given memory region to check if the contents are already stored internally - boost::crc_32_type result; - result.process_bytes(data, size); - element.hash = result.checksum(); - - element.uses_existing_data = (memory_regions.find(element.hash) != memory_regions.end()); - if (!element.uses_existing_data) { - element.extra_data.resize(size); - memcpy(element.extra_data.data(), data, size); - memory_regions.insert({element.hash, 0}); // file offset will be initialized in Finish() - } - - stream.push_back(element); -} - -template <typename T> -void Recorder::RegisterWritten(u32 physical_address, T value) { - StreamElement element = {{RegisterWrite}}; - element.data.register_write.size = - (sizeof(T) == 1) ? CTRegisterWrite::SIZE_8 - : (sizeof(T) == 2) ? CTRegisterWrite::SIZE_16 - : (sizeof(T) == 4) ? CTRegisterWrite::SIZE_32 - : CTRegisterWrite::SIZE_64; - element.data.register_write.physical_address = physical_address; - element.data.register_write.value = value; - - stream.push_back(element); -} - -template void Recorder::RegisterWritten(u32, u8); -template void Recorder::RegisterWritten(u32, u16); -template void Recorder::RegisterWritten(u32, u32); -template void Recorder::RegisterWritten(u32, u64); -} // namespace CiTrace diff --git a/src/core/tracer/recorder.h b/src/core/tracer/recorder.h deleted file mode 100644 index e1cefd5fe..000000000 --- a/src/core/tracer/recorder.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> -#include <unordered_map> -#include <vector> -#include <boost/crc.hpp> -#include "common/common_types.h" -#include "core/tracer/citrace.h" - -namespace CiTrace { - -class Recorder { -public: - struct InitialState { - std::vector<u32> gpu_registers; - std::vector<u32> lcd_registers; - std::vector<u32> pica_registers; - std::vector<u32> default_attributes; - std::vector<u32> vs_program_binary; - std::vector<u32> vs_swizzle_data; - std::vector<u32> vs_float_uniforms; - std::vector<u32> gs_program_binary; - std::vector<u32> gs_swizzle_data; - std::vector<u32> gs_float_uniforms; - }; - - /** - * Recorder constructor - * @param initial_state Initial recorder state - */ - explicit Recorder(const InitialState& initial_state); - - /// Finish recording of this Citrace and save it using the given filename. - void Finish(const std::string& filename); - - /// Mark end of a frame - void FrameFinished(); - - /** - * Store a copy of the given memory range in the recording. - * @note Use this whenever the GPU is about to access a particular memory region. - * @note The implementation will make sure to minimize redundant memory updates. - */ - void MemoryAccessed(const u8* data, u32 size, u32 physical_address); - - /** - * Record a register write. - * @note Use this whenever a GPU-related MMIO register has been written to. - */ - template <typename T> - void RegisterWritten(u32 physical_address, T value); - -private: - // Initial state of recording start - InitialState initial_state; - - // Command stream - struct StreamElement { - CTStreamElement data; - - /** - * Extra data to store along "core" data. - * This is e.g. used for data used in MemoryUpdates. - */ - std::vector<u8> extra_data; - - /// Optional CRC hash (e.g. for hashing memory regions) - boost::crc_32_type::value_type hash; - - /// If true, refer to data already written to the output file instead of extra_data - bool uses_existing_data; - }; - - std::vector<StreamElement> stream; - - /** - * Internal cache which maps hashes of memory contents to file offsets at which those memory - * contents are stored. - */ - std::unordered_map<boost::crc_32_type::value_type /*hash*/, u32 /*file_offset*/> memory_regions; -}; - -} // namespace CiTrace diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 58af41f6e..ac7ae3e52 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -16,7 +16,8 @@ namespace ArmTests { TestEnvironment::TestEnvironment(bool mutable_memory_) : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} { - auto process = Kernel::Process::Create(Core::System::GetInstance(), ""); + auto process = Kernel::Process::Create(Core::System::GetInstance(), "", + Kernel::Process::ProcessType::Userland); page_table = &process->VMManager().page_table; std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr); diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 2d4caa08d..f8b67cbe1 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(video_core STATIC dma_pusher.h debug_utils/debug_utils.cpp debug_utils/debug_utils.h + engines/const_buffer_info.h engines/engine_upload.cpp engines/engine_upload.h engines/fermi_2d.cpp diff --git a/src/video_core/engines/const_buffer_info.h b/src/video_core/engines/const_buffer_info.h new file mode 100644 index 000000000..d8f672462 --- /dev/null +++ b/src/video_core/engines/const_buffer_info.h @@ -0,0 +1,17 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Tegra::Engines { + +struct ConstBufferInfo { + GPUVAddr address; + u32 size; + bool enabled; +}; + +} // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 39968d403..08d553696 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -396,12 +396,10 @@ void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) { auto& shader = state.shader_stages[static_cast<std::size_t>(stage)]; auto& bind_data = regs.cb_bind[static_cast<std::size_t>(stage)]; - auto& buffer = shader.const_buffers[bind_data.index]; - ASSERT(bind_data.index < Regs::MaxConstBuffers); + auto& buffer = shader.const_buffers[bind_data.index]; buffer.enabled = bind_data.valid.Value() != 0; - buffer.index = bind_data.index; buffer.address = regs.const_buffer.BufferAddress(); buffer.size = regs.const_buffer.cb_size; } diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index f342c78e6..13e314944 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -15,6 +15,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/math_util.h" +#include "video_core/engines/const_buffer_info.h" #include "video_core/engines/engine_upload.h" #include "video_core/gpu.h" #include "video_core/macro_interpreter.h" @@ -1112,13 +1113,6 @@ public: static_assert(std::is_trivially_copyable_v<Regs>, "Maxwell3D Regs must be trivially copyable"); struct State { - struct ConstBufferInfo { - GPUVAddr address; - u32 index; - u32 size; - bool enabled; - }; - struct ShaderStageInfo { std::array<ConstBufferInfo, Regs::MaxConstBuffers> const_buffers; }; diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 65a88b06c..a48e14d2e 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -43,8 +43,9 @@ bool Device::TestVariableAoffi() { // This is a unit test, please ignore me on apitrace bug reports. uniform sampler2D tex; uniform ivec2 variable_offset; +out vec4 output_attribute; void main() { - gl_Position = textureOffset(tex, vec2(0), variable_offset); + output_attribute = textureOffset(tex, vec2(0), variable_offset); } )"; const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &AOFFI_TEST)}; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index ca410287a..d77426067 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -322,9 +322,9 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { } const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage); - SetupConstBuffers(stage_enum, shader, program_handle, base_bindings); - SetupGlobalRegions(stage_enum, shader, program_handle, base_bindings); - SetupTextures(stage_enum, shader, program_handle, base_bindings); + SetupDrawConstBuffers(stage_enum, shader); + SetupGlobalRegions(stage_enum, shader); + SetupTextures(stage_enum, shader, base_bindings); // Workaround for Intel drivers. // When a clip distance is enabled but not set in the shader it crops parts of the screen @@ -776,57 +776,55 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config, return true; } -void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - const Shader& shader, GLuint program_handle, - BaseBindings base_bindings) { +void RasterizerOpenGL::SetupDrawConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader) { MICROPROFILE_SCOPE(OpenGL_UBO); - const auto& gpu = system.GPU(); - const auto& maxwell3d = gpu.Maxwell3D(); - const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)]; + const auto stage_index = static_cast<std::size_t>(stage); + const auto& shader_stage = system.GPU().Maxwell3D().state.shader_stages[stage_index]; const auto& entries = shader->GetShaderEntries().const_buffers; // Upload only the enabled buffers from the 16 constbuffers of each shader stage for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { - const auto& used_buffer = entries[bindpoint]; - const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()]; - - if (!buffer.enabled) { - // Set values to zero to unbind buffers - bind_ubo_pushbuffer.Push(0, 0, 0); - continue; - } + const auto& entry = entries[bindpoint]; + SetupConstBuffer(shader_stage.const_buffers[entry.GetIndex()], entry); + } +} - std::size_t size = 0; +void RasterizerOpenGL::SetupConstBuffer(const Tegra::Engines::ConstBufferInfo& buffer, + const GLShader::ConstBufferEntry& entry) { + if (!buffer.enabled) { + // Set values to zero to unbind buffers + bind_ubo_pushbuffer.Push(0, 0, 0); + return; + } - if (used_buffer.IsIndirect()) { - // Buffer is accessed indirectly, so upload the entire thing - size = buffer.size; + std::size_t size; + if (entry.IsIndirect()) { + // Buffer is accessed indirectly, so upload the entire thing + size = buffer.size; - if (size > MaxConstbufferSize) { - LOG_WARNING(Render_OpenGL, "Indirect constbuffer size {} exceeds maximum {}", size, - MaxConstbufferSize); - size = MaxConstbufferSize; - } - } else { - // Buffer is accessed directly, upload just what we use - size = used_buffer.GetSize(); + if (size > MaxConstbufferSize) { + LOG_WARNING(Render_OpenGL, "Indirect constbuffer size {} exceeds maximum {}", size, + MaxConstbufferSize); + size = MaxConstbufferSize; } + } else { + // Buffer is accessed directly, upload just what we use + size = entry.GetSize(); + } - // Align the actual size so it ends up being a multiple of vec4 to meet the OpenGL std140 - // UBO alignment requirements. - size = Common::AlignUp(size, sizeof(GLvec4)); - ASSERT_MSG(size <= MaxConstbufferSize, "Constbuffer too big"); - - const GLintptr const_buffer_offset = - buffer_cache.UploadMemory(buffer.address, size, device.GetUniformBufferAlignment()); + // Align the actual size so it ends up being a multiple of vec4 to meet the OpenGL std140 + // UBO alignment requirements. + size = Common::AlignUp(size, sizeof(GLvec4)); + ASSERT_MSG(size <= MaxConstbufferSize, "Constant buffer is too big"); - bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), const_buffer_offset, size); - } + const std::size_t alignment = device.GetUniformBufferAlignment(); + const GLintptr offset = buffer_cache.UploadMemory(buffer.address, size, alignment); + bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), offset, size); } void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - const Shader& shader, GLenum primitive_mode, - BaseBindings base_bindings) { + const Shader& shader) { const auto& entries = shader->GetShaderEntries().global_memory_entries; for (std::size_t bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& entry{entries[bindpoint]}; @@ -840,7 +838,7 @@ void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::Shade } void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, - GLuint program_handle, BaseBindings base_bindings) { + BaseBindings base_bindings) { MICROPROFILE_SCOPE(OpenGL_Texture); const auto& gpu = system.GPU(); const auto& maxwell3d = gpu.Maxwell3D(); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 2817f65c9..f7671ff5d 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -17,6 +17,7 @@ #include <glad/glad.h> #include "common/common_types.h" +#include "video_core/engines/const_buffer_info.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/rasterizer_cache.h" #include "video_core/rasterizer_interface.h" @@ -27,6 +28,7 @@ #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_sampler_cache.h" #include "video_core/renderer_opengl/gl_shader_cache.h" +#include "video_core/renderer_opengl/gl_shader_decompiler.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/utils.h" @@ -105,17 +107,20 @@ private: bool preserve_contents = true, std::optional<std::size_t> single_color_target = {}); /// Configures the current constbuffers to use for the draw command. - void SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader, - GLuint program_handle, BaseBindings base_bindings); + void SetupDrawConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader); + + /// Configures a constant buffer. + void SetupConstBuffer(const Tegra::Engines::ConstBufferInfo& buffer, + const GLShader::ConstBufferEntry& entry); /// Configures the current global memory entries to use for the draw command. void SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - const Shader& shader, GLenum primitive_mode, - BaseBindings base_bindings); + const Shader& shader); /// Configures the current textures to use for the draw command. void SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader, - GLuint program_handle, BaseBindings base_bindings); + BaseBindings base_bindings); /// Syncs the viewport and depth range to match the guest state void SyncViewport(OpenGLState& current_state); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 739477cc9..7dc2e0560 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -143,6 +143,24 @@ u32 GetGenericAttributeIndex(Attribute::Index index) { return static_cast<u32>(index) - static_cast<u32>(Attribute::Index::Attribute_0); } +constexpr const char* GetFlowStackPrefix(MetaStackClass stack) { + switch (stack) { + case MetaStackClass::Ssy: + return "ssy"; + case MetaStackClass::Pbk: + return "pbk"; + } + return {}; +} + +std::string FlowStackName(MetaStackClass stack) { + return fmt::format("{}_flow_stack", GetFlowStackPrefix(stack)); +} + +std::string FlowStackTopName(MetaStackClass stack) { + return fmt::format("{}_flow_stack_top", GetFlowStackPrefix(stack)); +} + class GLSLDecompiler final { public: explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ShaderStage stage, @@ -173,8 +191,10 @@ public: // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems // unlikely that shaders will use 20 nested SSYs and PBKs. constexpr u32 FLOW_STACK_SIZE = 20; - code.AddLine("uint flow_stack[{}];", FLOW_STACK_SIZE); - code.AddLine("uint flow_stack_top = 0u;"); + for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { + code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); + code.AddLine("uint {} = 0u;", FlowStackTopName(stack)); + } code.AddLine("while (true) {{"); ++code.scope; @@ -1438,15 +1458,18 @@ private: } std::string PushFlowStack(Operation operation) { + const auto stack = std::get<MetaStackClass>(operation.GetMeta()); const auto target = std::get_if<ImmediateNode>(&*operation[0]); UNIMPLEMENTED_IF(!target); - code.AddLine("flow_stack[flow_stack_top++] = 0x{:x}u;", target->GetValue()); + code.AddLine("{}[{}++] = 0x{:x}u;", FlowStackName(stack), FlowStackTopName(stack), + target->GetValue()); return {}; } std::string PopFlowStack(Operation operation) { - code.AddLine("jmp_to = flow_stack[--flow_stack_top];"); + const auto stack = std::get<MetaStackClass>(operation.GetMeta()); + code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); code.AddLine("break;"); return {}; } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 3451d321d..aafd6f31b 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -18,7 +18,6 @@ #include "core/perf_stats.h" #include "core/settings.h" #include "core/telemetry_session.h" -#include "core/tracer/recorder.h" #include "video_core/morton.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/renderer_opengl.h" diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index 547883425..33ad9764a 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -132,20 +132,16 @@ public: branch_labels.push_back(label); } - // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely - // that shaders will use 20 nested SSYs and PBKs. - constexpr u32 FLOW_STACK_SIZE = 20; - const Id flow_stack_type = TypeArray(t_uint, Constant(t_uint, FLOW_STACK_SIZE)); jmp_to = Emit(OpVariable(TypePointer(spv::StorageClass::Function, t_uint), spv::StorageClass::Function, Constant(t_uint, first_address))); - flow_stack = Emit(OpVariable(TypePointer(spv::StorageClass::Function, flow_stack_type), - spv::StorageClass::Function, ConstantNull(flow_stack_type))); - flow_stack_top = - Emit(OpVariable(t_func_uint, spv::StorageClass::Function, Constant(t_uint, 0))); + std::tie(ssy_flow_stack, ssy_flow_stack_top) = CreateFlowStack(); + std::tie(pbk_flow_stack, pbk_flow_stack_top) = CreateFlowStack(); Name(jmp_to, "jmp_to"); - Name(flow_stack, "flow_stack"); - Name(flow_stack_top, "flow_stack_top"); + Name(ssy_flow_stack, "ssy_flow_stack"); + Name(ssy_flow_stack_top, "ssy_flow_stack_top"); + Name(pbk_flow_stack, "pbk_flow_stack"); + Name(pbk_flow_stack_top, "pbk_flow_stack_top"); Emit(OpBranch(loop_label)); Emit(loop_label); @@ -952,6 +948,7 @@ private: const auto target = std::get_if<ImmediateNode>(&*operation[0]); ASSERT(target); + const auto [flow_stack, flow_stack_top] = GetFlowStack(operation); const Id current = Emit(OpLoad(t_uint, flow_stack_top)); const Id next = Emit(OpIAdd(t_uint, current, Constant(t_uint, 1))); const Id access = Emit(OpAccessChain(t_func_uint, flow_stack, current)); @@ -962,6 +959,7 @@ private: } Id PopFlowStack(Operation operation) { + const auto [flow_stack, flow_stack_top] = GetFlowStack(operation); const Id current = Emit(OpLoad(t_uint, flow_stack_top)); const Id previous = Emit(OpISub(t_uint, current, Constant(t_uint, 1))); const Id access = Emit(OpAccessChain(t_func_uint, flow_stack, previous)); @@ -1172,6 +1170,31 @@ private: Emit(skip_label); } + std::tuple<Id, Id> CreateFlowStack() { + // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely + // that shaders will use 20 nested SSYs and PBKs. + constexpr u32 FLOW_STACK_SIZE = 20; + constexpr auto storage_class = spv::StorageClass::Function; + + const Id flow_stack_type = TypeArray(t_uint, Constant(t_uint, FLOW_STACK_SIZE)); + const Id stack = Emit(OpVariable(TypePointer(storage_class, flow_stack_type), storage_class, + ConstantNull(flow_stack_type))); + const Id top = Emit(OpVariable(t_func_uint, storage_class, Constant(t_uint, 0))); + return std::tie(stack, top); + } + + std::pair<Id, Id> GetFlowStack(Operation operation) { + const auto stack_class = std::get<MetaStackClass>(operation.GetMeta()); + switch (stack_class) { + case MetaStackClass::Ssy: + return {ssy_flow_stack, ssy_flow_stack_top}; + case MetaStackClass::Pbk: + return {pbk_flow_stack, pbk_flow_stack_top}; + } + UNREACHABLE(); + return {}; + } + static constexpr OperationDecompilersArray operation_decompilers = { &SPIRVDecompiler::Assign, @@ -1414,8 +1437,10 @@ private: Id execute_function{}; Id jmp_to{}; - Id flow_stack_top{}; - Id flow_stack{}; + Id ssy_flow_stack_top{}; + Id pbk_flow_stack_top{}; + Id ssy_flow_stack{}; + Id pbk_flow_stack{}; Id continue_label{}; std::map<u32, Id> labels; }; diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp index 6fc07f213..d46a8ab82 100644 --- a/src/video_core/shader/decode/other.cpp +++ b/src/video_core/shader/decode/other.cpp @@ -109,22 +109,20 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, "Constant buffer flow is not supported"); - // The SSY opcode tells the GPU where to re-converge divergent execution paths, it sets the - // target of the jump that the SYNC instruction will make. The SSY opcode has a similar - // structure to the BRA opcode. + // The SSY opcode tells the GPU where to re-converge divergent execution paths with SYNC. const u32 target = pc + instr.bra.GetBranchTarget(); - bb.push_back(Operation(OperationCode::PushFlowStack, Immediate(target))); + bb.push_back( + Operation(OperationCode::PushFlowStack, MetaStackClass::Ssy, Immediate(target))); break; } case OpCode::Id::PBK: { UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, "Constant buffer PBK is not supported"); - // PBK pushes to a stack the address where BRK will jump to. This shares stack with SSY but - // using SYNC on a PBK address will kill the shader execution. We don't emulate this because - // it's very unlikely a driver will emit such invalid shader. + // PBK pushes to a stack the address where BRK will jump to. const u32 target = pc + instr.bra.GetBranchTarget(); - bb.push_back(Operation(OperationCode::PushFlowStack, Immediate(target))); + bb.push_back( + Operation(OperationCode::PushFlowStack, MetaStackClass::Pbk, Immediate(target))); break; } case OpCode::Id::SYNC: { @@ -133,7 +131,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { static_cast<u32>(cc)); // The SYNC opcode jumps to the address previously set by the SSY opcode - bb.push_back(Operation(OperationCode::PopFlowStack)); + bb.push_back(Operation(OperationCode::PopFlowStack, MetaStackClass::Ssy)); break; } case OpCode::Id::BRK: { @@ -142,7 +140,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { static_cast<u32>(cc)); // The BRK opcode jumps to the address previously set by the PBK opcode - bb.push_back(Operation(OperationCode::PopFlowStack)); + bb.push_back(Operation(OperationCode::PopFlowStack, MetaStackClass::Pbk)); break; } case OpCode::Id::IPA: { diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index c002f90f9..3cfb911bb 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -174,6 +174,11 @@ enum class InternalFlag { Amount = 4, }; +enum class MetaStackClass { + Ssy, + Pbk, +}; + class OperationNode; class ConditionalNode; class GprNode; @@ -285,7 +290,7 @@ struct MetaTexture { }; /// Parameters that modify an operation but are not part of any particular operand -using Meta = std::variant<MetaArithmetic, MetaTexture, Tegra::Shader::HalfType>; +using Meta = std::variant<MetaArithmetic, MetaTexture, MetaStackClass, Tegra::Shader::HalfType>; /// Holds any kind of operation that can be done in the IR class OperationNode final { diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index ac80b2fa2..33f1c385d 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp @@ -87,8 +87,8 @@ QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { QtWebBrowser::~QtWebBrowser() = default; -void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) { +void QtWebBrowser::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) { this->unpack_romfs_callback = std::move(unpack_romfs_callback); this->finished_callback = std::move(finished_callback); diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h index 1a3d67353..b38437e46 100644 --- a/src/yuzu/applets/web_browser.h +++ b/src/yuzu/applets/web_browser.h @@ -37,8 +37,8 @@ public: explicit QtWebBrowser(GMainWindow& main_window); ~QtWebBrowser() override; - void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) override; + void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) override; signals: void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 10e5c5c38..5a456e603 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -473,6 +473,8 @@ void Config::ReadDebuggingValues() { ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString(); Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool(); + Settings::values.reporting_services = + ReadSetting(QStringLiteral("reporting_services"), false).toBool(); qt_config->endGroup(); } @@ -691,7 +693,7 @@ void Config::ReadValues() { ReadDataStorageValues(); ReadSystemValues(); ReadMiscellaneousValues(); - ReadDebugValues(); + ReadDebuggingValues(); ReadWebServiceValues(); ReadDisabledAddOnValues(); ReadUIValues(); diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index efc2bedfd..63426fe4f 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -36,6 +36,7 @@ void ConfigureDebug::SetConfiguration() { ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); ui->dump_exefs->setChecked(Settings::values.dump_exefs); ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso); + ui->reporting_services->setChecked(Settings::values.reporting_services); } void ConfigureDebug::ApplyConfiguration() { @@ -46,6 +47,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); Settings::values.dump_exefs = ui->dump_exefs->isChecked(); Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked(); + Settings::values.reporting_services = ui->reporting_services->isChecked(); Debugger::ToggleConsole(); Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 5ca9ce0e6..4a7e3dc3d 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -155,6 +155,28 @@ </property> </widget> </item> + <item> + <widget class="QCheckBox" name="reporting_services"> + <property name="text"> + <string>Enable Verbose Reporting Services</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>This will be reset automatically when yuzu closes.</string> + </property> + <property name="indent"> + <number>20</number> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 8378451c8..efffd8487 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>ConfigureInput</string> + <string>Custom Input Settings</string> </property> <layout class="QVBoxLayout" name="verticalLayout_5"> <item> diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 83d675773..1885587af 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -468,8 +468,7 @@ void GameList::LoadInterfaceLayout() { const QStringList GameList::supported_file_extensions = { QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), - QStringLiteral("xci"), QStringLiteral("nsp"), -}; + QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; void GameList::RefreshGameDirectory() { if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) { diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index cd32623b4..47e46f574 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -814,11 +814,13 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetGPUDebugContext(debug_context); system.SetAppletFrontendSet({ - std::make_unique<QtErrorDisplay>(*this), - nullptr, - std::make_unique<QtProfileSelector>(*this), - std::make_unique<QtSoftwareKeyboard>(*this), - std::make_unique<QtWebBrowser>(*this), + nullptr, // Parental Controls + std::make_unique<QtErrorDisplay>(*this), // + nullptr, // Photo Viewer + std::make_unique<QtProfileSelector>(*this), // + std::make_unique<QtSoftwareKeyboard>(*this), // + std::make_unique<QtWebBrowser>(*this), // + nullptr, // E-Commerce }); const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; @@ -1944,7 +1946,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { errors + tr("<br><br>You can get all of these and dump all of your games easily by " "following <a href='https://yuzu-emu.org/help/quickstart/'>the " - "quickstart guide</a>. Alternatively, you can use another method of dumping" + "quickstart guide</a>. Alternatively, you can use another method of dumping " "to obtain all of your keys.")); } diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index f3817bb87..9ac92e937 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -381,6 +381,8 @@ void Config::ReadValues() { Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", ""); Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); + Settings::values.reporting_services = + sdl2_config->GetBoolean("Debugging", "reporting_services", false); const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); std::stringstream ss(title_list); diff --git a/src/yuzu_tester/CMakeLists.txt b/src/yuzu_tester/CMakeLists.txt new file mode 100644 index 000000000..06c2ee011 --- /dev/null +++ b/src/yuzu_tester/CMakeLists.txt @@ -0,0 +1,34 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) + +add_executable(yuzu-tester + config.cpp + config.h + default_ini.h + emu_window/emu_window_sdl2_hide.cpp + emu_window/emu_window_sdl2_hide.h + resource.h + service/yuzutest.cpp + service/yuzutest.h + yuzu.cpp + yuzu.rc +) + +create_target_directory_groups(yuzu-tester) + +target_link_libraries(yuzu-tester PRIVATE common core input_common) +target_link_libraries(yuzu-tester PRIVATE inih glad) +if (MSVC) + target_link_libraries(yuzu-tester PRIVATE getopt) +endif() +target_link_libraries(yuzu-tester PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads) + +if(UNIX AND NOT APPLE) + install(TARGETS yuzu-tester RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") +endif() + +if (MSVC) + include(CopyYuzuSDLDeps) + include(CopyYuzuUnicornDeps) + copy_yuzu_SDL_deps(yuzu-tester) + copy_yuzu_unicorn_deps(yuzu-tester) +endif() diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp new file mode 100644 index 000000000..d7e0d408d --- /dev/null +++ b/src/yuzu_tester/config.cpp @@ -0,0 +1,184 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <sstream> +#include <SDL.h> +#include <inih/cpp/INIReader.h> +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/param_package.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "yuzu_tester/config.h" +#include "yuzu_tester/default_ini.h" + +Config::Config() { + // TODO: Don't hardcode the path; let the frontend decide where to put the config files. + sdl2_config_loc = + FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-tester-config.ini"; + sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); + + Reload(); +} + +Config::~Config() = default; + +bool Config::LoadINI(const std::string& default_contents, bool retry) { + const char* location = this->sdl2_config_loc.c_str(); + if (sdl2_config->ParseError() < 0) { + if (retry) { + LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); + FileUtil::CreateFullPath(location); + FileUtil::WriteStringToFile(true, default_contents, location); + sdl2_config = std::make_unique<INIReader>(location); // Reopen file + + return LoadINI(default_contents, false); + } + LOG_ERROR(Config, "Failed."); + return false; + } + LOG_INFO(Config, "Successfully loaded {}", location); + return true; +} + +void Config::ReadValues() { + // Controls + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + Settings::values.players[p].buttons[i] = ""; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + Settings::values.players[p].analogs[i] = ""; + } + } + + Settings::values.mouse_enabled = false; + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + Settings::values.mouse_buttons[i] = ""; + } + + Settings::values.motion_device = ""; + + Settings::values.keyboard_enabled = false; + + Settings::values.debug_pad_enabled = false; + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + Settings::values.debug_pad_buttons[i] = ""; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + Settings::values.debug_pad_analogs[i] = ""; + } + + Settings::values.touchscreen.enabled = ""; + Settings::values.touchscreen.device = ""; + Settings::values.touchscreen.finger = 0; + Settings::values.touchscreen.rotation_angle = 0; + Settings::values.touchscreen.diameter_x = 15; + Settings::values.touchscreen.diameter_y = 15; + + // Data Storage + Settings::values.use_virtual_sd = + sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, + sdl2_config->Get("Data Storage", "nand_directory", + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, + sdl2_config->Get("Data Storage", "sdmc_directory", + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); + + // System + Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); + const auto size = sdl2_config->GetInteger("System", "users_size", 0); + + Settings::values.current_user = std::clamp<int>( + sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); + + const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); + if (rng_seed_enabled) { + Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0); + } else { + Settings::values.rng_seed = std::nullopt; + } + + const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); + if (custom_rtc_enabled) { + Settings::values.custom_rtc = + std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); + } else { + Settings::values.custom_rtc = std::nullopt; + } + + // Core + Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); + Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); + + // Renderer + Settings::values.resolution_factor = + static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0)); + Settings::values.use_frame_limit = false; + Settings::values.frame_limit = 100; + Settings::values.use_disk_shader_cache = + sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false); + Settings::values.use_accurate_gpu_emulation = + sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); + Settings::values.use_asynchronous_gpu_emulation = + sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); + + Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); + Settings::values.bg_green = + static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)); + Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)); + + // Audio + Settings::values.sink_id = "null"; + Settings::values.enable_audio_stretching = false; + Settings::values.audio_device_id = "auto"; + Settings::values.volume = 0; + + Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); + + // Miscellaneous + Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); + Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); + + // Debugging + Settings::values.use_gdbstub = false; + Settings::values.program_args = ""; + Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); + Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); + + const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); + std::stringstream ss(title_list); + std::string line; + while (std::getline(ss, line, '|')) { + const auto title_id = std::stoul(line, nullptr, 16); + const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); + + std::stringstream inner_ss(disabled_list); + std::string inner_line; + std::vector<std::string> out; + while (std::getline(inner_ss, inner_line, '|')) { + out.push_back(inner_line); + } + + Settings::values.disabled_addons.insert_or_assign(title_id, out); + } + + // Web Service + Settings::values.enable_telemetry = + sdl2_config->GetBoolean("WebService", "enable_telemetry", true); + Settings::values.web_api_url = + sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org"); + Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", ""); + Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", ""); +} + +void Config::Reload() { + LoadINI(DefaultINI::sdl2_config_file); + ReadValues(); +} diff --git a/src/yuzu_tester/config.h b/src/yuzu_tester/config.h new file mode 100644 index 000000000..3b68e5bc9 --- /dev/null +++ b/src/yuzu_tester/config.h @@ -0,0 +1,24 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> + +class INIReader; + +class Config { + std::unique_ptr<INIReader> sdl2_config; + std::string sdl2_config_loc; + + bool LoadINI(const std::string& default_contents = "", bool retry = true); + void ReadValues(); + +public: + Config(); + ~Config(); + + void Reload(); +}; diff --git a/src/yuzu_tester/default_ini.h b/src/yuzu_tester/default_ini.h new file mode 100644 index 000000000..46a9960cd --- /dev/null +++ b/src/yuzu_tester/default_ini.h @@ -0,0 +1,150 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace DefaultINI { + +const char* sdl2_config_file = R"( +[Core] +# Whether to use the Just-In-Time (JIT) compiler for CPU emulation +# 0: Interpreter (slow), 1 (default): JIT (fast) +use_cpu_jit = + +# Whether to use multi-core for CPU emulation +# 0 (default): Disabled, 1: Enabled +use_multi_core= + +[Renderer] +# Whether to use software or hardware rendering. +# 0: Software, 1 (default): Hardware +use_hw_renderer = + +# Whether to use the Just-In-Time (JIT) compiler for shader emulation +# 0: Interpreter (slow), 1 (default): JIT (fast) +use_shader_jit = + +# Resolution scale factor +# 0: Auto (scales resolution to window size), 1: Native Switch screen resolution, Otherwise a scale +# factor for the Switch resolution +resolution_factor = + +# Whether to enable V-Sync (caps the framerate at 60FPS) or not. +# 0 (default): Off, 1: On +use_vsync = + +# Whether to use disk based shader cache +# 0 (default): Off, 1 : On +use_disk_shader_cache = + +# Whether to use accurate GPU emulation +# 0 (default): Off (fast), 1 : On (slow) +use_accurate_gpu_emulation = + +# Whether to use asynchronous GPU emulation +# 0 : Off (slow), 1 (default): On (fast) +use_asynchronous_gpu_emulation = + +# The clear color for the renderer. What shows up on the sides of the bottom screen. +# Must be in range of 0.0-1.0. Defaults to 1.0 for all. +bg_red = +bg_blue = +bg_green = + +[Layout] +# Layout for the screen inside the render window. +# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen +layout_option = + +# Toggle custom layout (using the settings below) on or off. +# 0 (default): Off, 1: On +custom_layout = + +# Screen placement when using Custom layout option +# 0x, 0y is the top left corner of the render window. +custom_top_left = +custom_top_top = +custom_top_right = +custom_top_bottom = +custom_bottom_left = +custom_bottom_top = +custom_bottom_right = +custom_bottom_bottom = + +# Swaps the prominent screen with the other screen. +# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen. +# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent +swap_screen = + +[Data Storage] +# Whether to create a virtual SD card. +# 1 (default): Yes, 0: No +use_virtual_sd = + +[System] +# Whether the system is docked +# 1: Yes, 0 (default): No +use_docked_mode = + +# Allow the use of NFC in games +# 1 (default): Yes, 0 : No +enable_nfc = + +# Sets the seed for the RNG generator built into the switch +# rng_seed will be ignored and randomly generated if rng_seed_enabled is false +rng_seed_enabled = +rng_seed = + +# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service +# This will auto-increment, with the time set being the time the game is started +# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used +custom_rtc_enabled = +custom_rtc = + +# Sets the account username, max length is 32 characters +# yuzu (default) +username = yuzu + +# Sets the systems language index +# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, +# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French, +# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese +language_index = + +# The system region that yuzu will use during emulation +# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan +region_value = + +[Miscellaneous] +# A filter which removes logs below a certain logging level. +# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical +log_filter = *:Trace + +[Debugging] +# Arguments to be passed to argv/argc in the emulated program. It is preferable to use the testing service datastring +program_args= +# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them +dump_exefs=false +# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them +dump_nso=false + +[WebService] +# Whether or not to enable telemetry +# 0: No, 1 (default): Yes +enable_telemetry = +# URL for Web API +web_api_url = https://api.yuzu-emu.org +# Username and token for yuzu Web Service +# See https://profile.yuzu-emu.org/ for more info +yuzu_username = +yuzu_token = + +[AddOns] +# Used to disable add-ons +# List of title IDs of games that will have add-ons disabled (separated by '|'): +title_ids = +# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|') +# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey +)"; +} diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp new file mode 100644 index 000000000..e7fe8decf --- /dev/null +++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp @@ -0,0 +1,122 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstdlib> +#include <string> +#define SDL_MAIN_HANDLED +#include <SDL.h> +#include <fmt/format.h> +#include <glad/glad.h> +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "yuzu_tester/emu_window/emu_window_sdl2_hide.h" + +bool EmuWindow_SDL2_Hide::SupportsRequiredGLExtensions() { + std::vector<std::string> unsupported_ext; + + if (!GLAD_GL_ARB_direct_state_access) + unsupported_ext.push_back("ARB_direct_state_access"); + if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) + unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); + if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) + unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); + if (!GLAD_GL_ARB_multi_bind) + unsupported_ext.push_back("ARB_multi_bind"); + + // Extensions required to support some texture formats. + if (!GLAD_GL_EXT_texture_compression_s3tc) + unsupported_ext.push_back("EXT_texture_compression_s3tc"); + if (!GLAD_GL_ARB_texture_compression_rgtc) + unsupported_ext.push_back("ARB_texture_compression_rgtc"); + if (!GLAD_GL_ARB_depth_buffer_float) + unsupported_ext.push_back("ARB_depth_buffer_float"); + + for (const std::string& ext : unsupported_ext) + LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext); + + return unsupported_ext.empty(); +} + +EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() { + // Initialize the window + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); + exit(1); + } + + InputCommon::Init(); + + SDL_SetMainReady(); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); + + std::string window_title = fmt::format("yuzu-tester {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); + render_window = SDL_CreateWindow(window_title.c_str(), + SDL_WINDOWPOS_UNDEFINED, // x position + SDL_WINDOWPOS_UNDEFINED, // y position + Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | + SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); + + if (render_window == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError()); + exit(1); + } + + gl_context = SDL_GL_CreateContext(render_window); + + if (gl_context == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError()); + exit(1); + } + + if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) { + LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError()); + exit(1); + } + + if (!SupportsRequiredGLExtensions()) { + LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting..."); + exit(1); + } + + SDL_PumpEvents(); + SDL_GL_SetSwapInterval(false); + LOG_INFO(Frontend, "yuzu-tester Version: {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); + Settings::LogSettings(); + + DoneCurrent(); +} + +EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() { + InputCommon::Shutdown(); + SDL_GL_DeleteContext(gl_context); + SDL_Quit(); +} + +void EmuWindow_SDL2_Hide::SwapBuffers() { + SDL_GL_SwapWindow(render_window); +} + +void EmuWindow_SDL2_Hide::PollEvents() {} + +void EmuWindow_SDL2_Hide::MakeCurrent() { + SDL_GL_MakeCurrent(render_window, gl_context); +} + +void EmuWindow_SDL2_Hide::DoneCurrent() { + SDL_GL_MakeCurrent(render_window, nullptr); +} diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h new file mode 100644 index 000000000..1a8953c75 --- /dev/null +++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h @@ -0,0 +1,41 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/frontend/emu_window.h" + +struct SDL_Window; + +class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow { +public: + explicit EmuWindow_SDL2_Hide(); + ~EmuWindow_SDL2_Hide(); + + /// Swap buffers to display the next frame + void SwapBuffers() override; + + /// Polls window events + void PollEvents() override; + + /// Makes the graphics context current for the caller thread + void MakeCurrent() override; + + /// Releases the GL context from the caller thread + void DoneCurrent() override; + + /// Whether the window is still open, and a close request hasn't yet been sent + bool IsOpen() const; + +private: + /// Whether the GPU and driver supports the OpenGL extension required + bool SupportsRequiredGLExtensions(); + + /// Internal SDL2 render window + SDL_Window* render_window; + + using SDL_GLContext = void*; + /// The OpenGL context associated with the window + SDL_GLContext gl_context; +}; diff --git a/src/yuzu_tester/resource.h b/src/yuzu_tester/resource.h new file mode 100644 index 000000000..df8e459e4 --- /dev/null +++ b/src/yuzu_tester/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by pcafe.rc +// +#define IDI_ICON3 103 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/yuzu_tester/service/yuzutest.cpp b/src/yuzu_tester/service/yuzutest.cpp new file mode 100644 index 000000000..85d3f436b --- /dev/null +++ b/src/yuzu_tester/service/yuzutest.cpp @@ -0,0 +1,112 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "common/string_util.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" +#include "yuzu_tester/service/yuzutest.h" + +namespace Service::Yuzu { + +constexpr u64 SERVICE_VERSION = 0x00000002; + +class YuzuTest final : public ServiceFramework<YuzuTest> { +public: + explicit YuzuTest(std::string data, + std::function<void(std::vector<TestResult>)> finish_callback) + : ServiceFramework{"yuzutest"}, data(std::move(data)), + finish_callback(std::move(finish_callback)) { + static const FunctionInfo functions[] = { + {0, &YuzuTest::Initialize, "Initialize"}, + {1, &YuzuTest::GetServiceVersion, "GetServiceVersion"}, + {2, &YuzuTest::GetData, "GetData"}, + {10, &YuzuTest::StartIndividual, "StartIndividual"}, + {20, &YuzuTest::FinishIndividual, "FinishIndividual"}, + {100, &YuzuTest::ExitProgram, "ExitProgram"}, + }; + + RegisterHandlers(functions); + } + +private: + void Initialize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Frontend, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetServiceVersion(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Frontend, "called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(SERVICE_VERSION); + } + + void GetData(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Frontend, "called"); + const auto size = ctx.GetWriteBufferSize(); + const auto write_size = std::min(size, data.size()); + ctx.WriteBuffer(data.data(), write_size); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(write_size); + } + + void StartIndividual(Kernel::HLERequestContext& ctx) { + const auto name_raw = ctx.ReadBuffer(); + + const auto name = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(name_raw.data()), name_raw.size()); + + LOG_DEBUG(Frontend, "called, name={}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void FinishIndividual(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto code = rp.PopRaw<u32>(); + + const auto result_data_raw = ctx.ReadBuffer(); + const auto test_name_raw = ctx.ReadBuffer(1); + + const auto data = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(result_data_raw.data()), result_data_raw.size()); + const auto test_name = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(test_name_raw.data()), test_name_raw.size()); + + LOG_INFO(Frontend, "called, result_code={:08X}, data={}, name={}", code, data, test_name); + + results.push_back({code, data, test_name}); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void ExitProgram(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Frontend, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + finish_callback(std::move(results)); + } + + std::string data; + + std::vector<TestResult> results; + std::function<void(std::vector<TestResult>)> finish_callback; +}; + +void InstallInterfaces(SM::ServiceManager& sm, std::string data, + std::function<void(std::vector<TestResult>)> finish_callback) { + std::make_shared<YuzuTest>(data, finish_callback)->InstallAsService(sm); +} + +} // namespace Service::Yuzu diff --git a/src/yuzu_tester/service/yuzutest.h b/src/yuzu_tester/service/yuzutest.h new file mode 100644 index 000000000..eca129c8c --- /dev/null +++ b/src/yuzu_tester/service/yuzutest.h @@ -0,0 +1,25 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <string> + +namespace Service::SM { +class ServiceManager; +} + +namespace Service::Yuzu { + +struct TestResult { + u32 code; + std::string data; + std::string name; +}; + +void InstallInterfaces(SM::ServiceManager& sm, std::string data, + std::function<void(std::vector<TestResult>)> finish_callback); + +} // namespace Service::Yuzu diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp new file mode 100644 index 000000000..b589c3de3 --- /dev/null +++ b/src/yuzu_tester/yuzu.cpp @@ -0,0 +1,267 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <iostream> +#include <memory> +#include <string> +#include <thread> + +#include <fmt/ostream.h> + +#include "common/common_paths.h" +#include "common/detached_tasks.h" +#include "common/file_util.h" +#include "common/logging/backend.h" +#include "common/logging/filter.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/scm_rev.h" +#include "common/scope_exit.h" +#include "common/string_util.h" +#include "common/telemetry.h" +#include "core/core.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/vfs_real.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "core/settings.h" +#include "core/telemetry_session.h" +#include "video_core/renderer_base.h" +#include "yuzu_tester/config.h" +#include "yuzu_tester/emu_window/emu_window_sdl2_hide.h" +#include "yuzu_tester/service/yuzutest.h" + +#ifdef _WIN32 +// windows.h needs to be included before shellapi.h +#include <windows.h> + +#include <shellapi.h> +#endif + +#undef _UNICODE +#include <getopt.h> +#ifndef _MSC_VER +#include <unistd.h> +#endif + +#ifdef _WIN32 +extern "C" { +// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable +// graphics +__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} +#endif + +static void PrintHelp(const char* argv0) { + std::cout << "Usage: " << argv0 + << " [options] <filename>\n" + "-h, --help Display this help and exit\n" + "-v, --version Output version information and exit\n" + "-d, --datastring Pass following string as data to test service command #2\n" + "-l, --log Log to console in addition to file (will log to file only " + "by default)\n"; +} + +static void PrintVersion() { + std::cout << "yuzu [Test Utility] " << Common::g_scm_branch << " " << Common::g_scm_desc + << std::endl; +} + +static void InitializeLogging(bool console) { + Log::Filter log_filter(Log::Level::Debug); + log_filter.ParseFilterString(Settings::values.log_filter); + Log::SetGlobalFilter(log_filter); + + if (console) + Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); + + const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + FileUtil::CreateFullPath(log_dir); + Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); +#ifdef _WIN32 + Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); +#endif +} + +/// Application entry point +int main(int argc, char** argv) { + Common::DetachedTasks detached_tasks; + Config config; + + int option_index = 0; + + char* endarg; +#ifdef _WIN32 + int argc_w; + auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); + + if (argv_w == nullptr) { + std::cout << "Failed to get command line arguments" << std::endl; + return -1; + } +#endif + std::string filepath; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {"datastring", optional_argument, 0, 'd'}, + {"log", no_argument, 0, 'l'}, + {0, 0, 0, 0}, + }; + + bool console_log = false; + std::string datastring; + + while (optind < argc) { + int arg = getopt_long(argc, argv, "hvdl::", long_options, &option_index); + if (arg != -1) { + switch (static_cast<char>(arg)) { + case 'h': + PrintHelp(argv[0]); + return 0; + case 'v': + PrintVersion(); + return 0; + case 'd': + datastring = argv[optind]; + ++optind; + break; + case 'l': + console_log = true; + break; + } + } else { +#ifdef _WIN32 + filepath = Common::UTF16ToUTF8(argv_w[optind]); +#else + filepath = argv[optind]; +#endif + optind++; + } + } + + InitializeLogging(console_log); + +#ifdef _WIN32 + LocalFree(argv_w); +#endif + + MicroProfileOnThreadCreate("EmuThread"); + SCOPE_EXIT({ MicroProfileShutdown(); }); + + if (filepath.empty()) { + LOG_CRITICAL(Frontend, "Failed to load application: No application specified"); + std::cout << "Failed to load application: No application specified" << std::endl; + PrintHelp(argv[0]); + return -1; + } + + Settings::values.use_gdbstub = false; + Settings::Apply(); + + std::unique_ptr<EmuWindow_SDL2_Hide> emu_window{std::make_unique<EmuWindow_SDL2_Hide>()}; + + if (!Settings::values.use_multi_core) { + // Single core mode must acquire OpenGL context for entire emulation session + emu_window->MakeCurrent(); + } + + bool finished = false; + int return_value = 0; + const auto callback = [&finished, + &return_value](std::vector<Service::Yuzu::TestResult> results) { + finished = true; + return_value = 0; + + // Find the minimum length needed to fully enclose all test names (and the header field) in + // the fmt::format column by first finding the maximum size of any test name and comparing + // that to 9, the string length of 'Test Name' + const auto needed_length_name = + std::max<u64>(std::max_element(results.begin(), results.end(), + [](const auto& lhs, const auto& rhs) { + return lhs.name.size() < rhs.name.size(); + }) + ->name.size(), + 9ull); + + std::size_t passed = 0; + std::size_t failed = 0; + + std::cout << fmt::format("Result [Res Code] | {:<{}} | Extra Data", "Test Name", + needed_length_name) + << std::endl; + + for (const auto& res : results) { + const auto main_res = res.code == 0 ? "PASSED" : "FAILED"; + if (res.code == 0) + ++passed; + else + ++failed; + std::cout << fmt::format("{} [{:08X}] | {:<{}} | {}", main_res, res.code, res.name, + needed_length_name, res.data) + << std::endl; + } + + std::cout << std::endl + << fmt::format("{:4d} Passed | {:4d} Failed | {:4d} Total | {:2.2f} Passed Ratio", + passed, failed, passed + failed, + static_cast<float>(passed) / (passed + failed)) + << std::endl + << (failed == 0 ? "PASSED" : "FAILED") << std::endl; + + if (failed > 0) + return_value = -1; + }; + + Core::System& system{Core::System::GetInstance()}; + system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); + Service::FileSystem::CreateFactories(*system.GetFilesystem()); + + SCOPE_EXIT({ system.Shutdown(); }); + + const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)}; + + switch (load_result) { + case Core::System::ResultStatus::ErrorGetLoader: + LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filepath.c_str()); + return -1; + case Core::System::ResultStatus::ErrorLoader: + LOG_CRITICAL(Frontend, "Failed to load ROM!"); + return -1; + case Core::System::ResultStatus::ErrorNotInitialized: + LOG_CRITICAL(Frontend, "CPUCore not initialized"); + return -1; + case Core::System::ResultStatus::ErrorVideoCore: + LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!"); + return -1; + case Core::System::ResultStatus::Success: + break; // Expected case + default: + if (static_cast<u32>(load_result) > + static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) { + const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader); + const u16 error_id = static_cast<u16>(load_result) - loader_id; + LOG_CRITICAL(Frontend, + "While attempting to load the ROM requested, an error occured. Please " + "refer to the yuzu wiki for more information or the yuzu discord for " + "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", + loader_id, error_id, static_cast<Loader::ResultStatus>(error_id)); + } + } + + Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback); + + system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDLHideTester"); + + system.Renderer().Rasterizer().LoadDiskResources(); + + while (!finished) { + system.RunLoop(); + } + + detached_tasks.WaitForAllTasks(); + return return_value; +} diff --git a/src/yuzu_tester/yuzu.rc b/src/yuzu_tester/yuzu.rc new file mode 100644 index 000000000..7de8ef3d9 --- /dev/null +++ b/src/yuzu_tester/yuzu.rc @@ -0,0 +1,17 @@ +#include "winresrc.h" +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +YUZU_ICON ICON "../../dist/yuzu.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +1 RT_MANIFEST "../../dist/yuzu.manifest" |