summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x.travis/linux-mingw/build.sh5
-rw-r--r--CMakeLists.txt28
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/audio_core/stream.cpp10
-rw-r--r--src/audio_core/stream.h7
-rw-r--r--src/common/hex_util.cpp7
-rw-r--r--src/common/hex_util.h16
-rw-r--r--src/core/CMakeLists.txt11
-rw-r--r--src/core/arm/arm_interface.cpp201
-rw-r--r--src/core/arm/arm_interface.h11
-rw-r--r--src/core/core.cpp9
-rw-r--r--src/core/core.h4
-rw-r--r--src/core/crypto/key_manager.cpp4
-rw-r--r--src/core/crypto/partition_data_manager.cpp131
-rw-r--r--src/core/file_sys/card_image.cpp46
-rw-r--r--src/core/file_sys/content_archive.cpp4
-rw-r--r--src/core/file_sys/control_metadata.cpp4
-rw-r--r--src/core/file_sys/control_metadata.h4
-rw-r--r--src/core/file_sys/ips_layer.cpp3
-rw-r--r--src/core/file_sys/kernel_executable.cpp228
-rw-r--r--src/core/file_sys/kernel_executable.h99
-rw-r--r--src/core/file_sys/nca_metadata.h9
-rw-r--r--src/core/file_sys/patch_manager.cpp8
-rw-r--r--src/core/file_sys/program_metadata.cpp15
-rw-r--r--src/core/file_sys/program_metadata.h5
-rw-r--r--src/core/file_sys/registered_cache.cpp14
-rw-r--r--src/core/file_sys/submission_package.cpp13
-rw-r--r--src/core/file_sys/xts_archive.cpp2
-rw-r--r--src/core/hle/kernel/svc.cpp8
-rw-r--r--src/core/hle/kernel/vm_manager.cpp4
-rw-r--r--src/core/hle/service/acc/acc.cpp51
-rw-r--r--src/core/hle/service/acc/acc.h9
-rw-r--r--src/core/hle/service/acc/acc_aa.cpp5
-rw-r--r--src/core/hle/service/acc/acc_aa.h4
-rw-r--r--src/core/hle/service/acc/acc_su.cpp5
-rw-r--r--src/core/hle/service/acc/acc_su.h4
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp9
-rw-r--r--src/core/hle/service/acc/acc_u0.h4
-rw-r--r--src/core/hle/service/acc/acc_u1.cpp5
-rw-r--r--src/core/hle/service/acc/acc_u1.h4
-rw-r--r--src/core/hle/service/am/am.cpp18
-rw-r--r--src/core/hle/service/am/am.h3
-rw-r--r--src/core/hle/service/am/applets/applets.cpp34
-rw-r--r--src/core/hle/service/am/applets/applets.h16
-rw-r--r--src/core/hle/service/am/applets/error.cpp19
-rw-r--r--src/core/hle/service/am/applets/general_backend.cpp22
-rw-r--r--src/core/hle/service/am/applets/general_backend.h5
-rw-r--r--src/core/hle/service/audio/audout_u.cpp23
-rw-r--r--src/core/hle/service/fatal/fatal.cpp26
-rw-r--r--src/core/hle/service/ldr/ldr.cpp2
-rw-r--r--src/core/hle/service/prepo/prepo.cpp25
-rw-r--r--src/core/hle/service/service.cpp5
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp11
-rw-r--r--src/core/loader/deconstructed_rom_directory.h4
-rw-r--r--src/core/loader/kip.cpp102
-rw-r--r--src/core/loader/kip.h35
-rw-r--r--src/core/loader/loader.cpp16
-rw-r--r--src/core/loader/loader.h11
-rw-r--r--src/core/loader/nax.cpp4
-rw-r--r--src/core/loader/nax.h2
-rw-r--r--src/core/loader/nca.cpp9
-rw-r--r--src/core/loader/nca.h2
-rw-r--r--src/core/loader/nso.cpp13
-rw-r--r--src/core/loader/nso.h5
-rw-r--r--src/core/loader/nsp.cpp4
-rw-r--r--src/core/loader/nsp.h2
-rw-r--r--src/core/loader/xci.cpp4
-rw-r--r--src/core/loader/xci.h2
-rw-r--r--src/core/reporter.cpp353
-rw-r--r--src/core/reporter.h56
-rw-r--r--src/core/settings.h1
-rw-r--r--src/core/tracer/citrace.h100
-rw-r--r--src/core/tracer/recorder.cpp208
-rw-r--r--src/core/tracer/recorder.h87
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/engines/const_buffer_info.h17
-rw-r--r--src/video_core/engines/maxwell_3d.cpp4
-rw-r--r--src/video_core/engines/maxwell_3d.h8
-rw-r--r--src/video_core/memory_manager.h4
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp80
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h15
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp31
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp49
-rw-r--r--src/video_core/shader/decode/other.cpp18
-rw-r--r--src/video_core/shader/node.h7
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/configuration/configure_debug.cpp2
-rw-r--r--src/yuzu/configuration/configure_debug.ui22
-rw-r--r--src/yuzu/configuration/configure_input.ui2
-rw-r--r--src/yuzu/game_list.cpp3
-rw-r--r--src/yuzu/main.cpp2
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_tester/CMakeLists.txt34
-rw-r--r--src/yuzu_tester/config.cpp184
-rw-r--r--src/yuzu_tester/config.h24
-rw-r--r--src/yuzu_tester/default_ini.h150
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp122
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.h41
-rw-r--r--src/yuzu_tester/resource.h16
-rw-r--r--src/yuzu_tester/service/yuzutest.cpp112
-rw-r--r--src/yuzu_tester/service/yuzutest.h25
-rw-r--r--src/yuzu_tester/yuzu.cpp267
-rw-r--r--src/yuzu_tester/yuzu.rc17
104 files changed, 2724 insertions, 780 deletions
diff --git a/.travis/linux-mingw/build.sh b/.travis/linux-mingw/build.sh
index b12d70b12..c32a909d3 100755
--- a/.travis/linux-mingw/build.sh
+++ b/.travis/linux-mingw/build.sh
@@ -1,3 +1,4 @@
#!/bin/bash -ex
-mkdir "$HOME/.ccache" || true
-docker run --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache yuzuemu/build-environments:linux-mingw /bin/bash -ex /yuzu/.travis/linux-mingw/docker.sh
+
+mkdir -p "$HOME/.ccache"
+docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache yuzuemu/build-environments:linux-mingw /bin/bash /yuzu/.travis/linux-mingw/docker.sh
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bfa104034..9a207f9e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,6 +7,18 @@ include(CMakeDependentOption)
project(yuzu)
+# Get Git submodule dependencies
+find_package(Git QUIET)
+if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
+ execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ RESULT_VARIABLE GIT_SUBMOD_RESULT)
+ if(NOT GIT_SUBMOD_RESULT EQUAL "0")
+ message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, "
+ "please checkout submodules manually with \"git submodule update --init --recursive\"")
+ endif()
+endif()
+
# Set bundled sdl2/qt as dependent options.
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
option(ENABLE_SDL2 "Enable the SDL2 frontend" ON)
@@ -33,22 +45,6 @@ if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
DESTINATION ${PROJECT_SOURCE_DIR}/.git/hooks)
endif()
-# Sanity check : Check that all submodules are present
-# =======================================================================
-
-function(check_submodules_present)
- file(READ "${PROJECT_SOURCE_DIR}/.gitmodules" gitmodules)
- string(REGEX MATCHALL "path *= *[^ \t\r\n]*" gitmodules ${gitmodules})
- foreach(module ${gitmodules})
- string(REGEX REPLACE "path *= *" "" module ${module})
- if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
- message(FATAL_ERROR "Git submodule ${module} not found. "
- "Please run: git submodule update --init --recursive")
- endif()
- endforeach()
-endfunction()
-check_submodules_present()
-
configure_file(${PROJECT_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
COPYONLY)
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 11481a776..982c7af2f 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..cdb3bf6ab 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
@@ -440,6 +442,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 +463,18 @@ 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
)
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 d808c0417..c00dfd33c 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -29,6 +29,7 @@
#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 "file_sys/cheat_engine.h"
@@ -74,7 +75,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();
@@ -254,6 +255,8 @@ struct System::Impl {
/// Telemetry session for this emulation session
std::unique_ptr<Core::TelemetrySession> telemetry_session;
+ Reporter reporter;
+
ResultStatus status = ResultStatus::Success;
std::string status_details = "";
@@ -493,6 +496,10 @@ void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
impl->content_provider->ClearSlot(slot);
}
+const Reporter& System::GetReporter() const {
+ return impl->reporter;
+}
+
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..226ef4630 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"
@@ -68,6 +69,7 @@ class Cpu;
class ExclusiveMonitor;
class FrameLimiter;
class PerfStats;
+class Reporter;
class TelemetrySession;
struct PerfStatsResults;
@@ -284,6 +286,8 @@ public:
void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
+ const Reporter& GetReporter() 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/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 50bf38471..84d5cd1e0 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -4,6 +4,7 @@
#pragma once
+#include <array>
#include <memory>
#include <vector>
#include "common/common_funcs.h"
@@ -69,11 +70,15 @@ struct CNMTHeader {
u64_le title_id;
u32_le title_version;
TitleType type;
- INSERT_PADDING_BYTES(1);
+ u8 reserved;
u16_le table_offset;
u16_le number_content_entries;
u16_le number_meta_entries;
- INSERT_PADDING_BYTES(12);
+ u8 attributes;
+ std::array<u8, 2> reserved2;
+ u8 is_committed;
+ u32_le required_download_system_version;
+ std::array<u8, 4> reserved3;
};
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 78dbadee3..da823c37b 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));
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..58917e094 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,
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/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..025714e5a 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 {
@@ -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..4a7bf4acb 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,
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 991b7d47c..1fa069e56 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;
};
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 14fa92318..e3e4ead03 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();
}
@@ -204,7 +220,7 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
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..05ae739ca 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -54,6 +54,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 +84,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;
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..54c155dd8 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,24 +13,25 @@
#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) {
+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));
}
}
@@ -83,13 +84,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..fb68a2543 100644
--- a/src/core/hle/service/am/applets/general_backend.h
+++ b/src/core/hle/service/am/applets/general_backend.h
@@ -34,7 +34,7 @@ private:
class StubApplet final : public Applet {
public:
- StubApplet();
+ explicit StubApplet(AppletId id);
~StubApplet() override;
void Initialize() override;
@@ -43,6 +43,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/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/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..b2954eb34 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -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,7 +203,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
SM::ServiceManager::InstallInterfaces(sm);
- Account::InstallInterfaces(*sm);
+ Account::InstallInterfaces(system);
AM::InstallInterfaces(*sm, nv_flinger);
AOC::InstallInterfaces(*sm);
APM::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..8fe621aa0
--- /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::HexVectorToString(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::HexVectorToString(*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::HexVectorToString(data));
+ }
+
+ auto interactive_out = json::array();
+ for (const auto& data : interactive_channel) {
+ interactive_out.push_back(Common::HexVectorToString(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::HexVectorToString(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/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/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/memory_manager.h b/src/video_core/memory_manager.h
index 113f9d8f3..43a84bd52 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -163,8 +163,8 @@ private:
static constexpr u64 page_size{1 << page_bits};
static constexpr u64 page_mask{page_size - 1};
- /// Address space in bits, this is fairly arbitrary but sufficiently large.
- static constexpr u32 address_space_width{39};
+ /// Address space in bits, according to Tegra X1 TRM
+ static constexpr u32 address_space_width{40};
/// Start address for mapping, this is fairly arbitrary but must be non-zero.
static constexpr GPUVAddr address_space_base{0x100000};
/// End of address space, based on address space in bits.
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/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..66a7080c9 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1944,7 +1944,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"