Compare commits

...

67 Commits

Author SHA1 Message Date
Tobias
cae9708a00 Revert "CMake: Get Git submodule dependencies via CMake (#2474)"
This reverts commit 5cef446f42.
2019-06-29 22:19:47 +02:00
bunnei
bb4a1e059c Merge pull request #2533 from DarkLordZach/memory-frozen
memory: Add class to manage and enforce memory freezing
2019-06-28 14:03:38 -04:00
bunnei
5829ba1ccc Merge pull request #2548 from DarkLordZach/applet-shopn
applets: Implement backend and default frontend for Parental Controls and EShop (ShopN) applets
2019-06-26 15:55:24 -04:00
bunnei
4ed2774c26 Merge pull request #2607 from DarkLordZach/arp-1
glue: Implement arp:w and arp:r services
2019-06-26 10:26:44 -04:00
Zach Hilman
d10fc2d727 glue: Correct missing bytes in ApplicationLaunchParameter 2019-06-25 22:25:10 -04:00
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
d40a38df8d core: Keep track of ARPManager and register current application on boot 2019-06-24 19:27:35 -04:00
Zach Hilman
ce21973022 glue: Implement arp:w and arp:r services
These keep track of running process' launch properties and control properties and allows for issuing and reading them by process and title ID.
2019-06-24 19:26:16 -04:00
Zach Hilman
df3ee4f444 glue: Add errors for glue/arp services 2019-06-24 19:20:28 -04:00
Zach Hilman
4fab0d392b glue: Add scaffolding for bgtc:t and bgtc:sc services 2019-06-24 19:20:06 -04:00
Zach Hilman
9003e19797 arp: Move to glue services
Glue is the name of the sysmodule that contains both arp and bgtc.
2019-06-24 19:17:48 -04:00
Zach Hilman
5f8d2a2044 glue: Add manager to keep track of application registry
Manages mapping between title IDs and application launch and control properties.
2019-06-24 19:17:13 -04:00
Zach Hilman
db2e5e5fa6 registered_cache: Add getter to determine source slot in content provider union
Used to determine StorageId source for application data.
2019-06-24 19:10:17 -04:00
Zach Hilman
a468273221 patch_manager: Add getter for title version 2019-06-24 19:05:50 -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
Zach Hilman
4111971cbd freezer: Update documentation 2019-06-20 19:22:53 -04:00
Zach Hilman
ed82fa3a91 core: Move Freezer class to tools namespace 2019-06-20 19:22:53 -04:00
Zach Hilman
c9983ad9a7 freezer: Add documentation for methods 2019-06-20 19:22:04 -04:00
Zach Hilman
1b7d619914 memory: Add class to manage and enforce memory freezing 2019-06-20 19:22:04 -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
96 changed files with 4285 additions and 429 deletions

View File

@@ -1,4 +1,3 @@
#!/bin/bash -ex
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
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

View File

@@ -7,18 +7,6 @@ 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)
@@ -45,6 +33,22 @@ 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
@@ -207,8 +209,6 @@ add_library(core STATIC
hle/service/apm/apm.h
hle/service/apm/interface.cpp
hle/service/apm/interface.h
hle/service/arp/arp.cpp
hle/service/arp/arp.h
hle/service/audio/audctl.cpp
hle/service/audio/audctl.h
hle/service/audio/auddbg.cpp
@@ -274,6 +274,15 @@ add_library(core STATIC
hle/service/friend/friend.h
hle/service/friend/interface.cpp
hle/service/friend/interface.h
hle/service/glue/arp.cpp
hle/service/glue/arp.h
hle/service/glue/bgtc.cpp
hle/service/glue/bgtc.h
hle/service/glue/errors.h
hle/service/glue/glue.cpp
hle/service/glue/glue.h
hle/service/glue/manager.cpp
hle/service/glue/manager.h
hle/service/grc/grc.cpp
hle/service/grc/grc.h
hle/service/hid/hid.cpp
@@ -440,6 +449,8 @@ add_library(core STATIC
loader/deconstructed_rom_directory.h
loader/elf.cpp
loader/elf.h
loader/kip.cpp
loader/kip.h
loader/loader.cpp
loader/loader.h
loader/nax.cpp
@@ -459,16 +470,20 @@ add_library(core STATIC
memory_setup.h
perf_stats.cpp
perf_stats.h
reporter.cpp
reporter.h
settings.cpp
settings.h
telemetry_session.cpp
telemetry_session.h
tools/freezer.cpp
tools/freezer.h
)
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt mbedtls opus unicorn open_source_archives)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)

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

@@ -25,19 +25,47 @@
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/glue/manager.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/reporter.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
#include "core/tools/freezer.h"
#include "file_sys/cheat_engine.h"
#include "file_sys/patch_manager.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
namespace Core {
namespace {
FileSys::StorageId GetStorageIdForFrontendSlot(
std::optional<FileSys::ContentProviderUnionSlot> slot) {
if (!slot.has_value()) {
return FileSys::StorageId::None;
}
switch (*slot) {
case FileSys::ContentProviderUnionSlot::UserNAND:
return FileSys::StorageId::NandUser;
case FileSys::ContentProviderUnionSlot::SysNAND:
return FileSys::StorageId::NandSystem;
case FileSys::ContentProviderUnionSlot::SDMC:
return FileSys::StorageId::SdCard;
case FileSys::ContentProviderUnionSlot::FrontendManual:
return FileSys::StorageId::Host;
default:
return FileSys::StorageId::None;
}
}
} // Anonymous namespace
/*static*/ System System::s_instance;
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
@@ -74,7 +102,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
return vfs->OpenFile(path, FileSys::Mode::Read);
}
struct System::Impl {
explicit Impl(System& system) : kernel{system}, cpu_core_manager{system} {}
explicit Impl(System& system) : kernel{system}, cpu_core_manager{system}, reporter{system} {}
Cpu& CurrentCpuCore() {
return cpu_core_manager.GetCurrentCore();
@@ -109,6 +137,9 @@ struct System::Impl {
/// Create default implementations of applets if one is not provided.
applet_manager.SetDefaultAppletsIfMissing();
/// Reset all glue registrations
arp_manager.ResetAll();
telemetry_session = std::make_unique<Core::TelemetrySession>();
service_manager = std::make_shared<Service::SM::ServiceManager>();
@@ -150,7 +181,8 @@ struct System::Impl {
}
telemetry_session->AddInitialInfo(*app_loader);
auto main_process = Kernel::Process::Create(system, "main");
auto main_process =
Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland);
const auto [load_result, load_parameters] = app_loader->Load(*main_process);
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
@@ -159,6 +191,7 @@ struct System::Impl {
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
static_cast<u32>(load_result));
}
AddGlueRegistrationForProcess(*app_loader, *main_process);
kernel.MakeCurrentProcess(main_process.get());
// Main process has been loaded and been made current.
@@ -217,6 +250,31 @@ struct System::Impl {
return app_loader->ReadTitle(out);
}
void AddGlueRegistrationForProcess(Loader::AppLoader& loader, Kernel::Process& process) {
std::vector<u8> nacp_data;
FileSys::NACP nacp;
if (loader.ReadControlData(nacp) == Loader::ResultStatus::Success) {
nacp_data = nacp.GetRawBytes();
} else {
nacp_data.resize(sizeof(FileSys::RawNACP));
}
Service::Glue::ApplicationLaunchProperty launch{};
launch.title_id = process.GetTitleID();
FileSys::PatchManager pm{launch.title_id};
launch.version = pm.GetGameVersion().value_or(0);
// TODO(DarkLordZach): When FSController/Game Card Support is added, if
// current_process_game_card use correct StorageId
launch.base_game_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry(
launch.title_id, FileSys::ContentRecordType::Program));
launch.update_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry(
FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
arp_manager.Register(launch.title_id, launch, std::move(nacp_data));
}
void SetStatus(ResultStatus new_status, const char* details = nullptr) {
status = new_status;
if (details) {
@@ -243,16 +301,22 @@ struct System::Impl {
bool is_powered_on = false;
std::unique_ptr<FileSys::CheatEngine> cheat_engine;
std::unique_ptr<Tools::Freezer> memory_freezer;
/// Frontend applets
Service::AM::Applets::AppletManager applet_manager;
/// Glue services
Service::Glue::ARPManager arp_manager;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
/// Telemetry session for this emulation session
std::unique_ptr<Core::TelemetrySession> telemetry_session;
Reporter reporter;
ResultStatus status = ResultStatus::Success;
std::string status_details = "";
@@ -492,6 +556,18 @@ void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
impl->content_provider->ClearSlot(slot);
}
const Reporter& System::GetReporter() const {
return impl->reporter;
}
Service::Glue::ARPManager& System::GetARPManager() {
return impl->arp_manager;
}
const Service::Glue::ARPManager& System::GetARPManager() const {
return impl->arp_manager;
}
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
return impl->Init(*this, emu_window);
}

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"
@@ -42,6 +43,10 @@ struct AppletFrontendSet;
class AppletManager;
} // namespace AM::Applets
namespace Glue {
class ARPManager;
}
namespace SM {
class ServiceManager;
} // namespace SM
@@ -68,6 +73,7 @@ class Cpu;
class ExclusiveMonitor;
class FrameLimiter;
class PerfStats;
class Reporter;
class TelemetrySession;
struct PerfStatsResults;
@@ -284,6 +290,12 @@ public:
void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
const Reporter& GetReporter() const;
Service::Glue::ARPManager& GetARPManager();
const Service::Glue::ARPManager& GetARPManager() const;
private:
System();

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

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

@@ -493,6 +493,16 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
return out;
}
std::optional<u32> PatchManager::GetGameVersion() const {
const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto update_tid = GetUpdateTitleID(title_id);
if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
return installed.GetEntryVersion(update_tid);
}
return installed.GetEntryVersion(title_id);
}
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
const auto& installed = Core::System::GetInstance().GetContentProvider();

View File

@@ -66,8 +66,13 @@ public:
std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
VirtualFile update_raw = nullptr) const;
// Given title_id of the program, attempts to get the control data of the update and parse it,
// falling back to the base control data.
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
// std::nullopt
std::optional<u32> GetGameVersion() const;
// Given title_id of the program, attempts to get the control data of the update and parse
// it, falling back to the base control data.
std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
// Version of GetControlMetadata that takes an arbitrary NCA

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

@@ -645,6 +645,20 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnion
return out;
}
std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(
u64 title_id, ContentRecordType type) const {
const auto iter =
std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) {
return provider.second != nullptr && provider.second->HasEntry(title_id, type);
});
if (iter == providers.end()) {
return std::nullopt;
}
return iter->first;
}
ManualContentProvider::~ManualContentProvider() = default;
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,

View File

@@ -199,6 +199,9 @@ public:
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
std::optional<u64> title_id = {}) const;
std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id,
ContentRecordType type) const;
private:
std::map<ContentProviderUnionSlot, ContentProvider*> providers;
};

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

@@ -1,75 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/arp/arp.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::ARP {
class ARP_R final : public ServiceFramework<ARP_R> {
public:
explicit ARP_R() : ServiceFramework{"arp:r"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetApplicationLaunchProperty"},
{1, nullptr, "GetApplicationLaunchPropertyWithApplicationId"},
{2, nullptr, "GetApplicationControlProperty"},
{3, nullptr, "GetApplicationControlPropertyWithApplicationId"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IRegistrar final : public ServiceFramework<IRegistrar> {
public:
explicit IRegistrar() : ServiceFramework{"IRegistrar"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Issue"},
{1, nullptr, "SetApplicationLaunchProperty"},
{2, nullptr, "SetApplicationControlProperty"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class ARP_W final : public ServiceFramework<ARP_W> {
public:
explicit ARP_W() : ServiceFramework{"arp:w"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"},
{1, nullptr, "DeleteProperties"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void AcquireRegistrar(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ARP, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IRegistrar>();
}
};
void InstallInterfaces(SM::ServiceManager& sm) {
std::make_shared<ARP_R>()->InstallAsService(sm);
std::make_shared<ARP_W>()->InstallAsService(sm);
}
} // namespace Service::ARP

View File

@@ -1,16 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Service::SM {
class ServiceManager;
}
namespace Service::ARP {
/// Registers all ARP services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& sm);
} // namespace Service::ARP

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

@@ -0,0 +1,297 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "common/logging/log.h"
#include "core/file_sys/control_metadata.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/glue/arp.h"
#include "core/hle/service/glue/errors.h"
#include "core/hle/service/glue/manager.h"
#include "core/hle/service/service.h"
namespace Service::Glue {
namespace {
std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 process_id) {
const auto& list = system.Kernel().GetProcessList();
const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
return process->GetProcessID() == process_id;
});
if (iter == list.end()) {
return std::nullopt;
}
return (*iter)->GetTitleID();
}
} // Anonymous namespace
ARP_R::ARP_R(const Core::System& system, const ARPManager& manager)
: ServiceFramework{"arp:r"}, system(system), manager(manager) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"},
{1, &ARP_R::GetApplicationLaunchPropertyWithApplicationId, "GetApplicationLaunchPropertyWithApplicationId"},
{2, &ARP_R::GetApplicationControlProperty, "GetApplicationControlProperty"},
{3, &ARP_R::GetApplicationControlPropertyWithApplicationId, "GetApplicationControlPropertyWithApplicationId"},
};
// clang-format on
RegisterHandlers(functions);
}
ARP_R::~ARP_R() = default;
void ARP_R::GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto process_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
const auto title_id = GetTitleIDForProcessID(system, process_id);
if (!title_id.has_value()) {
LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_REGISTERED);
return;
}
const auto res = manager.GetLaunchProperty(*title_id);
if (res.Failed()) {
LOG_ERROR(Service_ARP, "Failed to get launch property!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res.Code());
return;
}
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(*res);
}
void ARP_R::GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
const auto res = manager.GetLaunchProperty(title_id);
if (res.Failed()) {
LOG_ERROR(Service_ARP, "Failed to get launch property!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res.Code());
return;
}
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(*res);
}
void ARP_R::GetApplicationControlProperty(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto process_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
const auto title_id = GetTitleIDForProcessID(system, process_id);
if (!title_id.has_value()) {
LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_REGISTERED);
return;
}
const auto res = manager.GetControlProperty(*title_id);
if (res.Failed()) {
LOG_ERROR(Service_ARP, "Failed to get control property!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res.Code());
return;
}
ctx.WriteBuffer(*res);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void ARP_R::GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
const auto res = manager.GetControlProperty(title_id);
if (res.Failed()) {
LOG_ERROR(Service_ARP, "Failed to get control property!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res.Code());
return;
}
ctx.WriteBuffer(*res);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
class IRegistrar final : public ServiceFramework<IRegistrar> {
friend class ARP_W;
public:
explicit IRegistrar(
std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issuer)
: ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IRegistrar::Issue, "Issue"},
{1, &IRegistrar::SetApplicationLaunchProperty, "SetApplicationLaunchProperty"},
{2, &IRegistrar::SetApplicationControlProperty, "SetApplicationControlProperty"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Issue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto process_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
if (process_id == 0) {
LOG_ERROR(Service_ARP, "Must have non-zero process ID!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_PROCESS_ID);
return;
}
if (issued) {
LOG_ERROR(Service_ARP,
"Attempted to issue registrar, but registrar is already issued!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_ACCESS);
return;
}
issue_process_id(process_id, launch, std::move(control));
issued = true;
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ARP, "called");
if (issued) {
LOG_ERROR(
Service_ARP,
"Attempted to set application launch property, but registrar is already issued!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_ACCESS);
return;
}
IPC::RequestParser rp{ctx};
launch = rp.PopRaw<ApplicationLaunchProperty>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetApplicationControlProperty(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ARP, "called");
if (issued) {
LOG_ERROR(
Service_ARP,
"Attempted to set application control property, but registrar is already issued!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_ACCESS);
return;
}
control = ctx.ReadBuffer();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issue_process_id;
bool issued = false;
ApplicationLaunchProperty launch;
std::vector<u8> control;
};
ARP_W::ARP_W(const Core::System& system, ARPManager& manager)
: ServiceFramework{"arp:w"}, system(system), manager(manager) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"},
{1, &ARP_W::DeleteProperties, "DeleteProperties"},
};
// clang-format on
RegisterHandlers(functions);
}
ARP_W::~ARP_W() = default;
void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ARP, "called");
registrar = std::make_shared<IRegistrar>(
[this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) {
const auto res = GetTitleIDForProcessID(system, process_id);
if (!res.has_value()) {
return ERR_NOT_REGISTERED;
}
return manager.Register(*res, launch, std::move(control));
});
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface(registrar);
}
void ARP_W::DeleteProperties(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto process_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
if (process_id == 0) {
LOG_ERROR(Service_ARP, "Must have non-zero process ID!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_INVALID_PROCESS_ID);
return;
}
const auto title_id = GetTitleIDForProcessID(system, process_id);
if (!title_id.has_value()) {
LOG_ERROR(Service_ARP, "No title ID for process ID!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_NOT_REGISTERED);
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(manager.Unregister(*title_id));
}
} // namespace Service::Glue

View File

@@ -0,0 +1,43 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service::Glue {
class ARPManager;
class IRegistrar;
class ARP_R final : public ServiceFramework<ARP_R> {
public:
explicit ARP_R(const Core::System& system, const ARPManager& manager);
~ARP_R() override;
private:
void GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx);
void GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx);
void GetApplicationControlProperty(Kernel::HLERequestContext& ctx);
void GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx);
const Core::System& system;
const ARPManager& manager;
};
class ARP_W final : public ServiceFramework<ARP_W> {
public:
explicit ARP_W(const Core::System& system, ARPManager& manager);
~ARP_W() override;
private:
void AcquireRegistrar(Kernel::HLERequestContext& ctx);
void DeleteProperties(Kernel::HLERequestContext& ctx);
const Core::System& system;
ARPManager& manager;
std::shared_ptr<IRegistrar> registrar;
};
} // namespace Service::Glue

View File

@@ -0,0 +1,50 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/glue/bgtc.h"
namespace Service::Glue {
BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "NotifyTaskStarting"},
{2, nullptr, "NotifyTaskFinished"},
{3, nullptr, "GetTriggerEvent"},
{4, nullptr, "IsInHalfAwake"},
{5, nullptr, "NotifyClientName"},
{6, nullptr, "IsInFullAwake"},
{11, nullptr, "ScheduleTask"},
{12, nullptr, "GetScheduledTaskInterval"},
{13, nullptr, "UnscheduleTask"},
{14, nullptr, "GetScheduleEvent"},
{15, nullptr, "SchedulePeriodicTask"},
{101, nullptr, "GetOperationMode"},
{102, nullptr, "WillDisconnectNetworkWhenEnteringSleep"},
{103, nullptr, "WillStayHalfAwakeInsteadSleep"},
};
// clang-format on
RegisterHandlers(functions);
}
BGTC_T::~BGTC_T() = default;
BGTC_SC::BGTC_SC() : ServiceFramework{"bgtc:sc"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "GetState"},
{2, nullptr, "GetStateChangedEvent"},
{3, nullptr, "NotifyEnteringHalfAwake"},
{4, nullptr, "NotifyLeavingHalfAwake"},
{5, nullptr, "SetIsUsingSleepUnsupportedDevices"},
};
// clang-format on
RegisterHandlers(functions);
}
BGTC_SC::~BGTC_SC() = default;
} // namespace Service::Glue

View File

@@ -0,0 +1,23 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service::Glue {
class BGTC_T final : public ServiceFramework<BGTC_T> {
public:
BGTC_T();
~BGTC_T() override;
};
class BGTC_SC final : public ServiceFramework<BGTC_SC> {
public:
BGTC_SC();
~BGTC_SC() override;
};
} // namespace Service::Glue

View File

@@ -0,0 +1,16 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/result.h"
namespace Service::Glue {
constexpr ResultCode ERR_INVALID_RESOURCE{ErrorModule::ARP, 0x1E};
constexpr ResultCode ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 0x1F};
constexpr ResultCode ERR_INVALID_ACCESS{ErrorModule::ARP, 0x2A};
constexpr ResultCode ERR_NOT_REGISTERED{ErrorModule::ARP, 0x66};
} // namespace Service::Glue

View File

@@ -0,0 +1,25 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "core/core.h"
#include "core/hle/service/glue/arp.h"
#include "core/hle/service/glue/bgtc.h"
#include "core/hle/service/glue/glue.h"
namespace Service::Glue {
void InstallInterfaces(Core::System& system) {
// ARP
std::make_shared<ARP_R>(system, system.GetARPManager())
->InstallAsService(system.ServiceManager());
std::make_shared<ARP_W>(system, system.GetARPManager())
->InstallAsService(system.ServiceManager());
// BackGround Task Controller
std::make_shared<BGTC_T>()->InstallAsService(system.ServiceManager());
std::make_shared<BGTC_SC>()->InstallAsService(system.ServiceManager());
}
} // namespace Service::Glue

View File

@@ -0,0 +1,16 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Core {
class System;
} // namespace Core
namespace Service::Glue {
/// Registers all Glue services with the specified service manager.
void InstallInterfaces(Core::System& system);
} // namespace Service::Glue

View File

@@ -0,0 +1,78 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/glue/errors.h"
#include "core/hle/service/glue/manager.h"
namespace Service::Glue {
struct ARPManager::MapEntry {
ApplicationLaunchProperty launch;
std::vector<u8> control;
};
ARPManager::ARPManager() = default;
ARPManager::~ARPManager() = default;
ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const {
if (title_id == 0) {
return ERR_INVALID_PROCESS_ID;
}
const auto iter = entries.find(title_id);
if (iter == entries.end()) {
return ERR_NOT_REGISTERED;
}
return MakeResult<ApplicationLaunchProperty>(iter->second.launch);
}
ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const {
if (title_id == 0) {
return ERR_INVALID_PROCESS_ID;
}
const auto iter = entries.find(title_id);
if (iter == entries.end()) {
return ERR_NOT_REGISTERED;
}
return MakeResult<std::vector<u8>>(iter->second.control);
}
ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch,
std::vector<u8> control) {
if (title_id == 0) {
return ERR_INVALID_PROCESS_ID;
}
const auto iter = entries.find(title_id);
if (iter != entries.end()) {
return ERR_INVALID_ACCESS;
}
entries.insert_or_assign(title_id, MapEntry{launch, std::move(control)});
return RESULT_SUCCESS;
}
ResultCode ARPManager::Unregister(u64 title_id) {
if (title_id == 0) {
return ERR_INVALID_PROCESS_ID;
}
const auto iter = entries.find(title_id);
if (iter == entries.end()) {
return ERR_NOT_REGISTERED;
}
entries.erase(iter);
return RESULT_SUCCESS;
}
void ARPManager::ResetAll() {
entries.clear();
}
} // namespace Service::Glue

View File

@@ -0,0 +1,63 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <map>
#include <vector>
#include "common/common_types.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/hle/result.h"
namespace Service::Glue {
struct ApplicationLaunchProperty {
u64 title_id;
u32 version;
FileSys::StorageId base_game_storage_id;
FileSys::StorageId update_storage_id;
u8 program_index;
u8 reserved;
};
static_assert(sizeof(ApplicationLaunchProperty) == 0x10,
"ApplicationLaunchProperty has incorrect size.");
// A class to manage state related to the arp:w and arp:r services, specifically the registration
// and unregistration of launch and control properties.
class ARPManager {
public:
ARPManager();
~ARPManager();
// Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was
// previously registered, otherwise ERR_NOT_REGISTERED if it was never registered or
// ERR_INVALID_PROCESS_ID if the title ID is 0.
ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const;
// Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to
// the provided title ID if it was previously registered, otherwise ERR_NOT_REGISTERED if it was
// never registered or ERR_INVALID_PROCESS_ID if the title ID is 0.
ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const;
// Adds a new entry to the internal database with the provided parameters, returning
// ERR_INVALID_ACCESS if attempting to re-register a title ID without an intermediate Unregister
// step, and ERR_INVALID_PROCESS_ID if the title ID is 0.
ResultCode Register(u64 title_id, ApplicationLaunchProperty launch, std::vector<u8> control);
// Removes the registration for the provided title ID from the database, returning
// ERR_NOT_REGISTERED if it doesn't exist in the database and ERR_INVALID_PROCESS_ID if the
// title ID is 0.
ResultCode Unregister(u64 title_id);
// Removes all entries from the database, always succeeds. Should only be used when resetting
// system state.
void ResetAll();
private:
struct MapEntry;
std::map<u64, MapEntry> entries;
};
} // namespace Service::Glue

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

@@ -19,7 +19,6 @@
#include "core/hle/service/am/am.h"
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/arp/arp.h"
#include "core/hle/service/audio/audio.h"
#include "core/hle/service/bcat/module.h"
#include "core/hle/service/bpc/bpc.h"
@@ -33,6 +32,7 @@
#include "core/hle/service/fgm/fgm.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/friend/friend.h"
#include "core/hle/service/glue/glue.h"
#include "core/hle/service/grc/grc.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/lbl/lbl.h"
@@ -68,6 +68,7 @@
#include "core/hle/service/usb/usb.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/wlan/wlan.h"
#include "core/reporter.h"
namespace Service {
@@ -148,6 +149,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
}
buf.push_back('}');
Core::System::GetInstance().GetReporter().SaveUnimplementedFunctionReport(
ctx, ctx.GetCommand(), function_name, service_name);
UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));
}
@@ -201,10 +204,9 @@ 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);
Audio::InstallInterfaces(*sm);
BCAT::InstallInterfaces(*sm);
BPC::InstallInterfaces(*sm);
@@ -218,6 +220,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
FGM::InstallInterfaces(*sm);
FileSystem::InstallInterfaces(*sm, vfs);
Friend::InstallInterfaces(*sm);
Glue::InstallInterfaces(system);
GRC::InstallInterfaces(*sm);
HID::InstallInterfaces(*sm);
LBL::InstallInterfaces(*sm);

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;

188
src/core/tools/freezer.cpp Normal file
View File

@@ -0,0 +1,188 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/memory.h"
#include "core/tools/freezer.h"
namespace Tools {
namespace {
constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
u64 MemoryReadWidth(u32 width, VAddr addr) {
switch (width) {
case 1:
return Memory::Read8(addr);
case 2:
return Memory::Read16(addr);
case 4:
return Memory::Read32(addr);
case 8:
return Memory::Read64(addr);
default:
UNREACHABLE();
return 0;
}
}
void MemoryWriteWidth(u32 width, VAddr addr, u64 value) {
switch (width) {
case 1:
Memory::Write8(addr, static_cast<u8>(value));
break;
case 2:
Memory::Write16(addr, static_cast<u16>(value));
break;
case 4:
Memory::Write32(addr, static_cast<u32>(value));
break;
case 8:
Memory::Write64(addr, value);
break;
default:
UNREACHABLE();
}
}
} // Anonymous namespace
Freezer::Freezer(Core::Timing::CoreTiming& core_timing) : core_timing(core_timing) {
event = core_timing.RegisterEvent(
"MemoryFreezer::FrameCallback",
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event);
}
Freezer::~Freezer() {
core_timing.UnscheduleEvent(event, 0);
}
void Freezer::SetActive(bool active) {
if (!this->active.exchange(active)) {
FillEntryReads();
core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event);
LOG_DEBUG(Common_Memory, "Memory freezer activated!");
} else {
LOG_DEBUG(Common_Memory, "Memory freezer deactivated!");
}
}
bool Freezer::IsActive() const {
return active.load(std::memory_order_relaxed);
}
void Freezer::Clear() {
std::lock_guard lock{entries_mutex};
LOG_DEBUG(Common_Memory, "Clearing all frozen memory values.");
entries.clear();
}
u64 Freezer::Freeze(VAddr address, u32 width) {
std::lock_guard lock{entries_mutex};
const auto current_value = MemoryReadWidth(width, address);
entries.push_back({address, width, current_value});
LOG_DEBUG(Common_Memory,
"Freezing memory for address={:016X}, width={:02X}, current_value={:016X}", address,
width, current_value);
return current_value;
}
void Freezer::Unfreeze(VAddr address) {
std::lock_guard lock{entries_mutex};
LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address);
entries.erase(
std::remove_if(entries.begin(), entries.end(),
[&address](const Entry& entry) { return entry.address == address; }),
entries.end());
}
bool Freezer::IsFrozen(VAddr address) const {
std::lock_guard lock{entries_mutex};
return std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
return entry.address == address;
}) != entries.end();
}
void Freezer::SetFrozenValue(VAddr address, u64 value) {
std::lock_guard lock{entries_mutex};
const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
return entry.address == address;
});
if (iter == entries.end()) {
LOG_ERROR(Common_Memory,
"Tried to set freeze value for address={:016X} that is not frozen!", address);
return;
}
LOG_DEBUG(Common_Memory,
"Manually overridden freeze value for address={:016X}, width={:02X} to value={:016X}",
iter->address, iter->width, value);
iter->value = value;
}
std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const {
std::lock_guard lock{entries_mutex};
const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
return entry.address == address;
});
if (iter == entries.end()) {
return std::nullopt;
}
return *iter;
}
std::vector<Freezer::Entry> Freezer::GetEntries() const {
std::lock_guard lock{entries_mutex};
return entries;
}
void Freezer::FrameCallback(u64 userdata, s64 cycles_late) {
if (!IsActive()) {
LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events.");
return;
}
std::lock_guard lock{entries_mutex};
for (const auto& entry : entries) {
LOG_DEBUG(Common_Memory,
"Enforcing memory freeze at address={:016X}, value={:016X}, width={:02X}",
entry.address, entry.value, entry.width);
MemoryWriteWidth(entry.width, entry.address, entry.value);
}
core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - cycles_late, event);
}
void Freezer::FillEntryReads() {
std::lock_guard lock{entries_mutex};
LOG_DEBUG(Common_Memory, "Updating memory freeze entries to current values.");
for (auto& entry : entries) {
entry.value = MemoryReadWidth(entry.width, entry.address);
}
}
} // namespace Tools

82
src/core/tools/freezer.h Normal file
View File

@@ -0,0 +1,82 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <mutex>
#include <optional>
#include <vector>
#include "common/common_types.h"
namespace Core::Timing {
class CoreTiming;
struct EventType;
} // namespace Core::Timing
namespace Tools {
/**
* This class allows the user to prevent an application from writing new values to certain memory
* locations. This has a variety of uses when attempting to reverse a game.
*
* One example could be a cheat to prevent Mario from taking damage in SMO. One could freeze the
* memory address that the game uses to store Mario's health so when he takes damage (and the game
* tries to write the new health value to memory), the value won't change.
*/
class Freezer {
public:
struct Entry {
VAddr address;
u32 width;
u64 value;
};
explicit Freezer(Core::Timing::CoreTiming& core_timing);
~Freezer();
// Enables or disables the entire memory freezer.
void SetActive(bool active);
// Returns whether or not the freezer is active.
bool IsActive() const;
// Removes all entries from the freezer.
void Clear();
// Freezes a value to its current memory address. The value the memory is kept at will be the
// value that is read during this function. Width can be 1, 2, 4, or 8 (in bytes).
u64 Freeze(VAddr address, u32 width);
// Unfreezes the memory value at address. If the address isn't frozen, this is a no-op.
void Unfreeze(VAddr address);
// Returns whether or not the address is frozen.
bool IsFrozen(VAddr address) const;
// Sets the value that address should be frozen to. This doesn't change the width set by using
// Freeze(). If the value isn't frozen, this will not freeze it and is thus a no-op.
void SetFrozenValue(VAddr address, u64 value);
// Returns the entry corresponding to the address if the address is frozen, otherwise
// std::nullopt.
std::optional<Entry> GetEntry(VAddr address) const;
// Returns all the entries in the freezer, an empty vector means nothing is frozen.
std::vector<Entry> GetEntries() const;
private:
void FrameCallback(u64 userdata, s64 cycles_late);
void FillEntryReads();
std::atomic_bool active{false};
mutable std::mutex entries_mutex;
std::vector<Entry> entries;
Core::Timing::EventType* event;
Core::Timing::CoreTiming& core_timing;
};
} // namespace Tools

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

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

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