Compare commits

..

63 Commits

Author SHA1 Message Date
Zach Hilman
3c4238657d applets: Pass current process title ID to applets
Avoids using system accessor to get current process in applet code.
2019-06-24 20:07:10 -04:00
Zach Hilman
01ff38cca8 general_frontend: Add documentation for parental controls and ecommerce applets 2019-06-24 20:05:11 -04:00
Zach Hilman
73dcb13619 web_browser: Only delete temporary directory if it was created
Prevents crashes with ShopN applet occasionally.
2019-06-24 20:05:11 -04:00
Zach Hilman
d018ac2c60 web_browser: Take ECommerce applet frontend optionally in constructor
If it is needed but wasn't passed (or passed nullptr), the Shop handling code will alert and throw an error.
2019-06-24 20:05:11 -04:00
Zach Hilman
54684feffa frontend: Add base class and default impl for ECommerce applet frontend 2019-06-24 20:05:11 -04:00
Zach Hilman
3898c3903e web_browser: Use function tables for execute and initialize
Allows easy handling of multiple shim types, as they have enough in common to be the same backend but not enough to share init/exec.
2019-06-24 20:05:11 -04:00
Zach Hilman
675aa5f719 web_browser: Correct structures and properly parse TLVs/ShimKind
Much, much more HW-accurate and allows us to easily support all of the different web 'shim' types.
2019-06-24 20:05:11 -04:00
Zach Hilman
b889167b2c yuzu: Accept default applets for Parental Controls and ECommerce 2019-06-24 20:05:11 -04:00
Zach Hilman
e447d8aafa applets: Track ECommerce and Parental Control applet frontends 2019-06-24 20:05:11 -04:00
Zach Hilman
6ff9008230 web_browser: Rename OpenPage to OpenPageLocal
This is more representative of what actually occurs, as web does support remote URLs which wouldn't need a romfs callback. This paves for easy future support of this with a call like 'OpenPageRemote' or similar.
2019-06-24 20:05:11 -04:00
Zach Hilman
c96450f6e2 frontend: Add base class and default impl of parent controls applet frontend 2019-06-24 20:05:11 -04:00
Zach Hilman
9d2d349d7b applets: Implement Auth applet backend
This is responsible for parental controls and supports verifying, changing, and registering PIN codes.
2019-06-24 20:05:11 -04:00
Zach Hilman
221996a194 Merge pull request #2603 from WamWooWam/master
HexVectorToString -> HexToString
2019-06-22 13:26:56 -04:00
Thomas May
334e6370f9 Update reporter.cpp 2019-06-22 14:39:52 +01:00
bunnei
a9f3c54871 Merge pull request #2579 from ReinUsesLisp/fix-aoffi-test
gl_device: Fix TestVariableAoffi test
2019-06-21 15:28:55 -04:00
bunnei
69e490b303 Merge pull request #2602 from lioncash/cast
service/acc: Silence truncation warnings
2019-06-21 15:27:25 -04:00
bunnei
8f8fa82c83 Merge pull request #2575 from DarkLordZach/process-id-types
kernel: Differentiate kernel and user processes when picking ID
2019-06-21 15:25:50 -04:00
bunnei
e2f7933b3f Merge pull request #2546 from DarkLordZach/kips
loader, file_sys: Add support for parsing and loading KIP (Kernel Internal Process) files
2019-06-21 14:28:18 -04:00
bunnei
96412848a9 Merge pull request #2482 from DarkLordZach/prepo
core: Add detailed local reporting feature for development
2019-06-21 14:05:18 -04:00
bunnei
100ed88e15 Merge pull request #2291 from DarkLordZach/homebrew-testing
yuzu_tester: Add and implement testing utility for homebrew
2019-06-21 11:42:38 -04:00
Lioncash
82cd87f6c2 service/acc: Silence truncation warnings
The sanitizing function ensures that the returned type is always the
correct type. This eliminates warnings without extra casts.
2019-06-21 10:04:31 -04:00
bunnei
80a8456af8 Merge pull request #2596 from FernandoS27/revert-2590
Revert PR 2590.
2019-06-20 13:04:06 -04:00
Hexagon12
908a5a00c5 Merge pull request #2595 from jonsn0w/patch-1
Update content_archive.cpp
2019-06-20 10:03:24 +03:00
Fernando Sahmkow
26fcdf087d Revert PR 2590.
Even though it has been proven that IAudioRenderer:SystemEvent is 
actually an automatic event. The current implementation of such event is 
all thought to be manual. Thus it's implementation needs to be corrected 
when doing such change. As it is right now this PR introduced a series 
of regressions on softlocks on multiple games. Therefore, this pr 
reverts such change until a correct implementation is made.
2019-06-19 23:19:19 -04:00
bunnei
c28694d907 Merge pull request #2591 from lioncash/record
core: Remove unused CiTrace source files
2019-06-19 22:28:26 -04:00
bunnei
ca470890a3 Merge pull request #2590 from lioncash/event
service/audio/audren_u: Correct event reset type for the system event
2019-06-19 22:27:52 -04:00
Frederic L
5cef446f42 CMake: Get Git submodule dependencies via CMake (#2474)
* CMake: Get Git submodule dependencies via CMake

* CMakeLists: Fixed unintentional line break

* travis: Bring parity between linux-mingw and linux build script

* CMakeLists: Fixed typo in error message
2019-06-19 22:26:12 -04:00
jonsn0w
e78d069a81 Update content_archive.cpp
log clutter in debug logs when theres really no need
2019-06-19 22:01:41 -04:00
Zach Hilman
8893d63612 Merge pull request #2594 from FearlessTobi/very-important-change
yuzu/configure_input: Add missing space in window title
2019-06-19 19:15:37 -04:00
Tobias
0c64a6f0f2 Change to a more descriptive name 2019-06-19 23:55:13 +02:00
Tobias
c5b20a108d yuzu/configure_input: Add missing space in window name 2019-06-19 23:32:34 +02:00
Mat M
908ca1fc72 Merge pull request #2593 from deadmeu/patch-1
Added missing space between two words
2019-06-19 14:11:10 -04:00
Lioncash
61d2498f00 core: Remove unused CiTrace source files
These source files have been unused for the entire lifecycle of the
project. They're a hold-over from Citra and only add to the build time
of the project, so they can be removed.

There's also likely no way this would ever work in yuzu in its current
form without revamping quite a bit of it, given how different the GPU on
the Switch is compared to the 3DS.
2019-06-18 16:57:59 -04:00
Lioncash
5799404b78 service/audio/audren_u: Correct event reset type for the system event
This is actually an auto-reset event in the audio service itself, not a
manual one.
2019-06-18 09:23:20 -04:00
ReinUsesLisp
ee81fb94cd gl_device: Fix TestVariableAoffi test
This test is intended to be invalid GLSL, but it was being invalid in
two points instead of one. The intention is to use a non-immediate
parameter in a textureOffset like function.

The problem is that this shader was being compiled as a separable
shader object and the text was writting to gl_Position without a
redeclaration, being invalid GLSL.

Address that issue by using a user-defined output attribute.
2019-06-11 23:02:50 -03:00
Zach Hilman
3a26b49c2c yuzutest: Add minor comments 2019-06-10 00:31:54 -04:00
Zach Hilman
fc0bf91a96 kernel: Differentiate kernel and user processes when picking ID
This allows kernel internal type processes to be assigned IDs in the KIP range while userland processes are assigned in the user range.
2019-06-10 00:28:33 -04:00
Zach Hilman
511bf3435d yuzu_tester: Display results in table format 2019-06-10 00:03:11 -04:00
Zach Hilman
f279e792b7 yuzutest: Support multiple tests per executable 2019-06-10 00:03:11 -04:00
Zach Hilman
5ddc9cede5 yuzu_tester: Add 'yuzutest' service 2019-06-10 00:03:11 -04:00
Zach Hilman
ae5a46256e yuzu_tester: Add SDL2-based EmuWindow that doesn't show the window 2019-06-10 00:03:11 -04:00
Zach Hilman
819006d0d3 yuzu_tester: Use config, icon, and main from yuzu-cmd 2019-06-10 00:03:11 -04:00
Zach Hilman
c508a8d82a yuzu_tester: Add project subdirectory 2019-06-10 00:03:11 -04:00
Zach Hilman
9db119f8a2 kernel_executable: Optimize BLZ decompression 2019-06-06 19:20:15 -04:00
Zach Hilman
c417b4fe28 game_list: Accept *.kip as a file extension of executables 2019-06-05 00:33:05 -04:00
Zach Hilman
ce9f7ac4f2 loader: Add recognition for KIP file type 2019-06-05 00:22:07 -04:00
Zach Hilman
a76bd49268 loader: Add KIP and INI file parser-specific errors 2019-06-05 00:21:44 -04:00
Zach Hilman
c3548967b1 loader: Add AppLoader_KIP for KIP files 2019-06-05 00:21:17 -04:00
Zach Hilman
b550a01f74 program_metadata: Add function to load meta from raw parameters
Needed for KIP loading as KIPs do not have an NPDM but do have the essential parts of the data within.
2019-06-05 00:20:26 -04:00
Zach Hilman
0f37096820 partition_data_manager: Remove KIP processing and use FileSys
Previously, this TU contained the necessary headers to parse KIP/INI but now it should just use the FileSys class.
2019-06-05 00:19:29 -04:00
Zach Hilman
421c3e831a file_sys: Add classes to parse KIP1 and INI1 files 2019-06-05 00:18:25 -04:00
Zach Hilman
b77fde7c5c loader: Move NSO module tracking to AppLoader
Also cleanup of general stuff
2019-05-26 11:40:46 -04:00
Zach Hilman
cdf52b9374 prepo: Save reports from PlayReport service
Logs a lot of seemingly innocuous telemetry games generate.
2019-05-25 16:09:20 -04:00
Zach Hilman
34e9736910 fatal: Save report on fatal:u call
Matches offical behavior with creport and replaces old log/text based report system.
2019-05-25 16:09:20 -04:00
Zach Hilman
fc309bf893 service: Save report on unimplemented function call 2019-05-25 16:09:20 -04:00
Zach Hilman
24392c8ec8 applets/error: Save report on error applet
This matches official behavior with the erpt/eclct/eupld service chain.
2019-05-25 16:09:20 -04:00
Zach Hilman
bdc47693f1 applets: Save report on stubbed applet
This also reworks the applet data storage to be peekable.
2019-05-25 16:09:20 -04:00
Zach Hilman
186db894b9 svc: Save report on call to svcBreak 2019-05-25 16:09:20 -04:00
Zach Hilman
2dde8f5cfe core: Add Reporter class to take/save reports 2019-05-25 16:09:20 -04:00
Zach Hilman
6f0ee45b5c qt: Make UI option for 'Reporting Services' temporary
Reports are unnecessary for normal users and this is to prevent 'power' users from enabling the feature by accident.
2019-05-25 16:09:20 -04:00
Zach Hilman
658d2ee97d settings: Add 'Reporting Services' config option
Full enable/disable for all reports.
2019-05-25 16:09:20 -04:00
Zach Hilman
5574be21cc arm_interface: Expand backtrace generation
Returns results as a vector of entries for further processing. Logs addresses, offsets, and mangled name.
2019-05-25 16:06:53 -04:00
Zach Hilman
2179ad7483 core: Track load offsets of NSO modules
Needed for backtrace decomposition
2019-05-25 16:06:53 -04:00
85 changed files with 3281 additions and 737 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -88,6 +88,7 @@ add_subdirectory(tests)
if (ENABLE_SDL2)
add_subdirectory(yuzu_cmd)
add_subdirectory(yuzu_tester)
endif()
if (ENABLE_QT)

View File

@@ -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)

View File

@@ -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) {
LOG_ERROR(Core_ARM, "{:016X}", lr);
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) {
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

View File

@@ -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

View File

@@ -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();
@@ -150,7 +151,8 @@ struct System::Impl {
}
telemetry_session->AddInitialInfo(*app_loader);
auto main_process = Kernel::Process::Create(system, "main");
auto main_process =
Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland);
const auto [load_result, load_parameters] = app_loader->Load(*main_process);
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
@@ -253,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 = "";
@@ -492,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);
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -7,9 +7,38 @@
namespace Core::Frontend {
ParentalControlsApplet::~ParentalControlsApplet() = default;
DefaultParentalControlsApplet::~DefaultParentalControlsApplet() = default;
void DefaultParentalControlsApplet::VerifyPIN(std::function<void(bool)> finished,
bool suspend_future_verification_temporarily) {
LOG_INFO(Service_AM,
"Application requested frontend to verify PIN (normal), "
"suspend_future_verification_temporarily={}, verifying as correct.",
suspend_future_verification_temporarily);
finished(true);
}
void DefaultParentalControlsApplet::VerifyPINForSettings(std::function<void(bool)> finished) {
LOG_INFO(Service_AM,
"Application requested frontend to verify PIN (settings), verifying as correct.");
finished(true);
}
void DefaultParentalControlsApplet::RegisterPIN(std::function<void()> finished) {
LOG_INFO(Service_AM, "Application requested frontend to register new PIN");
finished();
}
void DefaultParentalControlsApplet::ChangePIN(std::function<void()> finished) {
LOG_INFO(Service_AM, "Application requested frontend to change PIN to new value");
finished();
}
PhotoViewerApplet::~PhotoViewerApplet() = default;
DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() {}
DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() = default;
void DefaultPhotoViewerApplet::ShowPhotosForApplication(u64 title_id,
std::function<void()> finished) const {
@@ -24,4 +53,72 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con
finished();
}
ECommerceApplet::~ECommerceApplet() = default;
DefaultECommerceApplet::~DefaultECommerceApplet() = default;
void DefaultECommerceApplet::ShowApplicationInformation(
std::function<void()> finished, u64 title_id, std::optional<u128> user_id,
std::optional<bool> full_display, std::optional<std::string> extra_parameter) {
const auto value = user_id.value_or(u128{});
LOG_INFO(Service_AM,
"Application requested frontend show application information for EShop, "
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}",
title_id, value[1], value[0],
full_display.has_value() ? fmt::format("{}", *full_display) : "null",
extra_parameter.value_or("null"));
finished();
}
void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id,
std::optional<bool> full_display) {
const auto value = user_id.value_or(u128{});
LOG_INFO(Service_AM,
"Application requested frontend show add on content list for EShop, "
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}",
title_id, value[1], value[0],
full_display.has_value() ? fmt::format("{}", *full_display) : "null");
finished();
}
void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id) {
const auto value = user_id.value_or(u128{});
LOG_INFO(Service_AM,
"Application requested frontend show subscription list for EShop, title_id={:016X}, "
"user_id={:016X}{:016X}",
title_id, value[1], value[0]);
finished();
}
void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id) {
const auto value = user_id.value_or(u128{});
LOG_INFO(
Service_AM,
"Application requested frontend show consumable item list for EShop, title_id={:016X}, "
"user_id={:016X}{:016X}",
title_id, value[1], value[0]);
finished();
}
void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id,
bool full_display) {
LOG_INFO(Service_AM,
"Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, "
"full_display={}",
user_id[1], user_id[0], full_display);
finished();
}
void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id,
bool full_display) {
LOG_INFO(Service_AM,
"Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, "
"full_display={}",
user_id[1], user_id[0], full_display);
finished();
}
} // namespace Core::Frontend

View File

@@ -5,10 +5,43 @@
#pragma once
#include <functional>
#include <optional>
#include "common/common_types.h"
namespace Core::Frontend {
class ParentalControlsApplet {
public:
virtual ~ParentalControlsApplet();
// Prompts the user to enter a PIN and calls the callback with whether or not it matches the
// correct PIN. If the bool is passed, and the PIN was recently entered correctly, the frontend
// should not prompt and simply return true.
virtual void VerifyPIN(std::function<void(bool)> finished,
bool suspend_future_verification_temporarily) = 0;
// Prompts the user to enter a PIN and calls the callback for correctness. Frontends can
// optionally alert the user that this is to change parental controls settings.
virtual void VerifyPINForSettings(std::function<void(bool)> finished) = 0;
// Prompts the user to create a new PIN for pctl and stores it with the service.
virtual void RegisterPIN(std::function<void()> finished) = 0;
// Prompts the user to verify the current PIN and then store a new one into pctl.
virtual void ChangePIN(std::function<void()> finished) = 0;
};
class DefaultParentalControlsApplet final : public ParentalControlsApplet {
public:
~DefaultParentalControlsApplet() override;
void VerifyPIN(std::function<void(bool)> finished,
bool suspend_future_verification_temporarily) override;
void VerifyPINForSettings(std::function<void(bool)> finished) override;
void RegisterPIN(std::function<void()> finished) override;
void ChangePIN(std::function<void()> finished) override;
};
class PhotoViewerApplet {
public:
virtual ~PhotoViewerApplet();
@@ -25,4 +58,55 @@ public:
void ShowAllPhotos(std::function<void()> finished) const override;
};
class ECommerceApplet {
public:
virtual ~ECommerceApplet();
// Shows a page with application icons, description, name, and price.
virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id = {},
std::optional<bool> full_display = {},
std::optional<std::string> extra_parameter = {}) = 0;
// Shows a page with all of the add on content available for a game, with name, description, and
// price.
virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id = {},
std::optional<bool> full_display = {}) = 0;
// Shows a page with all of the subscriptions (recurring payments) for a game, with name,
// description, price, and renewal period.
virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id = {}) = 0;
// Shows a page with a list of any additional game related purchasable items (DLC,
// subscriptions, etc) for a particular game, with name, description, type, and price.
virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id = {}) = 0;
// Shows the home page of the shop.
virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0;
// Shows the user settings page of the shop.
virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0;
};
class DefaultECommerceApplet : public ECommerceApplet {
public:
~DefaultECommerceApplet() override;
void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id, std::optional<bool> full_display,
std::optional<std::string> extra_parameter) override;
void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id,
std::optional<bool> full_display) override;
void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id) override;
void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
std::optional<u128> user_id) override;
void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override;
void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override;
};
} // namespace Core::Frontend

View File

@@ -11,9 +11,9 @@ WebBrowserApplet::~WebBrowserApplet() = default;
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
void DefaultWebBrowserApplet::OpenPage(std::string_view filename,
std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) {
void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename,
std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) {
LOG_INFO(Service_AM,
"(STUBBED) called - No suitable web browser implementation found to open website page "
"at '{}'!",

View File

@@ -13,16 +13,16 @@ class WebBrowserApplet {
public:
virtual ~WebBrowserApplet();
virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) = 0;
virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) = 0;
};
class DefaultWebBrowserApplet final : public WebBrowserApplet {
public:
~DefaultWebBrowserApplet() override;
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) override;
void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) override;
};
} // namespace Core::Frontend

View File

@@ -99,7 +99,8 @@ struct KernelCore::Impl {
void Shutdown() {
next_object_id = 0;
next_process_id = Process::ProcessIDMin;
next_kernel_process_id = Process::InitialKIPIDMin;
next_user_process_id = Process::ProcessIDMin;
next_thread_id = 1;
process_list.clear();
@@ -132,7 +133,8 @@ struct KernelCore::Impl {
}
std::atomic<u32> next_object_id{0};
std::atomic<u64> next_process_id{Process::ProcessIDMin};
std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin};
std::atomic<u64> next_user_process_id{Process::ProcessIDMin};
std::atomic<u64> next_thread_id{1};
// Lists all processes that exist in the current session.
@@ -226,8 +228,12 @@ u64 KernelCore::CreateNewThreadID() {
return impl->next_thread_id++;
}
u64 KernelCore::CreateNewProcessID() {
return impl->next_process_id++;
u64 KernelCore::CreateNewKernelProcessID() {
return impl->next_kernel_process_id++;
}
u64 KernelCore::CreateNewUserProcessID() {
return impl->next_user_process_id++;
}
Core::Timing::EventType* KernelCore::ThreadWakeupCallbackEventType() const {

View File

@@ -96,7 +96,10 @@ private:
u32 CreateNewObjectID();
/// Creates a new process ID, incrementing the internal process ID counter;
u64 CreateNewProcessID();
u64 CreateNewKernelProcessID();
/// Creates a new process ID, incrementing the internal process ID counter;
u64 CreateNewUserProcessID();
/// Creates a new thread ID, incrementing the internal thread ID counter.
u64 CreateNewThreadID();

View File

@@ -48,7 +48,8 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) {
}
} // Anonymous namespace
SharedPtr<Process> Process::Create(Core::System& system, std::string name) {
SharedPtr<Process> Process::Create(Core::System& system, std::string name,
Process::ProcessType type) {
auto& kernel = system.Kernel();
SharedPtr<Process> process(new Process(system));
@@ -56,7 +57,8 @@ SharedPtr<Process> Process::Create(Core::System& system, std::string name) {
process->resource_limit = kernel.GetSystemResourceLimit();
process->status = ProcessStatus::Created;
process->program_id = 0;
process->process_id = kernel.CreateNewProcessID();
process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID()
: kernel.CreateNewUserProcessID();
process->capabilities.InitializeForMetadatalessProcess();
std::mt19937 rng(Settings::values.rng_seed.value_or(0));

View File

@@ -73,9 +73,15 @@ public:
ProcessIDMax = 0xFFFFFFFFFFFFFFFF,
};
// Used to determine how process IDs are assigned.
enum class ProcessType {
KernelInternal,
Userland,
};
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
static SharedPtr<Process> Create(Core::System& system, std::string name);
static SharedPtr<Process> Create(Core::System& system, std::string name, ProcessType type);
std::string GetTypeName() const override {
return "Process";

View File

@@ -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,

View File

@@ -94,7 +94,7 @@ private:
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
ctx.WriteBuffer(Core::Constants::ACCOUNT_BACKUP_JPEG);
rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size());
rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
return;
}
@@ -116,9 +116,9 @@ private:
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size());
rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
} else {
rb.Push<u32>(SanitizeJPEGSize(image.GetSize()));
rb.Push(SanitizeJPEGSize(image.GetSize()));
}
}

View File

@@ -887,7 +887,9 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {
ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id)
: ServiceFramework("ILibraryAppletCreator"),
current_process_title_id(current_process_title_id) {
static const FunctionInfo functions[] = {
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
{1, nullptr, "TerminateAllLibraryApplets"},
@@ -910,7 +912,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
static_cast<u32>(applet_id), applet_mode);
const auto& applet_manager{Core::System::GetInstance().GetAppletManager()};
const auto applet = applet_manager.GetApplet(applet_id);
const auto applet = applet_manager.GetApplet(applet_id, current_process_title_id);
if (applet == nullptr) {
LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id));
@@ -1234,13 +1236,13 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
}
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nvflinger) {
std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) {
auto message_queue = std::make_shared<AppletMessageQueue>();
message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on
// game boot
std::make_shared<AppletAE>(nvflinger, message_queue)->InstallAsService(service_manager);
std::make_shared<AppletOE>(nvflinger, message_queue)->InstallAsService(service_manager);
std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
std::make_shared<IdleSys>()->InstallAsService(service_manager);
std::make_shared<OMM>()->InstallAsService(service_manager);
std::make_shared<SPSM>()->InstallAsService(service_manager);

View File

@@ -201,13 +201,15 @@ private:
class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
public:
ILibraryAppletCreator();
ILibraryAppletCreator(u64 current_process_title_id);
~ILibraryAppletCreator() override;
private:
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
void CreateStorage(Kernel::HLERequestContext& ctx);
void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
u64 current_process_title_id;
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@@ -264,7 +266,7 @@ public:
/// Registers all AM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system);
} // namespace AM
} // namespace Service

View File

@@ -4,6 +4,7 @@
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -13,9 +14,10 @@ namespace Service::AM {
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
public:
explicit ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
std::shared_ptr<AppletMessageQueue> msg_queue)
std::shared_ptr<AppletMessageQueue> msg_queue,
Core::System& system)
: ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)),
msg_queue(std::move(msg_queue)) {
msg_queue(std::move(msg_queue)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -96,7 +98,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>();
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
@@ -109,14 +111,15 @@ private:
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
Core::System& system;
};
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
public:
explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
std::shared_ptr<AppletMessageQueue> msg_queue)
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
: ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)),
msg_queue(std::move(msg_queue)) {
msg_queue(std::move(msg_queue)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -191,7 +194,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>();
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
}
void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
@@ -219,6 +222,7 @@ private:
}
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
Core::System& system;
};
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
@@ -226,7 +230,7 @@ void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue);
rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue, system);
}
void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
@@ -234,7 +238,7 @@ void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue);
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
}
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
@@ -242,13 +246,13 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue);
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
}
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
std::shared_ptr<AppletMessageQueue> msg_queue)
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)),
msg_queue(std::move(msg_queue)) {
msg_queue(std::move(msg_queue)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},

View File

@@ -18,7 +18,7 @@ namespace AM {
class AppletAE final : public ServiceFramework<AppletAE> {
public:
explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
std::shared_ptr<AppletMessageQueue> msg_queue);
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
~AppletAE() override;
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
@@ -30,6 +30,7 @@ private:
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
Core::System& system;
};
} // namespace AM

View File

@@ -4,6 +4,7 @@
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -13,9 +14,9 @@ namespace Service::AM {
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
public:
explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
std::shared_ptr<AppletMessageQueue> msg_queue)
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
: ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)),
msg_queue(std::move(msg_queue)) {
msg_queue(std::move(msg_queue)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -87,7 +88,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>();
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
@@ -100,6 +101,7 @@ private:
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
Core::System& system;
};
void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
@@ -107,13 +109,13 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue);
rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue, system);
}
AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
std::shared_ptr<AppletMessageQueue> msg_queue)
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
: ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)),
msg_queue(std::move(msg_queue)) {
msg_queue(std::move(msg_queue)), system(system) {
static const FunctionInfo functions[] = {
{0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
};

View File

@@ -18,7 +18,7 @@ namespace AM {
class AppletOE final : public ServiceFramework<AppletOE> {
public:
explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
std::shared_ptr<AppletMessageQueue> msg_queue);
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
~AppletOE() override;
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
@@ -28,6 +28,7 @@ private:
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
Core::System& system;
};
} // namespace AM

View File

@@ -35,12 +35,28 @@ AppletDataBroker::AppletDataBroker() {
AppletDataBroker::~AppletDataBroker() = default;
AppletDataBroker::RawChannelData AppletDataBroker::PeekDataToAppletForDebug() const {
std::vector<std::vector<u8>> out_normal;
for (const auto& storage : in_channel) {
out_normal.push_back(storage->GetData());
}
std::vector<std::vector<u8>> out_interactive;
for (const auto& storage : in_interactive_channel) {
out_interactive.push_back(storage->GetData());
}
return {std::move(out_normal), std::move(out_interactive)};
}
std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() {
if (out_channel.empty())
return nullptr;
auto out = std::move(out_channel.front());
out_channel.pop();
out_channel.pop_front();
return out;
}
@@ -49,7 +65,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() {
return nullptr;
auto out = std::move(in_channel.front());
in_channel.pop();
in_channel.pop_front();
return out;
}
@@ -58,7 +74,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() {
return nullptr;
auto out = std::move(out_interactive_channel.front());
out_interactive_channel.pop();
out_interactive_channel.pop_front();
return out;
}
@@ -67,25 +83,25 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() {
return nullptr;
auto out = std::move(in_interactive_channel.front());
in_interactive_channel.pop();
in_interactive_channel.pop_front();
return out;
}
void AppletDataBroker::PushNormalDataFromGame(IStorage storage) {
in_channel.push(std::make_unique<IStorage>(storage));
in_channel.push_back(std::make_unique<IStorage>(storage));
}
void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) {
out_channel.push(std::make_unique<IStorage>(storage));
out_channel.push_back(std::make_unique<IStorage>(storage));
pop_out_data_event.writable->Signal();
}
void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) {
in_interactive_channel.push(std::make_unique<IStorage>(storage));
in_interactive_channel.push_back(std::make_unique<IStorage>(storage));
}
void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) {
out_interactive_channel.push(std::make_unique<IStorage>(storage));
out_interactive_channel.push_back(std::make_unique<IStorage>(storage));
pop_interactive_out_data_event.writable->Signal();
}
@@ -123,12 +139,14 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
AppletFrontendSet::AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer,
ProfileSelect profile_select,
SoftwareKeyboard software_keyboard, WebBrowser web_browser)
: error{std::move(error)}, photo_viewer{std::move(photo_viewer)}, profile_select{std::move(
profile_select)},
software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)} {}
AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
PhotoViewer photo_viewer, ProfileSelect profile_select,
SoftwareKeyboard software_keyboard, WebBrowser web_browser,
ECommerceApplet e_commerce)
: parental_controls{std::move(parental_controls)}, error{std::move(error)},
photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)},
software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)},
e_commerce{std::move(e_commerce)} {}
AppletFrontendSet::~AppletFrontendSet() = default;
@@ -141,6 +159,8 @@ AppletManager::AppletManager() = default;
AppletManager::~AppletManager() = default;
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
if (set.parental_controls != nullptr)
frontend.parental_controls = std::move(set.parental_controls);
if (set.error != nullptr)
frontend.error = std::move(set.error);
if (set.photo_viewer != nullptr)
@@ -151,17 +171,21 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
frontend.software_keyboard = std::move(set.software_keyboard);
if (set.web_browser != nullptr)
frontend.web_browser = std::move(set.web_browser);
if (set.e_commerce != nullptr)
frontend.e_commerce = std::move(set.e_commerce);
}
void AppletManager::SetDefaultAppletFrontendSet() {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
frontend.software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
ClearAll();
SetDefaultAppletsIfMissing();
}
void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.parental_controls == nullptr) {
frontend.parental_controls =
std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
}
if (frontend.error == nullptr) {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
}
@@ -182,14 +206,20 @@ void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.web_browser == nullptr) {
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
}
if (frontend.e_commerce == nullptr) {
frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
}
}
void AppletManager::ClearAll() {
frontend = {};
}
std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const {
switch (id) {
case AppletId::Auth:
return std::make_shared<Auth>(*frontend.parental_controls);
case AppletId::Error:
return std::make_shared<Error>(*frontend.error);
case AppletId::ProfileSelect:
@@ -198,13 +228,16 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard);
case AppletId::PhotoViewer:
return std::make_shared<PhotoViewer>(*frontend.photo_viewer);
case AppletId::LibAppletShop:
return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id,
frontend.e_commerce.get());
case AppletId::LibAppletOff:
return std::make_shared<WebBrowser>(*frontend.web_browser);
return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id);
default:
UNIMPLEMENTED_MSG(
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
static_cast<u8>(id));
return std::make_shared<StubApplet>();
return std::make_shared<StubApplet>(id);
}
}

View File

@@ -13,7 +13,9 @@
union ResultCode;
namespace Core::Frontend {
class ECommerceApplet;
class ErrorApplet;
class ParentalControlsApplet;
class PhotoViewerApplet;
class ProfileSelectApplet;
class SoftwareKeyboardApplet;
@@ -54,6 +56,14 @@ public:
AppletDataBroker();
~AppletDataBroker();
struct RawChannelData {
std::vector<std::vector<u8>> normal;
std::vector<std::vector<u8>> interactive;
};
// Retrieves but does not pop the data sent to applet.
RawChannelData PeekDataToAppletForDebug() const;
std::unique_ptr<IStorage> PopNormalDataToGame();
std::unique_ptr<IStorage> PopNormalDataToApplet();
@@ -76,16 +86,16 @@ private:
// Queues are named from applet's perspective
// PopNormalDataToApplet and PushNormalDataFromGame
std::queue<std::unique_ptr<IStorage>> in_channel;
std::deque<std::unique_ptr<IStorage>> in_channel;
// PopNormalDataToGame and PushNormalDataFromApplet
std::queue<std::unique_ptr<IStorage>> out_channel;
std::deque<std::unique_ptr<IStorage>> out_channel;
// PopInteractiveDataToApplet and PushInteractiveDataFromGame
std::queue<std::unique_ptr<IStorage>> in_interactive_channel;
std::deque<std::unique_ptr<IStorage>> in_interactive_channel;
// PopInteractiveDataToGame and PushInteractiveDataFromApplet
std::queue<std::unique_ptr<IStorage>> out_interactive_channel;
std::deque<std::unique_ptr<IStorage>> out_interactive_channel;
Kernel::EventPair state_changed_event;
@@ -137,15 +147,19 @@ protected:
};
struct AppletFrontendSet {
using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
AppletFrontendSet();
AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer, ProfileSelect profile_select,
SoftwareKeyboard software_keyboard, WebBrowser web_browser);
AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
PhotoViewer photo_viewer, ProfileSelect profile_select,
SoftwareKeyboard software_keyboard, WebBrowser web_browser,
ECommerceApplet e_commerce);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -154,11 +168,13 @@ struct AppletFrontendSet {
AppletFrontendSet(AppletFrontendSet&&) noexcept;
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
ParentalControlsApplet parental_controls;
ErrorApplet error;
PhotoViewer photo_viewer;
ProfileSelect profile_select;
SoftwareKeyboard software_keyboard;
WebBrowser web_browser;
ECommerceApplet e_commerce;
};
class AppletManager {
@@ -171,7 +187,7 @@ public:
void SetDefaultAppletsIfMissing();
void ClearAll();
std::shared_ptr<Applet> GetApplet(AppletId id) const;
std::shared_ptr<Applet> GetApplet(AppletId id, u64 current_process_title_id) const;
private:
AppletFrontendSet frontend;

View File

@@ -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;

View File

@@ -13,9 +13,12 @@
#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 {
constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221};
static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet();
for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) {
@@ -34,6 +37,120 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix)
}
}
Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {}
Auth::~Auth() = default;
void Auth::Initialize() {
Applet::Initialize();
complete = false;
const auto storage = broker.PopNormalDataToApplet();
ASSERT(storage != nullptr);
const auto data = storage->GetData();
ASSERT(data.size() >= 0xC);
struct Arg {
INSERT_PADDING_BYTES(4);
AuthAppletType type;
u8 arg0;
u8 arg1;
u8 arg2;
INSERT_PADDING_BYTES(1);
};
static_assert(sizeof(Arg) == 0xC, "Arg (AuthApplet) has incorrect size.");
Arg arg{};
std::memcpy(&arg, data.data(), sizeof(Arg));
type = arg.type;
arg0 = arg.arg0;
arg1 = arg.arg1;
arg2 = arg.arg2;
}
bool Auth::TransactionComplete() const {
return complete;
}
ResultCode Auth::GetStatus() const {
return successful ? RESULT_SUCCESS : ERROR_INVALID_PIN;
}
void Auth::ExecuteInteractive() {
UNREACHABLE_MSG("Unexpected interactive applet data.");
}
void Auth::Execute() {
if (complete) {
return;
}
const auto unimplemented_log = [this] {
UNIMPLEMENTED_MSG("Unimplemented Auth applet type for type={:08X}, arg0={:02X}, "
"arg1={:02X}, arg2={:02X}",
static_cast<u32>(type), arg0, arg1, arg2);
};
switch (type) {
case AuthAppletType::ShowParentalAuthentication: {
const auto callback = [this](bool successful) { AuthFinished(successful); };
if (arg0 == 1 && arg1 == 0 && arg2 == 1) {
// ShowAuthenticatorForConfiguration
frontend.VerifyPINForSettings(callback);
} else if (arg1 == 0 && arg2 == 0) {
// ShowParentalAuthentication(bool)
frontend.VerifyPIN(callback, static_cast<bool>(arg0));
} else {
unimplemented_log();
}
break;
}
case AuthAppletType::RegisterParentalPasscode: {
const auto callback = [this] { AuthFinished(true); };
if (arg0 == 0 && arg1 == 0 && arg2 == 0) {
// RegisterParentalPasscode
frontend.RegisterPIN(callback);
} else {
unimplemented_log();
}
break;
}
case AuthAppletType::ChangeParentalPasscode: {
const auto callback = [this] { AuthFinished(true); };
if (arg0 == 0 && arg1 == 0 && arg2 == 0) {
// ChangeParentalPasscode
frontend.ChangePIN(callback);
} else {
unimplemented_log();
}
break;
}
default:
unimplemented_log();
}
}
void Auth::AuthFinished(bool successful) {
this->successful = successful;
struct Return {
ResultCode result_code;
};
static_assert(sizeof(Return) == 0x4, "Return (AuthApplet) has incorrect size.");
Return return_{GetStatus()};
std::vector<u8> out(sizeof(Return));
std::memcpy(out.data(), &return_, sizeof(Return));
broker.PushNormalDataFromApplet(IStorage{out});
broker.SignalStateChanged();
}
PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {}
PhotoViewer::~PhotoViewer() = default;
@@ -83,13 +200,20 @@ void PhotoViewer::ViewFinished() {
broker.SignalStateChanged();
}
StubApplet::StubApplet() = default;
StubApplet::StubApplet(AppletId id) : id(id) {}
StubApplet::~StubApplet() = default;
void StubApplet::Initialize() {
LOG_WARNING(Service_AM, "called (STUBBED)");
Applet::Initialize();
const auto data = broker.PeekDataToAppletForDebug();
Core::System::GetInstance().GetReporter().SaveUnimplementedAppletReport(
static_cast<u32>(id), common_args.arguments_version, common_args.library_version,
common_args.theme_color, common_args.play_startup_sound, common_args.system_tick,
data.normal, data.interactive);
LogCurrentStorage(broker, "Initialize");
}

View File

@@ -8,6 +8,36 @@
namespace Service::AM::Applets {
enum class AuthAppletType : u32 {
ShowParentalAuthentication,
RegisterParentalPasscode,
ChangeParentalPasscode,
};
class Auth final : public Applet {
public:
explicit Auth(Core::Frontend::ParentalControlsApplet& frontend);
~Auth() override;
void Initialize() override;
bool TransactionComplete() const override;
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
void AuthFinished(bool successful = true);
private:
Core::Frontend::ParentalControlsApplet& frontend;
bool complete = false;
bool successful = false;
AuthAppletType type = AuthAppletType::ShowParentalAuthentication;
u8 arg0 = 0;
u8 arg1 = 0;
u8 arg2 = 0;
};
enum class PhotoViewerAppletMode : u8 {
CurrentApp = 0,
AllApps = 1,
@@ -34,7 +64,7 @@ private:
class StubApplet final : public Applet {
public:
StubApplet();
explicit StubApplet(AppletId id);
~StubApplet() override;
void Initialize() override;
@@ -43,6 +73,9 @@ public:
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
private:
AppletId id;
};
} // namespace Service::AM::Applets

View File

@@ -19,7 +19,9 @@
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs_types.h"
#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/applets/web_browser.h"
@@ -28,74 +30,187 @@
namespace Service::AM::Applets {
// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
// but some may be worth an implementation.
constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6;
struct WebBufferHeader {
u16 count;
INSERT_PADDING_BYTES(6);
enum class WebArgTLVType : u16 {
InitialURL = 0x1,
ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name.
CallbackURL = 0x3,
CallbackableURL = 0x4,
ApplicationID = 0x5,
DocumentPath = 0x6,
DocumentKind = 0x7,
SystemDataID = 0x8,
ShareStartPage = 0x9,
Whitelist = 0xA,
News = 0xB,
UserID = 0xE,
AlbumEntry0 = 0xF,
ScreenShotEnabled = 0x10,
EcClientCertEnabled = 0x11,
Unk12 = 0x12,
PlayReportEnabled = 0x13,
Unk14 = 0x14,
Unk15 = 0x15,
BootDisplayKind = 0x17,
BackgroundKind = 0x18,
FooterEnabled = 0x19,
PointerEnabled = 0x1A,
LeftStickMode = 0x1B,
KeyRepeatFrame1 = 0x1C,
KeyRepeatFrame2 = 0x1D,
BootAsMediaPlayerInv = 0x1E,
DisplayUrlKind = 0x1F,
BootAsMediaPlayer = 0x21,
ShopJumpEnabled = 0x22,
MediaAutoPlayEnabled = 0x23,
LobbyParameter = 0x24,
ApplicationAlbumEntry = 0x26,
JsExtensionEnabled = 0x27,
AdditionalCommentText = 0x28,
TouchEnabledOnContents = 0x29,
UserAgentAdditionalString = 0x2A,
AdditionalMediaData0 = 0x2B,
MediaPlayerAutoCloseEnabled = 0x2C,
PageCacheEnabled = 0x2D,
WebAudioEnabled = 0x2E,
Unk2F = 0x2F,
YouTubeVideoWhitelist = 0x31,
FooterFixedKind = 0x32,
PageFadeEnabled = 0x33,
MediaCreatorApplicationRatingAge = 0x34,
BootLoadingIconEnabled = 0x35,
PageScrollIndicationEnabled = 0x36,
MediaPlayerSpeedControlEnabled = 0x37,
AlbumEntry1 = 0x38,
AlbumEntry2 = 0x39,
AlbumEntry3 = 0x3A,
AdditionalMediaData1 = 0x3B,
AdditionalMediaData2 = 0x3C,
AdditionalMediaData3 = 0x3D,
BootFooterButton = 0x3E,
OverrideWebAudioVolume = 0x3F,
OverrideMediaAudioVolume = 0x40,
BootMode = 0x41,
WebSessionEnabled = 0x42,
};
static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size.");
struct WebArgumentHeader {
u16 type;
enum class ShimKind : u32 {
Shop = 1,
Login = 2,
Offline = 3,
Share = 4,
Web = 5,
Wifi = 6,
Lobby = 7,
};
enum class ShopWebTarget {
ApplicationInfo,
AddOnContentList,
SubscriptionList,
ConsumableItemList,
Home,
Settings,
};
namespace {
constexpr std::size_t SHIM_KIND_COUNT = 0x8;
struct WebArgHeader {
u16 count;
INSERT_PADDING_BYTES(2);
ShimKind kind;
};
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
struct WebArgTLV {
WebArgTLVType type;
u16 size;
u32 offset;
};
static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size.");
static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size.");
struct WebArgumentResult {
struct WebCommonReturnValue {
u32 result_code;
INSERT_PADDING_BYTES(0x4);
std::array<char, 0x1000> last_url;
u64 last_url_size;
};
static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size.");
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) {
WebBufferHeader header;
ASSERT(sizeof(WebBufferHeader) <= data.size());
std::memcpy(&header, data.data(), sizeof(WebBufferHeader));
struct WebWifiPageArg {
INSERT_PADDING_BYTES(4);
std::array<char, 0x100> connection_test_url;
std::array<char, 0x400> initial_url;
std::array<u8, 0x10> nifm_network_uuid;
u32 nifm_requirement;
};
static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size.");
u64 offset = sizeof(WebBufferHeader);
for (u16 i = 0; i < header.count; ++i) {
WebArgumentHeader arg;
ASSERT(offset + sizeof(WebArgumentHeader) <= data.size());
std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader));
offset += sizeof(WebArgumentHeader);
struct WebWifiReturnValue {
INSERT_PADDING_BYTES(4);
u32 result;
};
static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size.");
if (arg.type == type) {
std::vector<u8> out(arg.size);
offset += arg.offset;
ASSERT(offset + arg.size <= data.size());
std::memcpy(out.data(), data.data() + offset, out.size());
enum class OfflineWebSource : u32 {
OfflineHtmlPage = 0x1,
ApplicationLegalInformation = 0x2,
SystemDataPage = 0x3,
};
std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) {
if (arg.size() < sizeof(WebArgHeader))
return {};
WebArgHeader header{};
std::memcpy(&header, arg.data(), sizeof(WebArgHeader));
std::map<WebArgTLVType, std::vector<u8>> out;
u64 offset = sizeof(WebArgHeader);
for (std::size_t i = 0; i < header.count; ++i) {
if (arg.size() < (offset + sizeof(WebArgTLV)))
return out;
}
offset += arg.offset + arg.size;
WebArgTLV tlv{};
std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV));
offset += sizeof(WebArgTLV);
offset += tlv.offset;
if (arg.size() < (offset + tlv.size))
return out;
std::vector<u8> data(tlv.size);
std::memcpy(data.data(), arg.data() + offset, tlv.size);
offset += tlv.size;
out.insert_or_assign(tlv.type, data);
}
return {};
return out;
}
static FileSys::VirtualFile GetManualRomFS() {
auto& loader{Core::System::GetInstance().GetAppLoader()};
FileSys::VirtualFile out;
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
return out;
FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) {
const auto& installed{Core::System::GetInstance().GetContentProvider()};
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
FileSys::ContentRecordType::Manual);
const auto res = installed.GetEntry(title_id, type);
if (res != nullptr)
if (res != nullptr) {
return res->GetRomFS();
}
if (type == FileSys::ContentRecordType::Data) {
return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
}
return nullptr;
}
WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend) : frontend(frontend) {}
} // Anonymous namespace
WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id,
Core::Frontend::ECommerceApplet* frontend_e_commerce)
: frontend(frontend), frontend_e_commerce(frontend_e_commerce),
current_process_title_id(current_process_title_id) {}
WebBrowser::~WebBrowser() = default;
@@ -111,24 +226,12 @@ void WebBrowser::Initialize() {
ASSERT(web_arg_storage != nullptr);
const auto& web_arg = web_arg_storage->GetData();
const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE);
filename = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(url_data.data()), url_data.size());
ASSERT(web_arg.size() >= 0x8);
std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind));
temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
"web_applet_manual",
FileUtil::DirectorySeparator::PlatformDefault);
FileUtil::DeleteDirRecursively(temporary_dir);
args = GetWebArguments(web_arg);
manual_romfs = GetManualRomFS();
if (manual_romfs == nullptr) {
status = ResultCode(-1);
LOG_ERROR(Service_AM, "Failed to find manual for current process!");
}
filename =
FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename,
FileUtil::DirectorySeparator::PlatformDefault);
InitializeInternal();
}
bool WebBrowser::TransactionComplete() const {
@@ -144,24 +247,25 @@ void WebBrowser::ExecuteInteractive() {
}
void WebBrowser::Execute() {
if (complete)
if (complete) {
return;
}
if (status != RESULT_SUCCESS) {
complete = true;
return;
}
frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
ExecuteInternal();
}
void WebBrowser::UnpackRomFS() {
if (unpacked)
return;
ASSERT(manual_romfs != nullptr);
ASSERT(offline_romfs != nullptr);
const auto dir =
FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard);
FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
const auto& vfs{Core::System::GetInstance().GetFilesystem()};
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
FileSys::VfsRawCopyD(dir, temp_dir);
@@ -172,17 +276,275 @@ void WebBrowser::UnpackRomFS() {
void WebBrowser::Finalize() {
complete = true;
WebArgumentResult out{};
WebCommonReturnValue out{};
out.result_code = 0;
out.last_url_size = 0;
std::vector<u8> data(sizeof(WebArgumentResult));
std::memcpy(data.data(), &out, sizeof(WebArgumentResult));
std::vector<u8> data(sizeof(WebCommonReturnValue));
std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue));
broker.PushNormalDataFromApplet(IStorage{data});
broker.SignalStateChanged();
if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) {
FileUtil::DeleteDirRecursively(temporary_dir);
}
}
void WebBrowser::InitializeInternal() {
using WebAppletInitializer = void (WebBrowser::*)();
constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{
nullptr, &WebBrowser::InitializeShop,
nullptr, &WebBrowser::InitializeOffline,
nullptr, nullptr,
nullptr, nullptr,
};
const auto index = static_cast<u32>(kind);
if (index > functions.size() || functions[index] == nullptr) {
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
return;
}
const auto function = functions[index];
(this->*function)();
}
void WebBrowser::ExecuteInternal() {
using WebAppletExecutor = void (WebBrowser::*)();
constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{
nullptr, &WebBrowser::ExecuteShop,
nullptr, &WebBrowser::ExecuteOffline,
nullptr, nullptr,
nullptr, nullptr,
};
const auto index = static_cast<u32>(kind);
if (index > functions.size() || functions[index] == nullptr) {
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
return;
}
const auto function = functions[index];
(this->*function)();
}
void WebBrowser::InitializeShop() {
if (frontend_e_commerce == nullptr) {
LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!");
status = ResultCode(-1);
return;
}
const auto user_id_data = args.find(WebArgTLVType::UserID);
user_id = std::nullopt;
if (user_id_data != args.end()) {
user_id = u128{};
std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128));
}
const auto url = args.find(WebArgTLVType::ShopArgumentsURL);
if (url == args.end()) {
LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!");
status = ResultCode(-1);
return;
}
std::vector<std::string> split_query;
Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(url->second.data()), url->second.size()),
'?', split_query);
// 2 -> Main URL '?' Query Parameters
// Less is missing info, More is malformed
if (split_query.size() != 2) {
LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed");
status = ResultCode(-1);
return;
}
std::vector<std::string> queries;
Common::SplitString(split_query[1], '&', queries);
const auto split_single_query =
[](const std::string& in) -> std::pair<std::string, std::string> {
const auto index = in.find('=');
if (index == std::string::npos || index == in.size() - 1) {
return {in, ""};
}
return {in.substr(0, index), in.substr(index + 1)};
};
std::transform(queries.begin(), queries.end(),
std::inserter(shop_query, std::next(shop_query.begin())), split_single_query);
const auto scene = shop_query.find("scene");
if (scene == shop_query.end()) {
LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!");
status = ResultCode(-1);
return;
}
const std::map<std::string, ShopWebTarget, std::less<>> target_map{
{"product_detail", ShopWebTarget::ApplicationInfo},
{"aocs", ShopWebTarget::AddOnContentList},
{"subscriptions", ShopWebTarget::SubscriptionList},
{"consumption", ShopWebTarget::ConsumableItemList},
{"settings", ShopWebTarget::Settings},
{"top", ShopWebTarget::Home},
};
const auto target = target_map.find(scene->second);
if (target == target_map.end()) {
LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second);
status = ResultCode(-1);
return;
}
shop_web_target = target->second;
const auto title_id_data = shop_query.find("dst_app_id");
if (title_id_data != shop_query.end()) {
title_id = std::stoull(title_id_data->second, nullptr, 0x10);
}
const auto mode_data = shop_query.find("mode");
if (mode_data != shop_query.end()) {
shop_full_display = mode_data->second == "full";
}
}
void WebBrowser::InitializeOffline() {
if (args.find(WebArgTLVType::DocumentPath) == args.end() ||
args.find(WebArgTLVType::DocumentKind) == args.end() ||
args.find(WebArgTLVType::ApplicationID) == args.end()) {
status = ResultCode(-1);
LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!");
}
const auto url_data = args[WebArgTLVType::DocumentPath];
filename = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(url_data.data()), url_data.size());
OfflineWebSource source;
ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4);
std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource));
constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{
"manual",
"legal",
"system",
};
temporary_dir =
FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" +
WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
FileUtil::DirectorySeparator::PlatformDefault);
FileUtil::DeleteDirRecursively(temporary_dir);
u64 title_id = 0; // 0 corresponds to current process
ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64));
FileSys::ContentRecordType type = FileSys::ContentRecordType::Data;
switch (source) {
case OfflineWebSource::OfflineHtmlPage:
// While there is an AppID TLV field, in official SW this is always ignored.
title_id = 0;
type = FileSys::ContentRecordType::Manual;
break;
case OfflineWebSource::ApplicationLegalInformation:
type = FileSys::ContentRecordType::Legal;
break;
case OfflineWebSource::SystemDataPage:
type = FileSys::ContentRecordType::Data;
break;
}
if (title_id == 0) {
title_id = current_process_title_id;
}
offline_romfs = GetApplicationRomFS(title_id, type);
if (offline_romfs == nullptr) {
status = ResultCode(-1);
LOG_ERROR(Service_AM, "Failed to find offline data for request!");
}
std::string path_additional_directory;
if (source == OfflineWebSource::OfflineHtmlPage) {
path_additional_directory = std::string(DIR_SEP).append("html-document");
}
filename =
FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
FileUtil::DirectorySeparator::PlatformDefault);
}
void WebBrowser::ExecuteShop() {
const auto callback = [this]() { Finalize(); };
const auto check_optional_parameter = [this](const auto& p) {
if (!p.has_value()) {
LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!");
status = ResultCode(-1);
return false;
}
return true;
};
switch (shop_web_target) {
case ShopWebTarget::ApplicationInfo:
if (!check_optional_parameter(title_id))
return;
frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id,
shop_full_display, shop_extra_parameter);
break;
case ShopWebTarget::AddOnContentList:
if (!check_optional_parameter(title_id))
return;
frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display);
break;
case ShopWebTarget::ConsumableItemList:
if (!check_optional_parameter(title_id))
return;
frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id);
break;
case ShopWebTarget::Home:
if (!check_optional_parameter(user_id))
return;
if (!check_optional_parameter(shop_full_display))
return;
frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display);
break;
case ShopWebTarget::Settings:
if (!check_optional_parameter(user_id))
return;
if (!check_optional_parameter(shop_full_display))
return;
frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display);
break;
case ShopWebTarget::SubscriptionList:
if (!check_optional_parameter(title_id))
return;
frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id);
break;
default:
UNREACHABLE();
}
}
void WebBrowser::ExecuteOffline() {
frontend.OpenPageLocal(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
}
} // namespace Service::AM::Applets

View File

@@ -4,15 +4,22 @@
#pragma once
#include <map>
#include "core/file_sys/vfs_types.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
namespace Service::AM::Applets {
enum class ShimKind : u32;
enum class ShopWebTarget;
enum class WebArgTLVType : u16;
class WebBrowser final : public Applet {
public:
WebBrowser(Core::Frontend::WebBrowserApplet& frontend);
WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id,
Core::Frontend::ECommerceApplet* frontend_e_commerce = nullptr);
~WebBrowser() override;
void Initialize() override;
@@ -32,15 +39,41 @@ public:
void Finalize();
private:
void InitializeInternal();
void ExecuteInternal();
// Specific initializers for the types of web applets
void InitializeShop();
void InitializeOffline();
// Specific executors for the types of web applets
void ExecuteShop();
void ExecuteOffline();
Core::Frontend::WebBrowserApplet& frontend;
// Extra frontends for specialized functions
Core::Frontend::ECommerceApplet* frontend_e_commerce;
bool complete = false;
bool unpacked = false;
ResultCode status = RESULT_SUCCESS;
FileSys::VirtualFile manual_romfs;
u64 current_process_title_id;
ShimKind kind;
std::map<WebArgTLVType, std::vector<u8>> args;
FileSys::VirtualFile offline_romfs;
std::string temporary_dir;
std::string filename;
ShopWebTarget shop_web_target;
std::map<std::string, std::string, std::less<>> shop_query;
std::optional<u64> title_id = 0;
std::optional<u128> user_id;
std::optional<bool> shop_full_display;
std::string shop_extra_parameter;
};
} // namespace Service::AM::Applets

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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));
}
@@ -201,7 +204,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
SM::ServiceManager::InstallInterfaces(sm);
Account::InstallInterfaces(system);
AM::InstallInterfaces(*sm, nv_flinger);
AM::InstallInterfaces(*sm, nv_flinger, system);
AOC::InstallInterfaces(*sm);
APM::InstallInterfaces(*sm);
ARP::InstallInterfaces(*sm);

View File

@@ -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

View File

@@ -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

102
src/core/loader/kip.cpp Normal file
View File

@@ -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

35
src/core/loader/kip.h Normal file
View File

@@ -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

View File

@@ -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));

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

353
src/core/reporter.cpp Normal file
View File

@@ -0,0 +1,353 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fstream>
#include <json.hpp>
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/scm_rev.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
#include "core/reporter.h"
#include "core/settings.h"
#include "fmt/time.h"
namespace {
std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir),
type, title_id, timestamp);
}
std::string GetTimestamp() {
const auto time = std::time(nullptr);
return fmt::format("{:%FT%H-%M-%S}", *std::localtime(&time));
}
using namespace nlohmann;
void SaveToFile(const json& json, const std::string& filename) {
if (!FileUtil::CreateFullPath(filename))
LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename);
std::ofstream file(
FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault));
file << std::setw(4) << json << std::endl;
}
json GetYuzuVersionData() {
return {
{"scm_rev", std::string(Common::g_scm_rev)},
{"scm_branch", std::string(Common::g_scm_branch)},
{"scm_desc", std::string(Common::g_scm_desc)},
{"build_name", std::string(Common::g_build_name)},
{"build_date", std::string(Common::g_build_date)},
{"build_fullname", std::string(Common::g_build_fullname)},
{"build_version", std::string(Common::g_build_version)},
{"shader_cache_version", std::string(Common::g_shader_cache_version)},
};
}
json GetReportCommonData(u64 title_id, ResultCode result, const std::string& timestamp,
std::optional<u128> user_id = {}) {
auto out = json{
{"title_id", fmt::format("{:016X}", title_id)},
{"result_raw", fmt::format("{:08X}", result.raw)},
{"result_module", fmt::format("{:08X}", static_cast<u32>(result.module.Value()))},
{"result_description", fmt::format("{:08X}", result.description.Value())},
{"timestamp", timestamp},
};
if (user_id.has_value())
out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]);
return out;
}
json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64 sp, u64 pc,
u64 pstate, std::array<u64, 31> registers,
std::optional<std::array<u64, 32>> backtrace = {}) {
auto out = json{
{"entry_point", fmt::format("{:016X}", entry_point)},
{"sp", fmt::format("{:016X}", sp)},
{"pc", fmt::format("{:016X}", pc)},
{"pstate", fmt::format("{:016X}", pstate)},
{"architecture", architecture},
};
auto registers_out = json::object();
for (std::size_t i = 0; i < registers.size(); ++i) {
registers_out[fmt::format("X{:02d}", i)] = fmt::format("{:016X}", registers[i]);
}
out["registers"] = std::move(registers_out);
if (backtrace.has_value()) {
auto backtrace_out = json::array();
for (const auto& entry : *backtrace) {
backtrace_out.push_back(fmt::format("{:016X}", entry));
}
out["backtrace"] = std::move(backtrace_out);
}
return out;
}
json GetProcessorStateDataAuto(Core::System& system) {
const auto* process{system.CurrentProcess()};
const auto& vm_manager{process->VMManager()};
auto& arm{system.CurrentArmInterface()};
Core::ARM_Interface::ThreadContext context{};
arm.SaveContext(context);
return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
vm_manager.GetCodeRegionBaseAddress(), context.sp, context.pc,
context.pstate, context.cpu_registers);
}
json GetBacktraceData(Core::System& system) {
auto out = json::array();
const auto& backtrace{system.CurrentArmInterface().GetBacktrace()};
for (const auto& entry : backtrace) {
out.push_back({
{"module", entry.module},
{"address", fmt::format("{:016X}", entry.address)},
{"original_address", fmt::format("{:016X}", entry.original_address)},
{"offset", fmt::format("{:016X}", entry.offset)},
{"symbol_name", entry.name},
});
}
return out;
}
json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& system) {
json out;
out["yuzu_version"] = GetYuzuVersionData();
out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp);
out["processor_state"] = GetProcessorStateDataAuto(system);
out["backtrace"] = GetBacktraceData(system);
return out;
}
template <bool read_value, typename DescriptorType>
json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer) {
auto buffer_out = json::array();
for (const auto& desc : buffer) {
auto entry = json{
{"address", fmt::format("{:016X}", desc.Address())},
{"size", fmt::format("{:016X}", desc.Size())},
};
if constexpr (read_value) {
std::vector<u8> data(desc.Size());
Memory::ReadBlock(desc.Address(), data.data(), desc.Size());
entry["data"] = Common::HexToString(data);
}
buffer_out.push_back(std::move(entry));
}
return buffer_out;
}
json GetHLERequestContextData(Kernel::HLERequestContext& ctx) {
json out;
auto cmd_buf = json::array();
for (std::size_t i = 0; i < IPC::COMMAND_BUFFER_LENGTH; ++i) {
cmd_buf.push_back(fmt::format("{:08X}", ctx.CommandBuffer()[i]));
}
out["command_buffer"] = std::move(cmd_buf);
out["buffer_descriptor_a"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorA());
out["buffer_descriptor_b"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorB());
out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC());
out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX());
return std::move(out);
}
} // Anonymous namespace
namespace Core {
Reporter::Reporter(Core::System& system) : system(system) {}
Reporter::~Reporter() = default;
void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point,
u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
const std::array<u64, 31>& registers,
const std::array<u64, 32>& backtrace, u32 backtrace_size,
const std::string& arch, u32 unk10) const {
if (!IsReportingEnabled())
return;
const auto timestamp = GetTimestamp();
json out;
out["yuzu_version"] = GetYuzuVersionData();
out["report_common"] = GetReportCommonData(title_id, result, timestamp);
auto proc_out = GetProcessorStateData(arch, entry_point, sp, pc, pstate, registers, backtrace);
proc_out["set_flags"] = fmt::format("{:016X}", set_flags);
proc_out["afsr0"] = fmt::format("{:016X}", afsr0);
proc_out["afsr1"] = fmt::format("{:016X}", afsr1);
proc_out["esr"] = fmt::format("{:016X}", esr);
proc_out["far"] = fmt::format("{:016X}", far);
proc_out["backtrace_size"] = fmt::format("{:08X}", backtrace_size);
proc_out["unknown_10"] = fmt::format("{:08X}", unk10);
out["processor_state"] = std::move(proc_out);
SaveToFile(std::move(out), GetPath("crash_report", title_id, timestamp));
}
void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
std::optional<std::vector<u8>> resolved_buffer) const {
if (!IsReportingEnabled())
return;
const auto timestamp = GetTimestamp();
const auto title_id = system.CurrentProcess()->GetTitleID();
auto out = GetFullDataAuto(timestamp, title_id, system);
auto break_out = json{
{"type", fmt::format("{:08X}", type)},
{"signal_debugger", fmt::format("{}", signal_debugger)},
{"info1", fmt::format("{:016X}", info1)},
{"info2", fmt::format("{:016X}", info2)},
};
if (resolved_buffer.has_value()) {
break_out["debug_buffer"] = Common::HexToString(*resolved_buffer);
}
out["svc_break"] = std::move(break_out);
SaveToFile(std::move(out), GetPath("svc_break_report", title_id, timestamp));
}
void Reporter::SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
const std::string& name,
const std::string& service_name) const {
if (!IsReportingEnabled())
return;
const auto timestamp = GetTimestamp();
const auto title_id = system.CurrentProcess()->GetTitleID();
auto out = GetFullDataAuto(timestamp, title_id, system);
auto function_out = GetHLERequestContextData(ctx);
function_out["command_id"] = command_id;
function_out["function_name"] = name;
function_out["service_name"] = service_name;
out["function"] = std::move(function_out);
SaveToFile(std::move(out), GetPath("unimpl_func_report", title_id, timestamp));
}
void Reporter::SaveUnimplementedAppletReport(
u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color,
bool startup_sound, u64 system_tick, std::vector<std::vector<u8>> normal_channel,
std::vector<std::vector<u8>> interactive_channel) const {
if (!IsReportingEnabled())
return;
const auto timestamp = GetTimestamp();
const auto title_id = system.CurrentProcess()->GetTitleID();
auto out = GetFullDataAuto(timestamp, title_id, system);
out["applet_common_args"] = {
{"applet_id", fmt::format("{:02X}", applet_id)},
{"common_args_version", fmt::format("{:08X}", common_args_version)},
{"library_version", fmt::format("{:08X}", library_version)},
{"theme_color", fmt::format("{:08X}", theme_color)},
{"startup_sound", fmt::format("{}", startup_sound)},
{"system_tick", fmt::format("{:016X}", system_tick)},
};
auto normal_out = json::array();
for (const auto& data : normal_channel) {
normal_out.push_back(Common::HexToString(data));
}
auto interactive_out = json::array();
for (const auto& data : interactive_channel) {
interactive_out.push_back(Common::HexToString(data));
}
out["applet_normal_data"] = std::move(normal_out);
out["applet_interactive_data"] = std::move(interactive_out);
SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp));
}
void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
std::optional<u128> user_id) const {
if (!IsReportingEnabled())
return;
const auto timestamp = GetTimestamp();
json out;
out["yuzu_version"] = GetYuzuVersionData();
out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp, user_id);
auto data_out = json::array();
for (const auto& d : data) {
data_out.push_back(Common::HexToString(d));
}
out["play_report_process_id"] = fmt::format("{:016X}", process_id);
out["play_report_data"] = std::move(data_out);
SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp));
}
void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
std::optional<std::string> custom_text_main,
std::optional<std::string> custom_text_detail) const {
if (!IsReportingEnabled())
return;
const auto timestamp = GetTimestamp();
json out;
out["yuzu_version"] = GetYuzuVersionData();
out["report_common"] = GetReportCommonData(title_id, result, timestamp);
out["processor_state"] = GetProcessorStateDataAuto(system);
out["backtrace"] = GetBacktraceData(system);
out["error_custom_text"] = {
{"main", *custom_text_main},
{"detail", *custom_text_detail},
};
SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));
}
void Reporter::SaveUserReport() const {
if (!IsReportingEnabled())
return;
const auto timestamp = GetTimestamp();
const auto title_id = system.CurrentProcess()->GetTitleID();
SaveToFile(GetFullDataAuto(timestamp, title_id, system),
GetPath("user_report", title_id, timestamp));
}
bool Reporter::IsReportingEnabled() const {
return Settings::values.reporting_services;
}
} // namespace Core

56
src/core/reporter.h Normal file
View File

@@ -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

View File

@@ -415,6 +415,7 @@ struct Values {
std::string program_args;
bool dump_exefs;
bool dump_nso;
bool reporting_services;
// WebService
bool enable_telemetry;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -16,7 +16,8 @@ namespace ArmTests {
TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_),
test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} {
auto process = Kernel::Process::Create(Core::System::GetInstance(), "");
auto process = Kernel::Process::Create(Core::System::GetInstance(), "",
Kernel::Process::ProcessType::Userland);
page_table = &process->VMManager().page_table;
std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);

View File

@@ -43,8 +43,9 @@ bool Device::TestVariableAoffi() {
// This is a unit test, please ignore me on apitrace bug reports.
uniform sampler2D tex;
uniform ivec2 variable_offset;
out vec4 output_attribute;
void main() {
gl_Position = textureOffset(tex, vec2(0), variable_offset);
output_attribute = textureOffset(tex, vec2(0), variable_offset);
}
)";
const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &AOFFI_TEST)};

View File

@@ -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"

View File

@@ -87,8 +87,8 @@ QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
QtWebBrowser::~QtWebBrowser() = default;
void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) {
void QtWebBrowser::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) {
this->unpack_romfs_callback = std::move(unpack_romfs_callback);
this->finished_callback = std::move(finished_callback);

View File

@@ -37,8 +37,8 @@ public:
explicit QtWebBrowser(GMainWindow& main_window);
~QtWebBrowser() override;
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) override;
void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
std::function<void()> finished_callback) override;
signals:
void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;

View File

@@ -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();

View File

@@ -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);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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) {

View File

@@ -814,11 +814,13 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetGPUDebugContext(debug_context);
system.SetAppletFrontendSet({
std::make_unique<QtErrorDisplay>(*this),
nullptr,
std::make_unique<QtProfileSelector>(*this),
std::make_unique<QtSoftwareKeyboard>(*this),
std::make_unique<QtWebBrowser>(*this),
nullptr, // Parental Controls
std::make_unique<QtErrorDisplay>(*this), //
nullptr, // Photo Viewer
std::make_unique<QtProfileSelector>(*this), //
std::make_unique<QtSoftwareKeyboard>(*this), //
std::make_unique<QtWebBrowser>(*this), //
nullptr, // E-Commerce
});
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};

View File

@@ -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);

View File

@@ -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()

184
src/yuzu_tester/config.cpp Normal file
View File

@@ -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();
}

24
src/yuzu_tester/config.h Normal file
View File

@@ -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();
};

View File

@@ -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
)";
}

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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

View File

@@ -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

267
src/yuzu_tester/yuzu.cpp Normal file
View File

@@ -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;
}

17
src/yuzu_tester/yuzu.rc Normal file
View File

@@ -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"