From 9a48f252ae7b4471286a7d8899a0c70dff354b1a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 14 Jul 2021 00:29:12 -0400 Subject: applets: Append qt_ prefix to Qt frontend applets --- src/yuzu/CMakeLists.txt | 25 +- src/yuzu/applets/controller.cpp | 695 ------ src/yuzu/applets/controller.h | 164 -- src/yuzu/applets/controller.ui | 2653 ---------------------- src/yuzu/applets/error.cpp | 63 - src/yuzu/applets/error.h | 33 - src/yuzu/applets/profile_select.cpp | 163 -- src/yuzu/applets/profile_select.h | 72 - src/yuzu/applets/qt_controller.cpp | 695 ++++++ src/yuzu/applets/qt_controller.h | 164 ++ src/yuzu/applets/qt_controller.ui | 2653 ++++++++++++++++++++++ src/yuzu/applets/qt_error.cpp | 63 + src/yuzu/applets/qt_error.h | 33 + src/yuzu/applets/qt_profile_select.cpp | 163 ++ src/yuzu/applets/qt_profile_select.h | 72 + src/yuzu/applets/qt_software_keyboard.cpp | 1620 +++++++++++++ src/yuzu/applets/qt_software_keyboard.h | 285 +++ src/yuzu/applets/qt_software_keyboard.ui | 3503 +++++++++++++++++++++++++++++ src/yuzu/applets/qt_web_browser.cpp | 417 ++++ src/yuzu/applets/qt_web_browser.h | 218 ++ src/yuzu/applets/qt_web_browser_scripts.h | 193 ++ src/yuzu/applets/software_keyboard.cpp | 1620 ------------- src/yuzu/applets/software_keyboard.h | 285 --- src/yuzu/applets/software_keyboard.ui | 3503 ----------------------------- src/yuzu/applets/web_browser.cpp | 417 ---- src/yuzu/applets/web_browser.h | 218 -- src/yuzu/applets/web_browser_scripts.h | 193 -- src/yuzu/main.cpp | 10 +- 28 files changed, 10097 insertions(+), 10096 deletions(-) delete mode 100644 src/yuzu/applets/controller.cpp delete mode 100644 src/yuzu/applets/controller.h delete mode 100644 src/yuzu/applets/controller.ui delete mode 100644 src/yuzu/applets/error.cpp delete mode 100644 src/yuzu/applets/error.h delete mode 100644 src/yuzu/applets/profile_select.cpp delete mode 100644 src/yuzu/applets/profile_select.h create mode 100644 src/yuzu/applets/qt_controller.cpp create mode 100644 src/yuzu/applets/qt_controller.h create mode 100644 src/yuzu/applets/qt_controller.ui create mode 100644 src/yuzu/applets/qt_error.cpp create mode 100644 src/yuzu/applets/qt_error.h create mode 100644 src/yuzu/applets/qt_profile_select.cpp create mode 100644 src/yuzu/applets/qt_profile_select.h create mode 100644 src/yuzu/applets/qt_software_keyboard.cpp create mode 100644 src/yuzu/applets/qt_software_keyboard.h create mode 100644 src/yuzu/applets/qt_software_keyboard.ui create mode 100644 src/yuzu/applets/qt_web_browser.cpp create mode 100644 src/yuzu/applets/qt_web_browser.h create mode 100644 src/yuzu/applets/qt_web_browser_scripts.h delete mode 100644 src/yuzu/applets/software_keyboard.cpp delete mode 100644 src/yuzu/applets/software_keyboard.h delete mode 100644 src/yuzu/applets/software_keyboard.ui delete mode 100644 src/yuzu/applets/web_browser.cpp delete mode 100644 src/yuzu/applets/web_browser.h delete mode 100644 src/yuzu/applets/web_browser_scripts.h diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index f870b33b1..cb4bdcc7e 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -15,18 +15,19 @@ add_executable(yuzu about_dialog.cpp about_dialog.h aboutdialog.ui - applets/controller.cpp - applets/controller.h - applets/controller.ui - applets/error.cpp - applets/error.h - applets/profile_select.cpp - applets/profile_select.h - applets/software_keyboard.cpp - applets/software_keyboard.h - applets/software_keyboard.ui - applets/web_browser.cpp - applets/web_browser.h + applets/qt_controller.cpp + applets/qt_controller.h + applets/qt_controller.ui + applets/qt_error.cpp + applets/qt_error.h + applets/qt_profile_select.cpp + applets/qt_profile_select.h + applets/qt_software_keyboard.cpp + applets/qt_software_keyboard.h + applets/qt_software_keyboard.ui + applets/qt_web_browser.cpp + applets/qt_web_browser.h + applets/qt_web_browser_scripts.h bootmanager.cpp bootmanager.h compatdb.ui diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp deleted file mode 100644 index 836d90fda..000000000 --- a/src/yuzu/applets/controller.cpp +++ /dev/null @@ -1,695 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include - -#include "common/assert.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/hle/lock.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" -#include "core/hle/service/sm/sm.h" -#include "ui_controller.h" -#include "yuzu/applets/controller.h" -#include "yuzu/configuration/configure_input.h" -#include "yuzu/configuration/configure_input_profile_dialog.h" -#include "yuzu/configuration/configure_motion_touch.h" -#include "yuzu/configuration/configure_vibration.h" -#include "yuzu/configuration/input_profiles.h" -#include "yuzu/main.h" - -namespace { - -constexpr std::size_t HANDHELD_INDEX = 8; - -constexpr std::array, 8> led_patterns{{ - {true, false, false, false}, - {true, true, false, false}, - {true, true, true, false}, - {true, true, true, true}, - {true, false, false, true}, - {true, false, true, false}, - {true, false, true, true}, - {false, true, true, false}, -}}; - -void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, - bool connected) { - Core::System& system{Core::System::GetInstance()}; - - if (!system.IsPoweredOn()) { - return; - } - - Service::SM::ServiceManager& sm = system.ServiceManager(); - - auto& npad = - sm.GetService("hid") - ->GetAppletResource() - ->GetController(Service::HID::HidController::NPad); - - npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); -} - -// Returns true if the given controller type is compatible with the given parameters. -bool IsControllerCompatible(Settings::ControllerType controller_type, - Core::Frontend::ControllerParameters parameters) { - switch (controller_type) { - case Settings::ControllerType::ProController: - return parameters.allow_pro_controller; - case Settings::ControllerType::DualJoyconDetached: - return parameters.allow_dual_joycons; - case Settings::ControllerType::LeftJoycon: - return parameters.allow_left_joycon; - case Settings::ControllerType::RightJoycon: - return parameters.allow_right_joycon; - case Settings::ControllerType::Handheld: - return parameters.enable_single_mode && parameters.allow_handheld; - case Settings::ControllerType::GameCube: - return parameters.allow_gamecube_controller; - default: - return false; - } -} - -} // namespace - -QtControllerSelectorDialog::QtControllerSelectorDialog( - QWidget* parent, Core::Frontend::ControllerParameters parameters_, - InputCommon::InputSubsystem* input_subsystem_) - : QDialog(parent), ui(std::make_unique()), - parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, - input_profiles(std::make_unique()) { - ui->setupUi(this); - - player_widgets = { - ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4, - ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8, - }; - - player_groupboxes = { - ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected, - ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected, - ui->groupPlayer7Connected, ui->groupPlayer8Connected, - }; - - connected_controller_icons = { - ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4, - ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8, - }; - - led_patterns_boxes = {{ - {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3, - ui->checkboxPlayer1LED4}, - {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3, - ui->checkboxPlayer2LED4}, - {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3, - ui->checkboxPlayer3LED4}, - {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3, - ui->checkboxPlayer4LED4}, - {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3, - ui->checkboxPlayer5LED4}, - {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3, - ui->checkboxPlayer6LED4}, - {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3, - ui->checkboxPlayer7LED4}, - {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3, - ui->checkboxPlayer8LED4}, - }}; - - explain_text_labels = { - ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain, - ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain, - ui->labelPlayer7Explain, ui->labelPlayer8Explain, - }; - - emulated_controllers = { - ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated, - ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated, - ui->comboPlayer7Emulated, ui->comboPlayer8Emulated, - }; - - player_labels = { - ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4, - ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8, - }; - - connected_controller_labels = { - ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3, - ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6, - ui->labelConnectedPlayer7, ui->labelConnectedPlayer8, - }; - - connected_controller_checkboxes = { - ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, - ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, - ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, - }; - - // Setup/load everything prior to setting up connections. - // This avoids unintentionally changing the states of elements while loading them in. - SetSupportedControllers(); - DisableUnsupportedPlayers(); - - for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) { - SetEmulatedControllers(player_index); - } - - LoadConfiguration(); - - for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { - SetExplainText(i); - UpdateControllerIcon(i); - UpdateLEDPattern(i); - UpdateBorderColor(i); - - connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { - if (checked) { - for (std::size_t index = 0; index <= i; ++index) { - connected_controller_checkboxes[index]->setChecked(checked); - } - } else { - for (std::size_t index = i; index < NUM_PLAYERS; ++index) { - connected_controller_checkboxes[index]->setChecked(checked); - } - } - }); - - connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), - [this, i](int) { - UpdateControllerIcon(i); - UpdateControllerState(i); - UpdateLEDPattern(i); - CheckIfParametersMet(); - }); - - connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { - player_groupboxes[i]->setChecked(state == Qt::Checked); - UpdateControllerIcon(i); - UpdateControllerState(i); - UpdateLEDPattern(i); - UpdateBorderColor(i); - CheckIfParametersMet(); - }); - - if (i == 0) { - connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), - [this, i](int index) { - UpdateDockedState(GetControllerTypeFromIndex(index, i) == - Settings::ControllerType::Handheld); - }); - } - } - - connect(ui->vibrationButton, &QPushButton::clicked, this, - &QtControllerSelectorDialog::CallConfigureVibrationDialog); - - connect(ui->motionButton, &QPushButton::clicked, this, - &QtControllerSelectorDialog::CallConfigureMotionTouchDialog); - - connect(ui->inputConfigButton, &QPushButton::clicked, this, - &QtControllerSelectorDialog::CallConfigureInputProfileDialog); - - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, - &QtControllerSelectorDialog::ApplyConfiguration); - - // Enhancement: Check if the parameters have already been met before disconnecting controllers. - // If all the parameters are met AND only allows a single player, - // stop the constructor here as we do not need to continue. - if (CheckIfParametersMet() && parameters.enable_single_mode) { - return; - } - - // If keep_controllers_connected is false, forcefully disconnect all controllers - if (!parameters.keep_controllers_connected) { - for (auto player : player_groupboxes) { - player->setChecked(false); - } - } - - resize(0, 0); -} - -QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; - -int QtControllerSelectorDialog::exec() { - if (parameters_met && parameters.enable_single_mode) { - return QDialog::Accepted; - } - return QDialog::exec(); -} - -void QtControllerSelectorDialog::ApplyConfiguration() { - const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); - Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); - OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue()); - - Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); - Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); -} - -void QtControllerSelectorDialog::LoadConfiguration() { - for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { - const auto connected = - Settings::values.players.GetValue()[index].connected || - (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected); - player_groupboxes[index]->setChecked(connected); - connected_controller_checkboxes[index]->setChecked(connected); - emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType( - Settings::values.players.GetValue()[index].controller_type, index)); - } - - UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected); - - ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); - ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); -} - -void QtControllerSelectorDialog::CallConfigureVibrationDialog() { - ConfigureVibration dialog(this); - - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - - if (dialog.exec() == QDialog::Accepted) { - dialog.ApplyConfiguration(); - } -} - -void QtControllerSelectorDialog::CallConfigureMotionTouchDialog() { - ConfigureMotionTouch dialog(this, input_subsystem); - - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - - if (dialog.exec() == QDialog::Accepted) { - dialog.ApplyConfiguration(); - } -} - -void QtControllerSelectorDialog::CallConfigureInputProfileDialog() { - ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get()); - - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - dialog.exec(); -} - -bool QtControllerSelectorDialog::CheckIfParametersMet() { - // Here, we check and validate the current configuration against all applicable parameters. - const auto num_connected_players = static_cast( - std::count_if(player_groupboxes.begin(), player_groupboxes.end(), - [this](const QGroupBox* player) { return player->isChecked(); })); - - const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; - const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; - - // First, check against the number of connected players. - if (num_connected_players < min_supported_players || - num_connected_players > max_supported_players) { - parameters_met = false; - ui->buttonBox->setEnabled(parameters_met); - return parameters_met; - } - - // Next, check against all connected controllers. - const auto all_controllers_compatible = [this] { - for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { - // Skip controllers that are not used, we only care about the currently connected ones. - if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) { - continue; - } - - const auto compatible = IsControllerCompatible( - GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index), - parameters); - - // If any controller is found to be incompatible, return false early. - if (!compatible) { - return false; - } - } - - // Reaching here means all currently connected controllers are compatible. - return true; - }(); - - parameters_met = all_controllers_compatible; - ui->buttonBox->setEnabled(parameters_met); - return parameters_met; -} - -void QtControllerSelectorDialog::SetSupportedControllers() { - const QString theme = [] { - if (QIcon::themeName().contains(QStringLiteral("dark"))) { - return QStringLiteral("_dark"); - } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { - return QStringLiteral("_midnight"); - } else { - return QString{}; - } - }(); - - if (parameters.enable_single_mode && parameters.allow_handheld) { - ui->controllerSupported1->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme)); - } else { - ui->controllerSupported1->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme)); - } - - if (parameters.allow_dual_joycons) { - ui->controllerSupported2->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme)); - } else { - ui->controllerSupported2->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme)); - } - - if (parameters.allow_left_joycon) { - ui->controllerSupported3->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme)); - } else { - ui->controllerSupported3->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme)); - } - - if (parameters.allow_right_joycon) { - ui->controllerSupported4->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme)); - } else { - ui->controllerSupported4->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); - } - - if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) { - ui->controllerSupported5->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); - } else { - ui->controllerSupported5->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ") - .arg(theme)); - } - - // enable_single_mode overrides min_players and max_players. - if (parameters.enable_single_mode) { - ui->numberSupportedLabel->setText(QStringLiteral("1")); - return; - } - - if (parameters.min_players == parameters.max_players) { - ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players)); - } else { - ui->numberSupportedLabel->setText( - QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players)); - } -} - -void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) { - auto& pairs = index_controller_type_pairs[player_index]; - - pairs.clear(); - emulated_controllers[player_index]->clear(); - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::ProController); - emulated_controllers[player_index]->addItem(tr("Pro Controller")); - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::DualJoyconDetached); - emulated_controllers[player_index]->addItem(tr("Dual Joycons")); - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::LeftJoycon); - emulated_controllers[player_index]->addItem(tr("Left Joycon")); - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::RightJoycon); - emulated_controllers[player_index]->addItem(tr("Right Joycon")); - - if (player_index == 0) { - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::Handheld); - emulated_controllers[player_index]->addItem(tr("Handheld")); - } - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::GameCube); - emulated_controllers[player_index]->addItem(tr("GameCube Controller")); -} - -Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( - int index, std::size_t player_index) const { - const auto& pairs = index_controller_type_pairs[player_index]; - - const auto it = std::find_if(pairs.begin(), pairs.end(), - [index](const auto& pair) { return pair.first == index; }); - - if (it == pairs.end()) { - return Settings::ControllerType::ProController; - } - - return it->second; -} - -int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type, - std::size_t player_index) const { - const auto& pairs = index_controller_type_pairs[player_index]; - - const auto it = std::find_if(pairs.begin(), pairs.end(), - [type](const auto& pair) { return pair.second == type; }); - - if (it == pairs.end()) { - return 0; - } - - return it->first; -} - -void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) { - if (!player_groupboxes[player_index]->isChecked()) { - connected_controller_icons[player_index]->setStyleSheet(QString{}); - player_labels[player_index]->show(); - return; - } - - const QString stylesheet = [this, player_index] { - switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), - player_index)) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::GameCube: - return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); - case Settings::ControllerType::DualJoyconDetached: - return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); - case Settings::ControllerType::LeftJoycon: - return QStringLiteral("image: url(:/controller/applet_joycon_left%0); "); - case Settings::ControllerType::RightJoycon: - return QStringLiteral("image: url(:/controller/applet_joycon_right%0); "); - case Settings::ControllerType::Handheld: - return QStringLiteral("image: url(:/controller/applet_handheld%0); "); - default: - return QString{}; - } - }(); - - if (stylesheet.isEmpty()) { - connected_controller_icons[player_index]->setStyleSheet(QString{}); - player_labels[player_index]->show(); - return; - } - - const QString theme = [] { - if (QIcon::themeName().contains(QStringLiteral("dark"))) { - return QStringLiteral("_dark"); - } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { - return QStringLiteral("_midnight"); - } else { - return QString{}; - } - }(); - - connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme)); - player_labels[player_index]->hide(); -} - -void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) { - auto& player = Settings::values.players.GetValue()[player_index]; - - const auto controller_type = GetControllerTypeFromIndex( - emulated_controllers[player_index]->currentIndex(), player_index); - const auto player_connected = player_groupboxes[player_index]->isChecked() && - controller_type != Settings::ControllerType::Handheld; - - if (player.controller_type == controller_type && player.connected == player_connected) { - // Set vibration devices in the event that the input device has changed. - ConfigureVibration::SetVibrationDevices(player_index); - return; - } - - // Disconnect the controller first. - UpdateController(controller_type, player_index, false); - - player.controller_type = controller_type; - player.connected = player_connected; - - ConfigureVibration::SetVibrationDevices(player_index); - - // Handheld - if (player_index == 0) { - auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; - if (controller_type == Settings::ControllerType::Handheld) { - handheld = player; - } - handheld.connected = player_groupboxes[player_index]->isChecked() && - controller_type == Settings::ControllerType::Handheld; - UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected); - } - - if (!player.connected) { - return; - } - - // This emulates a delay between disconnecting and reconnecting controllers as some games - // do not respond to a change in controller type if it was instantaneous. - using namespace std::chrono_literals; - std::this_thread::sleep_for(60ms); - - UpdateController(controller_type, player_index, player_connected); -} - -void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { - if (!player_groupboxes[player_index]->isChecked() || - GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), - player_index) == Settings::ControllerType::Handheld) { - led_patterns_boxes[player_index][0]->setChecked(false); - led_patterns_boxes[player_index][1]->setChecked(false); - led_patterns_boxes[player_index][2]->setChecked(false); - led_patterns_boxes[player_index][3]->setChecked(false); - return; - } - - led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]); - led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]); - led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]); - led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]); -} - -void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { - if (!parameters.enable_border_color || - player_index >= static_cast(parameters.max_players) || - player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) { - return; - } - - player_groupboxes[player_index]->setStyleSheet( - player_groupboxes[player_index]->styleSheet().append( - QStringLiteral("QGroupBox#groupPlayer%1Connected:checked " - "{ border: 1px solid rgba(%2, %3, %4, %5); }") - .arg(player_index + 1) - .arg(parameters.border_colors[player_index][0]) - .arg(parameters.border_colors[player_index][1]) - .arg(parameters.border_colors[player_index][2]) - .arg(parameters.border_colors[player_index][3]))); -} - -void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) { - if (!parameters.enable_explain_text || - player_index >= static_cast(parameters.max_players)) { - return; - } - - explain_text_labels[player_index]->setText(QString::fromStdString( - Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(), - parameters.explain_text[player_index].size()))); -} - -void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { - // Disallow changing the console mode if the controller type is handheld. - ui->radioDocked->setEnabled(!is_handheld); - ui->radioUndocked->setEnabled(!is_handheld); - - ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); - ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); - - // Also force into undocked mode if the controller type is handheld. - if (is_handheld) { - ui->radioUndocked->setChecked(true); - } -} - -void QtControllerSelectorDialog::DisableUnsupportedPlayers() { - const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; - - switch (max_supported_players) { - case 0: - default: - UNREACHABLE(); - return; - case 1: - ui->widgetSpacer->hide(); - ui->widgetSpacer2->hide(); - ui->widgetSpacer3->hide(); - ui->widgetSpacer4->hide(); - break; - case 2: - ui->widgetSpacer->hide(); - ui->widgetSpacer2->hide(); - ui->widgetSpacer3->hide(); - break; - case 3: - ui->widgetSpacer->hide(); - ui->widgetSpacer2->hide(); - break; - case 4: - ui->widgetSpacer->hide(); - break; - case 5: - case 6: - case 7: - case 8: - break; - } - - for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) { - // Disconnect any unsupported players here and disable or hide them if applicable. - Settings::values.players.GetValue()[index].connected = false; - UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false); - // Hide the player widgets when max_supported_controllers is less than or equal to 4. - if (max_supported_players <= 4) { - player_widgets[index]->hide(); - } - - // Disable and hide the following to prevent these from interaction. - player_widgets[index]->setDisabled(true); - connected_controller_checkboxes[index]->setDisabled(true); - connected_controller_labels[index]->hide(); - connected_controller_checkboxes[index]->hide(); - } -} - -QtControllerSelector::QtControllerSelector(GMainWindow& parent) { - connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, - &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); - connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this, - &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection); -} - -QtControllerSelector::~QtControllerSelector() = default; - -void QtControllerSelector::ReconfigureControllers( - std::function callback_, const Core::Frontend::ControllerParameters& parameters) const { - callback = std::move(callback_); - emit MainWindowReconfigureControllers(parameters); -} - -void QtControllerSelector::MainWindowReconfigureFinished() { - // Acquire the HLE mutex - std::lock_guard lock(HLE::g_hle_lock); - callback(); -} diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h deleted file mode 100644 index 9b57aea1a..000000000 --- a/src/yuzu/applets/controller.h +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include "core/frontend/applets/controller.h" - -class GMainWindow; -class QCheckBox; -class QComboBox; -class QDialogButtonBox; -class QGroupBox; -class QLabel; - -class InputProfiles; - -namespace InputCommon { -class InputSubsystem; -} - -namespace Settings { -enum class ControllerType; -} - -namespace Ui { -class QtControllerSelectorDialog; -} - -class QtControllerSelectorDialog final : public QDialog { - Q_OBJECT - -public: - explicit QtControllerSelectorDialog(QWidget* parent, - Core::Frontend::ControllerParameters parameters_, - InputCommon::InputSubsystem* input_subsystem_); - ~QtControllerSelectorDialog() override; - - int exec() override; - -private: - // Applies the current configuration. - void ApplyConfiguration(); - - // Loads the current input configuration into the frontend applet. - void LoadConfiguration(); - - // Initializes the "Configure Vibration" Dialog. - void CallConfigureVibrationDialog(); - - // Initializes the "Configure Motion / Touch" Dialog. - void CallConfigureMotionTouchDialog(); - - // Initializes the "Create Input Profile" Dialog. - void CallConfigureInputProfileDialog(); - - // Checks the current configuration against the given parameters. - // This sets and returns the value of parameters_met. - bool CheckIfParametersMet(); - - // Sets the controller icons for "Supported Controller Types". - void SetSupportedControllers(); - - // Sets the emulated controllers per player. - void SetEmulatedControllers(std::size_t player_index); - - // Gets the Controller Type for a given controller combobox index per player. - Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const; - - // Gets the controller combobox index for a given Controller Type per player. - int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const; - - // Updates the controller icons per player. - void UpdateControllerIcon(std::size_t player_index); - - // Updates the controller state (type and connection status) per player. - void UpdateControllerState(std::size_t player_index); - - // Updates the LED pattern per player. - void UpdateLEDPattern(std::size_t player_index); - - // Updates the border color per player. - void UpdateBorderColor(std::size_t player_index); - - // Sets the "Explain Text" per player. - void SetExplainText(std::size_t player_index); - - // Updates the console mode. - void UpdateDockedState(bool is_handheld); - - // Disables and disconnects unsupported players based on the given parameters. - void DisableUnsupportedPlayers(); - - std::unique_ptr ui; - - // Parameters sent in from the backend HLE applet. - Core::Frontend::ControllerParameters parameters; - - InputCommon::InputSubsystem* input_subsystem; - - std::unique_ptr input_profiles; - - // This is true if and only if all parameters are met. Otherwise, this is false. - // This determines whether the "OK" button can be clicked to exit the applet. - bool parameters_met{false}; - - static constexpr std::size_t NUM_PLAYERS = 8; - - // Widgets encapsulating the groupboxes and comboboxes per player. - std::array player_widgets; - - // Groupboxes encapsulating the controller icons and LED patterns per player. - std::array player_groupboxes; - - // Icons for currently connected controllers/players. - std::array connected_controller_icons; - - // Labels that represent the player numbers in place of the controller icons. - std::array player_labels; - - // LED patterns for currently connected controllers/players. - std::array, NUM_PLAYERS> led_patterns_boxes; - - // Labels representing additional information known as "Explain Text" per player. - std::array explain_text_labels; - - // Comboboxes with a list of emulated controllers per player. - std::array emulated_controllers; - - /// Pairs of emulated controller index and Controller Type enum per player. - std::array>, NUM_PLAYERS> - index_controller_type_pairs; - - // Labels representing the number of connected controllers - // above the "Connected Controllers" checkboxes. - std::array connected_controller_labels; - - // Checkboxes representing the "Connected Controllers". - std::array connected_controller_checkboxes; -}; - -class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { - Q_OBJECT - -public: - explicit QtControllerSelector(GMainWindow& parent); - ~QtControllerSelector() override; - - void ReconfigureControllers( - std::function callback_, - const Core::Frontend::ControllerParameters& parameters) const override; - -signals: - void MainWindowReconfigureControllers( - const Core::Frontend::ControllerParameters& parameters) const; - -private: - void MainWindowReconfigureFinished(); - - mutable std::function callback; -}; diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui deleted file mode 100644 index c8cb6bcf3..000000000 --- a/src/yuzu/applets/controller.ui +++ /dev/null @@ -1,2653 +0,0 @@ - - - QtControllerSelectorDialog - - - - 0 - 0 - 839 - 630 - - - - Controller Applet - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 10 - - - 0 - - - 10 - - - 0 - - - 10 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - 75 - true - - - - Supported Controller Types: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - 0 - - - 0 - - - 16 - - - 14 - - - 16 - - - - - - 75 - true - - - - Players: - - - Qt::AlignCenter - - - false - - - - - - - - 14 - - - - 1 - 8 - - - Qt::AlignCenter - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 5 - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P4 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P2 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P1 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::LeftToRight - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - Handheld - - - - - - - - - Use Current Config - - - - - - - - - - - - 25 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 25 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P3 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 0 - 25 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 25 - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P7 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P8 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P5 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::LeftToRight - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P6 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 0 - 25 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 25 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 25 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 25 - 20 - - - - - - - - - - - - 0 - 25 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 25 - - - - - - - - - - - - - - - - - 15 - - - 15 - - - 8 - - - 15 - - - 15 - - - - - - 16777215 - 16777215 - - - - Console Mode - - - - 6 - - - 8 - - - 6 - - - 3 - - - 6 - - - - - Docked - - - true - - - - - - - Undocked - - - - - - - - - - Vibration - - - true - - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - min-width: 68px; - - - Configure - - - - - - - - - - Motion - - - true - - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - min-width: 68px; - - - Configure - - - - - - - - - - Profiles - - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 68 - 16777215 - - - - min-width: 68px; - - - Create - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 3 - - - - - - - - - - - - Controllers - - - - - - - - - - - - - - 1 - - - Qt::AlignCenter - - - - - - - - - - - - - - Qt::LeftToRight - - - false - - - - - - - 2 - - - Qt::AlignCenter - - - - - - - 4 - - - Qt::AlignCenter - - - - - - - 3 - - - Qt::AlignCenter - - - - - - - Connected - - - - - - - - - - - - - - 5 - - - Qt::AlignCenter - - - - - - - - - - - - - - 7 - - - Qt::AlignCenter - - - - - - - - - - - - - - 6 - - - Qt::AlignCenter - - - - - - - 8 - - - Qt::AlignCenter - - - - - - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - true - - - QDialogButtonBox::Ok - - - - - - - - - - - - - - - buttonBox - accepted() - QtControllerSelectorDialog - accept() - - - diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp deleted file mode 100644 index 085688cd4..000000000 --- a/src/yuzu/applets/error.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include "core/hle/lock.h" -#include "yuzu/applets/error.h" -#include "yuzu/main.h" - -QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { - connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent, - &GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection); - connect(&parent, &GMainWindow::ErrorDisplayFinished, this, - &QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection); -} - -QtErrorDisplay::~QtErrorDisplay() = default; - -void QtErrorDisplay::ShowError(ResultCode error, std::function finished) const { - callback = std::move(finished); - emit MainWindowDisplayError( - tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0')), - tr("An error has occurred.\nPlease try again or contact the developer of the software.")); -} - -void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, - std::function finished) const { - callback = std::move(finished); - - const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); - emit MainWindowDisplayError( - tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0')), - tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the " - "software.") - .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) - .arg(date_time.toString(QStringLiteral("h:mm:ss A")))); -} - -void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, - std::string fullscreen_text, - std::function finished) const { - callback = std::move(finished); - emit MainWindowDisplayError( - tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0')), - tr("An error has occurred.\n\n%1\n\n%2") - .arg(QString::fromStdString(dialog_text)) - .arg(QString::fromStdString(fullscreen_text))); -} - -void QtErrorDisplay::MainWindowFinishedError() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - callback(); -} diff --git a/src/yuzu/applets/error.h b/src/yuzu/applets/error.h deleted file mode 100644 index 8bd895a32..000000000 --- a/src/yuzu/applets/error.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include - -#include "core/frontend/applets/error.h" - -class GMainWindow; - -class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet { - Q_OBJECT - -public: - explicit QtErrorDisplay(GMainWindow& parent); - ~QtErrorDisplay() override; - - void ShowError(ResultCode error, std::function finished) const override; - void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, - std::function finished) const override; - void ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text, - std::function finished) const override; - -signals: - void MainWindowDisplayError(QString error_code, QString error_text) const; - -private: - void MainWindowFinishedError(); - - mutable std::function callback; -}; diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp deleted file mode 100644 index 62fd1141c..000000000 --- a/src/yuzu/applets/profile_select.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include -#include -#include "common/fs/path_util.h" -#include "common/string_util.h" -#include "core/constants.h" -#include "core/hle/lock.h" -#include "yuzu/applets/profile_select.h" -#include "yuzu/main.h" - -namespace { -QString FormatUserEntryText(const QString& username, Common::UUID uuid) { - return QtProfileSelectionDialog::tr( - "%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. " - "00112233-4455-6677-8899-AABBCCDDEEFF))") - .arg(username, QString::fromStdString(uuid.FormatSwitch())); -} - -QString GetImagePath(Common::UUID uuid) { - const auto path = - Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / - fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); - return QString::fromStdString(Common::FS::PathToUTF8String(path)); -} - -QPixmap GetIcon(Common::UUID uuid) { - QPixmap icon{GetImagePath(uuid)}; - - if (!icon) { - icon.fill(Qt::black); - icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(), - static_cast(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); - } - - return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); -} -} // Anonymous namespace - -QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) - : QDialog(parent), profile_manager(std::make_unique()) { - outer_layout = new QVBoxLayout; - - instruction_label = new QLabel(tr("Select a user:")); - - scroll_area = new QScrollArea; - - buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject); - - outer_layout->addWidget(instruction_label); - outer_layout->addWidget(scroll_area); - outer_layout->addWidget(buttons); - - layout = new QVBoxLayout; - tree_view = new QTreeView; - item_model = new QStandardItemModel(tree_view); - tree_view->setModel(item_model); - - tree_view->setAlternatingRowColors(true); - tree_view->setSelectionMode(QHeaderView::SingleSelection); - tree_view->setSelectionBehavior(QHeaderView::SelectRows); - tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); - tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); - tree_view->setSortingEnabled(true); - tree_view->setEditTriggers(QHeaderView::NoEditTriggers); - tree_view->setUniformRowHeights(true); - tree_view->setIconSize({64, 64}); - tree_view->setContextMenuPolicy(Qt::NoContextMenu); - - item_model->insertColumns(0, 1); - item_model->setHeaderData(0, Qt::Horizontal, tr("Users")); - - // We must register all custom types with the Qt Automoc system so that we are able to use it - // with signals/slots. In this case, QList falls under the umbrella of custom types. - qRegisterMetaType>("QList"); - - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - layout->addWidget(tree_view); - - scroll_area->setLayout(layout); - - connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); - - const auto& profiles = profile_manager->GetAllUsers(); - for (const auto& user : profiles) { - Service::Account::ProfileBase profile{}; - if (!profile_manager->GetProfileBase(user, profile)) - continue; - - const auto username = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast(profile.username.data()), profile.username.size()); - - list_items.push_back(QList{new QStandardItem{ - GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); - } - - for (const auto& item : list_items) - item_model->appendRow(item); - - setLayout(outer_layout); - setWindowTitle(tr("Profile Selector")); - resize(550, 400); -} - -QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; - -int QtProfileSelectionDialog::exec() { - // Skip profile selection when there's only one. - if (profile_manager->GetUserCount() == 1) { - user_index = 0; - return QDialog::Accepted; - } - return QDialog::exec(); -} - -void QtProfileSelectionDialog::accept() { - QDialog::accept(); -} - -void QtProfileSelectionDialog::reject() { - user_index = 0; - QDialog::reject(); -} - -int QtProfileSelectionDialog::GetIndex() const { - return user_index; -} - -void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) { - user_index = index.row(); -} - -QtProfileSelector::QtProfileSelector(GMainWindow& parent) { - connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent, - &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection); - connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this, - &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection); -} - -QtProfileSelector::~QtProfileSelector() = default; - -void QtProfileSelector::SelectProfile( - std::function)> callback_) const { - callback = std::move(callback_); - emit MainWindowSelectProfile(); -} - -void QtProfileSelector::MainWindowFinishedSelection(std::optional uuid) { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - callback(uuid); -} diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h deleted file mode 100644 index 4e9037488..000000000 --- a/src/yuzu/applets/profile_select.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "core/frontend/applets/profile_select.h" -#include "core/hle/service/acc/profile_manager.h" - -class GMainWindow; -class QDialogButtonBox; -class QGraphicsScene; -class QLabel; -class QScrollArea; -class QStandardItem; -class QStandardItemModel; -class QVBoxLayout; - -class QtProfileSelectionDialog final : public QDialog { - Q_OBJECT - -public: - explicit QtProfileSelectionDialog(QWidget* parent); - ~QtProfileSelectionDialog() override; - - int exec() override; - void accept() override; - void reject() override; - - int GetIndex() const; - -private: - void SelectUser(const QModelIndex& index); - - int user_index = 0; - - QVBoxLayout* layout; - QTreeView* tree_view; - QStandardItemModel* item_model; - QGraphicsScene* scene; - - std::vector> list_items; - - QVBoxLayout* outer_layout; - QLabel* instruction_label; - QScrollArea* scroll_area; - QDialogButtonBox* buttons; - - std::unique_ptr profile_manager; -}; - -class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet { - Q_OBJECT - -public: - explicit QtProfileSelector(GMainWindow& parent); - ~QtProfileSelector() override; - - void SelectProfile(std::function)> callback_) const override; - -signals: - void MainWindowSelectProfile() const; - -private: - void MainWindowFinishedSelection(std::optional uuid); - - mutable std::function)> callback; -}; diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp new file mode 100644 index 000000000..97106d2cc --- /dev/null +++ b/src/yuzu/applets/qt_controller.cpp @@ -0,0 +1,695 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/lock.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "ui_qt_controller.h" +#include "yuzu/applets/qt_controller.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_profile_dialog.h" +#include "yuzu/configuration/configure_motion_touch.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/main.h" + +namespace { + +constexpr std::size_t HANDHELD_INDEX = 8; + +constexpr std::array, 8> led_patterns{{ + {true, false, false, false}, + {true, true, false, false}, + {true, true, true, false}, + {true, true, true, true}, + {true, false, false, true}, + {true, false, true, false}, + {true, false, true, true}, + {false, true, true, false}, +}}; + +void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, + bool connected) { + Core::System& system{Core::System::GetInstance()}; + + if (!system.IsPoweredOn()) { + return; + } + + Service::SM::ServiceManager& sm = system.ServiceManager(); + + auto& npad = + sm.GetService("hid") + ->GetAppletResource() + ->GetController(Service::HID::HidController::NPad); + + npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); +} + +// Returns true if the given controller type is compatible with the given parameters. +bool IsControllerCompatible(Settings::ControllerType controller_type, + Core::Frontend::ControllerParameters parameters) { + switch (controller_type) { + case Settings::ControllerType::ProController: + return parameters.allow_pro_controller; + case Settings::ControllerType::DualJoyconDetached: + return parameters.allow_dual_joycons; + case Settings::ControllerType::LeftJoycon: + return parameters.allow_left_joycon; + case Settings::ControllerType::RightJoycon: + return parameters.allow_right_joycon; + case Settings::ControllerType::Handheld: + return parameters.enable_single_mode && parameters.allow_handheld; + case Settings::ControllerType::GameCube: + return parameters.allow_gamecube_controller; + default: + return false; + } +} + +} // namespace + +QtControllerSelectorDialog::QtControllerSelectorDialog( + QWidget* parent, Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), ui(std::make_unique()), + parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, + input_profiles(std::make_unique()) { + ui->setupUi(this); + + player_widgets = { + ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4, + ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8, + }; + + player_groupboxes = { + ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected, + ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected, + ui->groupPlayer7Connected, ui->groupPlayer8Connected, + }; + + connected_controller_icons = { + ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4, + ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8, + }; + + led_patterns_boxes = {{ + {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3, + ui->checkboxPlayer1LED4}, + {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3, + ui->checkboxPlayer2LED4}, + {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3, + ui->checkboxPlayer3LED4}, + {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3, + ui->checkboxPlayer4LED4}, + {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3, + ui->checkboxPlayer5LED4}, + {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3, + ui->checkboxPlayer6LED4}, + {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3, + ui->checkboxPlayer7LED4}, + {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3, + ui->checkboxPlayer8LED4}, + }}; + + explain_text_labels = { + ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain, + ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain, + ui->labelPlayer7Explain, ui->labelPlayer8Explain, + }; + + emulated_controllers = { + ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated, + ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated, + ui->comboPlayer7Emulated, ui->comboPlayer8Emulated, + }; + + player_labels = { + ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4, + ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8, + }; + + connected_controller_labels = { + ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3, + ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6, + ui->labelConnectedPlayer7, ui->labelConnectedPlayer8, + }; + + connected_controller_checkboxes = { + ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, + ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, + ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, + }; + + // Setup/load everything prior to setting up connections. + // This avoids unintentionally changing the states of elements while loading them in. + SetSupportedControllers(); + DisableUnsupportedPlayers(); + + for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) { + SetEmulatedControllers(player_index); + } + + LoadConfiguration(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + SetExplainText(i); + UpdateControllerIcon(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + + connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { + if (checked) { + for (std::size_t index = 0; index <= i; ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } else { + for (std::size_t index = i; index < NUM_PLAYERS; ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } + }); + + connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), + [this, i](int) { + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + CheckIfParametersMet(); + }); + + connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { + player_groupboxes[i]->setChecked(state == Qt::Checked); + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + CheckIfParametersMet(); + }); + + if (i == 0) { + connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), + [this, i](int index) { + UpdateDockedState(GetControllerTypeFromIndex(index, i) == + Settings::ControllerType::Handheld); + }); + } + } + + connect(ui->vibrationButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureVibrationDialog); + + connect(ui->motionButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureMotionTouchDialog); + + connect(ui->inputConfigButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureInputProfileDialog); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &QtControllerSelectorDialog::ApplyConfiguration); + + // Enhancement: Check if the parameters have already been met before disconnecting controllers. + // If all the parameters are met AND only allows a single player, + // stop the constructor here as we do not need to continue. + if (CheckIfParametersMet() && parameters.enable_single_mode) { + return; + } + + // If keep_controllers_connected is false, forcefully disconnect all controllers + if (!parameters.keep_controllers_connected) { + for (auto player : player_groupboxes) { + player->setChecked(false); + } + } + + resize(0, 0); +} + +QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; + +int QtControllerSelectorDialog::exec() { + if (parameters_met && parameters.enable_single_mode) { + return QDialog::Accepted; + } + return QDialog::exec(); +} + +void QtControllerSelectorDialog::ApplyConfiguration() { + const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); + Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue()); + + Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); + Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); +} + +void QtControllerSelectorDialog::LoadConfiguration() { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + const auto connected = + Settings::values.players.GetValue()[index].connected || + (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected); + player_groupboxes[index]->setChecked(connected); + connected_controller_checkboxes[index]->setChecked(connected); + emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType( + Settings::values.players.GetValue()[index].controller_type, index)); + } + + UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected); + + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); + ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); +} + +void QtControllerSelectorDialog::CallConfigureVibrationDialog() { + ConfigureVibration dialog(this); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + + if (dialog.exec() == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + +void QtControllerSelectorDialog::CallConfigureMotionTouchDialog() { + ConfigureMotionTouch dialog(this, input_subsystem); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + + if (dialog.exec() == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + +void QtControllerSelectorDialog::CallConfigureInputProfileDialog() { + ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get()); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); +} + +bool QtControllerSelectorDialog::CheckIfParametersMet() { + // Here, we check and validate the current configuration against all applicable parameters. + const auto num_connected_players = static_cast( + std::count_if(player_groupboxes.begin(), player_groupboxes.end(), + [this](const QGroupBox* player) { return player->isChecked(); })); + + const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + // First, check against the number of connected players. + if (num_connected_players < min_supported_players || + num_connected_players > max_supported_players) { + parameters_met = false; + ui->buttonBox->setEnabled(parameters_met); + return parameters_met; + } + + // Next, check against all connected controllers. + const auto all_controllers_compatible = [this] { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + // Skip controllers that are not used, we only care about the currently connected ones. + if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) { + continue; + } + + const auto compatible = IsControllerCompatible( + GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index), + parameters); + + // If any controller is found to be incompatible, return false early. + if (!compatible) { + return false; + } + } + + // Reaching here means all currently connected controllers are compatible. + return true; + }(); + + parameters_met = all_controllers_compatible; + ui->buttonBox->setEnabled(parameters_met); + return parameters_met; +} + +void QtControllerSelectorDialog::SetSupportedControllers() { + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + if (parameters.enable_single_mode && parameters.allow_handheld) { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme)); + } else { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme)); + } + + if (parameters.allow_dual_joycons) { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme)); + } else { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme)); + } + + if (parameters.allow_left_joycon) { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme)); + } else { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme)); + } + + if (parameters.allow_right_joycon) { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme)); + } else { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); + } + + if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); + } else { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ") + .arg(theme)); + } + + // enable_single_mode overrides min_players and max_players. + if (parameters.enable_single_mode) { + ui->numberSupportedLabel->setText(QStringLiteral("1")); + return; + } + + if (parameters.min_players == parameters.max_players) { + ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players)); + } else { + ui->numberSupportedLabel->setText( + QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players)); + } +} + +void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) { + auto& pairs = index_controller_type_pairs[player_index]; + + pairs.clear(); + emulated_controllers[player_index]->clear(); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::ProController); + emulated_controllers[player_index]->addItem(tr("Pro Controller")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::DualJoyconDetached); + emulated_controllers[player_index]->addItem(tr("Dual Joycons")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::LeftJoycon); + emulated_controllers[player_index]->addItem(tr("Left Joycon")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::RightJoycon); + emulated_controllers[player_index]->addItem(tr("Right Joycon")); + + if (player_index == 0) { + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::Handheld); + emulated_controllers[player_index]->addItem(tr("Handheld")); + } + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::GameCube); + emulated_controllers[player_index]->addItem(tr("GameCube Controller")); +} + +Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( + int index, std::size_t player_index) const { + const auto& pairs = index_controller_type_pairs[player_index]; + + const auto it = std::find_if(pairs.begin(), pairs.end(), + [index](const auto& pair) { return pair.first == index; }); + + if (it == pairs.end()) { + return Settings::ControllerType::ProController; + } + + return it->second; +} + +int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type, + std::size_t player_index) const { + const auto& pairs = index_controller_type_pairs[player_index]; + + const auto it = std::find_if(pairs.begin(), pairs.end(), + [type](const auto& pair) { return pair.second == type; }); + + if (it == pairs.end()) { + return 0; + } + + return it->first; +} + +void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString stylesheet = [this, player_index] { + switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), + player_index)) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::GameCube: + return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); + case Settings::ControllerType::DualJoyconDetached: + return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); + case Settings::ControllerType::LeftJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_left%0); "); + case Settings::ControllerType::RightJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_right%0); "); + case Settings::ControllerType::Handheld: + return QStringLiteral("image: url(:/controller/applet_handheld%0); "); + default: + return QString{}; + } + }(); + + if (stylesheet.isEmpty()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme)); + player_labels[player_index]->hide(); +} + +void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) { + auto& player = Settings::values.players.GetValue()[player_index]; + + const auto controller_type = GetControllerTypeFromIndex( + emulated_controllers[player_index]->currentIndex(), player_index); + const auto player_connected = player_groupboxes[player_index]->isChecked() && + controller_type != Settings::ControllerType::Handheld; + + if (player.controller_type == controller_type && player.connected == player_connected) { + // Set vibration devices in the event that the input device has changed. + ConfigureVibration::SetVibrationDevices(player_index); + return; + } + + // Disconnect the controller first. + UpdateController(controller_type, player_index, false); + + player.controller_type = controller_type; + player.connected = player_connected; + + ConfigureVibration::SetVibrationDevices(player_index); + + // Handheld + if (player_index == 0) { + auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + if (controller_type == Settings::ControllerType::Handheld) { + handheld = player; + } + handheld.connected = player_groupboxes[player_index]->isChecked() && + controller_type == Settings::ControllerType::Handheld; + UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected); + } + + if (!player.connected) { + return; + } + + // This emulates a delay between disconnecting and reconnecting controllers as some games + // do not respond to a change in controller type if it was instantaneous. + using namespace std::chrono_literals; + std::this_thread::sleep_for(60ms); + + UpdateController(controller_type, player_index, player_connected); +} + +void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked() || + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), + player_index) == Settings::ControllerType::Handheld) { + led_patterns_boxes[player_index][0]->setChecked(false); + led_patterns_boxes[player_index][1]->setChecked(false); + led_patterns_boxes[player_index][2]->setChecked(false); + led_patterns_boxes[player_index][3]->setChecked(false); + return; + } + + led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]); + led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]); + led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]); + led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]); +} + +void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { + if (!parameters.enable_border_color || + player_index >= static_cast(parameters.max_players) || + player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) { + return; + } + + player_groupboxes[player_index]->setStyleSheet( + player_groupboxes[player_index]->styleSheet().append( + QStringLiteral("QGroupBox#groupPlayer%1Connected:checked " + "{ border: 1px solid rgba(%2, %3, %4, %5); }") + .arg(player_index + 1) + .arg(parameters.border_colors[player_index][0]) + .arg(parameters.border_colors[player_index][1]) + .arg(parameters.border_colors[player_index][2]) + .arg(parameters.border_colors[player_index][3]))); +} + +void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) { + if (!parameters.enable_explain_text || + player_index >= static_cast(parameters.max_players)) { + return; + } + + explain_text_labels[player_index]->setText(QString::fromStdString( + Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(), + parameters.explain_text[player_index].size()))); +} + +void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { + // Disallow changing the console mode if the controller type is handheld. + ui->radioDocked->setEnabled(!is_handheld); + ui->radioUndocked->setEnabled(!is_handheld); + + ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); + + // Also force into undocked mode if the controller type is handheld. + if (is_handheld) { + ui->radioUndocked->setChecked(true); + } +} + +void QtControllerSelectorDialog::DisableUnsupportedPlayers() { + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + switch (max_supported_players) { + case 0: + default: + UNREACHABLE(); + return; + case 1: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + ui->widgetSpacer4->hide(); + break; + case 2: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + break; + case 3: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + break; + case 4: + ui->widgetSpacer->hide(); + break; + case 5: + case 6: + case 7: + case 8: + break; + } + + for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) { + // Disconnect any unsupported players here and disable or hide them if applicable. + Settings::values.players.GetValue()[index].connected = false; + UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false); + // Hide the player widgets when max_supported_controllers is less than or equal to 4. + if (max_supported_players <= 4) { + player_widgets[index]->hide(); + } + + // Disable and hide the following to prevent these from interaction. + player_widgets[index]->setDisabled(true); + connected_controller_checkboxes[index]->setDisabled(true); + connected_controller_labels[index]->hide(); + connected_controller_checkboxes[index]->hide(); + } +} + +QtControllerSelector::QtControllerSelector(GMainWindow& parent) { + connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, + &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this, + &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection); +} + +QtControllerSelector::~QtControllerSelector() = default; + +void QtControllerSelector::ReconfigureControllers( + std::function callback_, const Core::Frontend::ControllerParameters& parameters) const { + callback = std::move(callback_); + emit MainWindowReconfigureControllers(parameters); +} + +void QtControllerSelector::MainWindowReconfigureFinished() { + // Acquire the HLE mutex + std::lock_guard lock(HLE::g_hle_lock); + callback(); +} diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h new file mode 100644 index 000000000..9b57aea1a --- /dev/null +++ b/src/yuzu/applets/qt_controller.h @@ -0,0 +1,164 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "core/frontend/applets/controller.h" + +class GMainWindow; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Settings { +enum class ControllerType; +} + +namespace Ui { +class QtControllerSelectorDialog; +} + +class QtControllerSelectorDialog final : public QDialog { + Q_OBJECT + +public: + explicit QtControllerSelectorDialog(QWidget* parent, + Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_); + ~QtControllerSelectorDialog() override; + + int exec() override; + +private: + // Applies the current configuration. + void ApplyConfiguration(); + + // Loads the current input configuration into the frontend applet. + void LoadConfiguration(); + + // Initializes the "Configure Vibration" Dialog. + void CallConfigureVibrationDialog(); + + // Initializes the "Configure Motion / Touch" Dialog. + void CallConfigureMotionTouchDialog(); + + // Initializes the "Create Input Profile" Dialog. + void CallConfigureInputProfileDialog(); + + // Checks the current configuration against the given parameters. + // This sets and returns the value of parameters_met. + bool CheckIfParametersMet(); + + // Sets the controller icons for "Supported Controller Types". + void SetSupportedControllers(); + + // Sets the emulated controllers per player. + void SetEmulatedControllers(std::size_t player_index); + + // Gets the Controller Type for a given controller combobox index per player. + Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const; + + // Gets the controller combobox index for a given Controller Type per player. + int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const; + + // Updates the controller icons per player. + void UpdateControllerIcon(std::size_t player_index); + + // Updates the controller state (type and connection status) per player. + void UpdateControllerState(std::size_t player_index); + + // Updates the LED pattern per player. + void UpdateLEDPattern(std::size_t player_index); + + // Updates the border color per player. + void UpdateBorderColor(std::size_t player_index); + + // Sets the "Explain Text" per player. + void SetExplainText(std::size_t player_index); + + // Updates the console mode. + void UpdateDockedState(bool is_handheld); + + // Disables and disconnects unsupported players based on the given parameters. + void DisableUnsupportedPlayers(); + + std::unique_ptr ui; + + // Parameters sent in from the backend HLE applet. + Core::Frontend::ControllerParameters parameters; + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr input_profiles; + + // This is true if and only if all parameters are met. Otherwise, this is false. + // This determines whether the "OK" button can be clicked to exit the applet. + bool parameters_met{false}; + + static constexpr std::size_t NUM_PLAYERS = 8; + + // Widgets encapsulating the groupboxes and comboboxes per player. + std::array player_widgets; + + // Groupboxes encapsulating the controller icons and LED patterns per player. + std::array player_groupboxes; + + // Icons for currently connected controllers/players. + std::array connected_controller_icons; + + // Labels that represent the player numbers in place of the controller icons. + std::array player_labels; + + // LED patterns for currently connected controllers/players. + std::array, NUM_PLAYERS> led_patterns_boxes; + + // Labels representing additional information known as "Explain Text" per player. + std::array explain_text_labels; + + // Comboboxes with a list of emulated controllers per player. + std::array emulated_controllers; + + /// Pairs of emulated controller index and Controller Type enum per player. + std::array>, NUM_PLAYERS> + index_controller_type_pairs; + + // Labels representing the number of connected controllers + // above the "Connected Controllers" checkboxes. + std::array connected_controller_labels; + + // Checkboxes representing the "Connected Controllers". + std::array connected_controller_checkboxes; +}; + +class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { + Q_OBJECT + +public: + explicit QtControllerSelector(GMainWindow& parent); + ~QtControllerSelector() override; + + void ReconfigureControllers( + std::function callback_, + const Core::Frontend::ControllerParameters& parameters) const override; + +signals: + void MainWindowReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters) const; + +private: + void MainWindowReconfigureFinished(); + + mutable std::function callback; +}; diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui new file mode 100644 index 000000000..c8cb6bcf3 --- /dev/null +++ b/src/yuzu/applets/qt_controller.ui @@ -0,0 +1,2653 @@ + + + QtControllerSelectorDialog + + + + 0 + 0 + 839 + 630 + + + + Controller Applet + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 10 + + + 0 + + + 10 + + + 0 + + + 10 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 75 + true + + + + Supported Controller Types: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + + + + 0 + 0 + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 0 + + + 0 + + + 16 + + + 14 + + + 16 + + + + + + 75 + true + + + + Players: + + + Qt::AlignCenter + + + false + + + + + + + + 14 + + + + 1 - 8 + + + Qt::AlignCenter + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5 + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P4 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P2 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P1 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + Handheld + + + + + + + + + Use Current Config + + + + + + + + + + + + 25 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 25 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P3 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P7 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P8 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P5 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P6 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 25 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 25 + 20 + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + + + + + + 15 + + + 15 + + + 8 + + + 15 + + + 15 + + + + + + 16777215 + 16777215 + + + + Console Mode + + + + 6 + + + 8 + + + 6 + + + 3 + + + 6 + + + + + Docked + + + true + + + + + + + Undocked + + + + + + + + + + Vibration + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Configure + + + + + + + + + + Motion + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Configure + + + + + + + + + + Profiles + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Create + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 3 + + + + + + + + + + + + Controllers + + + + + + + + + + + + + + 1 + + + Qt::AlignCenter + + + + + + + + + + + + + + Qt::LeftToRight + + + false + + + + + + + 2 + + + Qt::AlignCenter + + + + + + + 4 + + + Qt::AlignCenter + + + + + + + 3 + + + Qt::AlignCenter + + + + + + + Connected + + + + + + + + + + + + + + 5 + + + Qt::AlignCenter + + + + + + + + + + + + + + 7 + + + Qt::AlignCenter + + + + + + + + + + + + + + 6 + + + Qt::AlignCenter + + + + + + + 8 + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + true + + + QDialogButtonBox::Ok + + + + + + + + + + + + + + + buttonBox + accepted() + QtControllerSelectorDialog + accept() + + + diff --git a/src/yuzu/applets/qt_error.cpp b/src/yuzu/applets/qt_error.cpp new file mode 100644 index 000000000..45cf64603 --- /dev/null +++ b/src/yuzu/applets/qt_error.cpp @@ -0,0 +1,63 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "core/hle/lock.h" +#include "yuzu/applets/qt_error.h" +#include "yuzu/main.h" + +QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { + connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent, + &GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ErrorDisplayFinished, this, + &QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection); +} + +QtErrorDisplay::~QtErrorDisplay() = default; + +void QtErrorDisplay::ShowError(ResultCode error, std::function finished) const { + callback = std::move(finished); + emit MainWindowDisplayError( + tr("Error Code: %1-%2 (0x%3)") + .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error has occurred.\nPlease try again or contact the developer of the software.")); +} + +void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, + std::function finished) const { + callback = std::move(finished); + + const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); + emit MainWindowDisplayError( + tr("Error Code: %1-%2 (0x%3)") + .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the " + "software.") + .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) + .arg(date_time.toString(QStringLiteral("h:mm:ss A")))); +} + +void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, + std::string fullscreen_text, + std::function finished) const { + callback = std::move(finished); + emit MainWindowDisplayError( + tr("Error Code: %1-%2 (0x%3)") + .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error has occurred.\n\n%1\n\n%2") + .arg(QString::fromStdString(dialog_text)) + .arg(QString::fromStdString(fullscreen_text))); +} + +void QtErrorDisplay::MainWindowFinishedError() { + // Acquire the HLE mutex + std::lock_guard lock{HLE::g_hle_lock}; + callback(); +} diff --git a/src/yuzu/applets/qt_error.h b/src/yuzu/applets/qt_error.h new file mode 100644 index 000000000..8bd895a32 --- /dev/null +++ b/src/yuzu/applets/qt_error.h @@ -0,0 +1,33 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "core/frontend/applets/error.h" + +class GMainWindow; + +class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet { + Q_OBJECT + +public: + explicit QtErrorDisplay(GMainWindow& parent); + ~QtErrorDisplay() override; + + void ShowError(ResultCode error, std::function finished) const override; + void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, + std::function finished) const override; + void ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text, + std::function finished) const override; + +signals: + void MainWindowDisplayError(QString error_code, QString error_text) const; + +private: + void MainWindowFinishedError(); + + mutable std::function callback; +}; diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp new file mode 100644 index 000000000..a56638e21 --- /dev/null +++ b/src/yuzu/applets/qt_profile_select.cpp @@ -0,0 +1,163 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/fs/path_util.h" +#include "common/string_util.h" +#include "core/constants.h" +#include "core/hle/lock.h" +#include "yuzu/applets/qt_profile_select.h" +#include "yuzu/main.h" + +namespace { +QString FormatUserEntryText(const QString& username, Common::UUID uuid) { + return QtProfileSelectionDialog::tr( + "%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. " + "00112233-4455-6677-8899-AABBCCDDEEFF))") + .arg(username, QString::fromStdString(uuid.FormatSwitch())); +} + +QString GetImagePath(Common::UUID uuid) { + const auto path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / + fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); + return QString::fromStdString(Common::FS::PathToUTF8String(path)); +} + +QPixmap GetIcon(Common::UUID uuid) { + QPixmap icon{GetImagePath(uuid)}; + + if (!icon) { + icon.fill(Qt::black); + icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(), + static_cast(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); + } + + return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} +} // Anonymous namespace + +QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) + : QDialog(parent), profile_manager(std::make_unique()) { + outer_layout = new QVBoxLayout; + + instruction_label = new QLabel(tr("Select a user:")); + + scroll_area = new QScrollArea; + + buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject); + + outer_layout->addWidget(instruction_label); + outer_layout->addWidget(scroll_area); + outer_layout->addWidget(buttons); + + layout = new QVBoxLayout; + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setIconSize({64, 64}); + tree_view->setContextMenuPolicy(Qt::NoContextMenu); + + item_model->insertColumns(0, 1); + item_model->setHeaderData(0, Qt::Horizontal, tr("Users")); + + // We must register all custom types with the Qt Automoc system so that we are able to use it + // with signals/slots. In this case, QList falls under the umbrella of custom types. + qRegisterMetaType>("QList"); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tree_view); + + scroll_area->setLayout(layout); + + connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); + + const auto& profiles = profile_manager->GetAllUsers(); + for (const auto& user : profiles) { + Service::Account::ProfileBase profile{}; + if (!profile_manager->GetProfileBase(user, profile)) + continue; + + const auto username = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast(profile.username.data()), profile.username.size()); + + list_items.push_back(QList{new QStandardItem{ + GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); + } + + for (const auto& item : list_items) + item_model->appendRow(item); + + setLayout(outer_layout); + setWindowTitle(tr("Profile Selector")); + resize(550, 400); +} + +QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; + +int QtProfileSelectionDialog::exec() { + // Skip profile selection when there's only one. + if (profile_manager->GetUserCount() == 1) { + user_index = 0; + return QDialog::Accepted; + } + return QDialog::exec(); +} + +void QtProfileSelectionDialog::accept() { + QDialog::accept(); +} + +void QtProfileSelectionDialog::reject() { + user_index = 0; + QDialog::reject(); +} + +int QtProfileSelectionDialog::GetIndex() const { + return user_index; +} + +void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) { + user_index = index.row(); +} + +QtProfileSelector::QtProfileSelector(GMainWindow& parent) { + connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent, + &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this, + &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection); +} + +QtProfileSelector::~QtProfileSelector() = default; + +void QtProfileSelector::SelectProfile( + std::function)> callback_) const { + callback = std::move(callback_); + emit MainWindowSelectProfile(); +} + +void QtProfileSelector::MainWindowFinishedSelection(std::optional uuid) { + // Acquire the HLE mutex + std::lock_guard lock{HLE::g_hle_lock}; + callback(uuid); +} diff --git a/src/yuzu/applets/qt_profile_select.h b/src/yuzu/applets/qt_profile_select.h new file mode 100644 index 000000000..4e9037488 --- /dev/null +++ b/src/yuzu/applets/qt_profile_select.h @@ -0,0 +1,72 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "core/frontend/applets/profile_select.h" +#include "core/hle/service/acc/profile_manager.h" + +class GMainWindow; +class QDialogButtonBox; +class QGraphicsScene; +class QLabel; +class QScrollArea; +class QStandardItem; +class QStandardItemModel; +class QVBoxLayout; + +class QtProfileSelectionDialog final : public QDialog { + Q_OBJECT + +public: + explicit QtProfileSelectionDialog(QWidget* parent); + ~QtProfileSelectionDialog() override; + + int exec() override; + void accept() override; + void reject() override; + + int GetIndex() const; + +private: + void SelectUser(const QModelIndex& index); + + int user_index = 0; + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector> list_items; + + QVBoxLayout* outer_layout; + QLabel* instruction_label; + QScrollArea* scroll_area; + QDialogButtonBox* buttons; + + std::unique_ptr profile_manager; +}; + +class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet { + Q_OBJECT + +public: + explicit QtProfileSelector(GMainWindow& parent); + ~QtProfileSelector() override; + + void SelectProfile(std::function)> callback_) const override; + +signals: + void MainWindowSelectProfile() const; + +private: + void MainWindowFinishedSelection(std::optional uuid); + + mutable std::function)> callback; +}; diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp new file mode 100644 index 000000000..848801cec --- /dev/null +++ b/src/yuzu/applets/qt_software_keyboard.cpp @@ -0,0 +1,1620 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "ui_qt_software_keyboard.h" +#include "yuzu/applets/qt_software_keyboard.h" +#include "yuzu/main.h" +#include "yuzu/util/overlay_dialog.h" + +namespace { + +using namespace Service::AM::Applets; + +constexpr float BASE_HEADER_FONT_SIZE = 23.0f; +constexpr float BASE_SUB_FONT_SIZE = 17.0f; +constexpr float BASE_EDITOR_FONT_SIZE = 26.0f; +constexpr float BASE_CHAR_BUTTON_FONT_SIZE = 28.0f; +constexpr float BASE_LABEL_BUTTON_FONT_SIZE = 18.0f; +constexpr float BASE_ICON_BUTTON_SIZE = 36.0f; +[[maybe_unused]] constexpr float BASE_WIDTH = 1280.0f; +constexpr float BASE_HEIGHT = 720.0f; + +} // Anonymous namespace + +QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( + QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_) + : QDialog(parent), ui{std::make_unique()}, system{system_}, + is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} { + ui->setupUi(this); + + setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_TranslucentBackground); + + keyboard_buttons = {{ + {{ + { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + }, + { + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + }, + { + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_return, + }, + { + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + }, + { + ui->button_shift, + ui->button_shift, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_ok, + }, + }}, + {{ + { + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + }, + { + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + }, + { + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_return_shift, + }, + { + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + }, + { + ui->button_shift_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_ok_shift, + }, + }}, + }}; + + numberpad_buttons = {{ + { + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + }, + { + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + }, + { + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_ok_num, + }, + { + nullptr, + ui->button_0_num, + nullptr, + ui->button_ok_num, + }, + }}; + + all_buttons = { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + ui->button_shift, + ui->button_space, + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_0_num, + }; + + SetupMouseHover(); + + if (!initialize_parameters.ok_text.empty()) { + ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text)); + } + + ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text)); + ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text)); + + current_text = initialize_parameters.initial_text; + cursor_position = initialize_parameters.initial_cursor_position; + + SetTextDrawType(); + + for (auto* button : all_buttons) { + connect(button, &QPushButton::clicked, this, [this, button](bool) { + if (is_inline) { + InlineKeyboardButtonClicked(button); + } else { + NormalKeyboardButtonClicked(button); + } + }); + } + + // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend + if (system.IsPoweredOn()) { + input_interpreter = std::make_unique(system); + } +} + +QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() { + StopInputThread(); +} + +void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) { + if (isVisible()) { + return; + } + + MoveAndResizeWindow(pos, size); + + SetKeyboardType(); + SetPasswordMode(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + switch (text_check_result) { + case SwkbdTextCheckResult::Success: + case SwkbdTextCheckResult::Silent: + default: + break; + case SwkbdTextCheckResult::Failure: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + QString{}, tr("OK"), Qt::AlignCenter); + dialog.exec(); + + StartInputThread(); + break; + } + case SwkbdTextCheckResult::Confirm: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + tr("Cancel"), tr("OK"), Qt::AlignCenter); + if (dialog.exec() != QDialog::Accepted) { + StartInputThread(); + break; + } + + auto text = ui->topOSK->currentIndex() == 1 + ? ui->text_edit_osk->toPlainText().toStdU16String() + : ui->line_edit_osk->text().toStdU16String(); + + emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); + break; + } + } +} + +void QtSoftwareKeyboardDialog::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) { + MoveAndResizeWindow(pos, size); + + ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);")); + + ui->headerOSK->hide(); + ui->subOSK->hide(); + ui->inputOSK->hide(); + ui->charactersOSK->hide(); + ui->inputBoxOSK->hide(); + ui->charactersBoxOSK->hide(); + + initialize_parameters.max_text_length = appear_parameters.max_text_length; + initialize_parameters.min_text_length = appear_parameters.min_text_length; + initialize_parameters.type = appear_parameters.type; + initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags; + initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button; + initialize_parameters.enable_return_button = appear_parameters.enable_return_button; + initialize_parameters.disable_cancel_button = initialize_parameters.disable_cancel_button; + + SetKeyboardType(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::HideInlineKeyboard() { + StopInputThread(); + QDialog::hide(); +} + +void QtSoftwareKeyboardDialog::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) { + current_text = text_parameters.input_text; + cursor_position = text_parameters.cursor_position; + + SetBackspaceOkEnabled(); +} + +void QtSoftwareKeyboardDialog::ExitKeyboard() { + StopInputThread(); + QDialog::done(QDialog::Accepted); +} + +void QtSoftwareKeyboardDialog::open() { + QDialog::open(); + + row = 0; + column = 0; + + const auto* const curr_button = + keyboard_buttons[static_cast(bottom_osk_index)][row][column]; + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + + StartInputThread(); +} + +void QtSoftwareKeyboardDialog::reject() { + // Pressing the ESC key in a dialog calls QDialog::reject(). + // We will override this behavior to the "Cancel" action on the software keyboard. + TranslateButtonPress(HIDButton::X); +} + +void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { + if (!is_inline) { + QDialog::keyPressEvent(event); + return; + } + + const auto entered_key = event->key(); + + switch (entered_key) { + case Qt::Key_Escape: + QDialog::keyPressEvent(event); + return; + case Qt::Key_Backspace: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Return: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Left: + MoveTextCursorDirection(Direction::Left); + return; + case Qt::Key_Right: + MoveTextCursorDirection(Direction::Right); + return; + default: + break; + } + + const auto entered_text = event->text(); + + if (entered_text.isEmpty()) { + return; + } + + InlineTextInsertString(entered_text.toStdU16String()); +} + +void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { + QDialog::move(pos); + QDialog::resize(size); + + // High DPI + const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; + + RescaleKeyboardElements(size.width(), size.height(), dpi_scale); +} + +void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) { + const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto char_button_font_size = + BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto label_button_font_size = + BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + + QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal); + QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal); + QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal); + QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal); + QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size, + QFont::Normal); + + ui->label_header->setFont(header_font); + ui->label_sub->setFont(sub_font); + ui->line_edit_osk->setFont(editor_font); + ui->text_edit_osk->setFont(editor_font); + ui->label_characters->setFont(sub_font); + ui->label_characters_box->setFont(sub_font); + + ui->label_shift->setFont(label_button_font); + ui->label_shift_shift->setFont(label_button_font); + ui->label_cancel->setFont(label_button_font); + ui->label_cancel_shift->setFont(label_button_font); + ui->label_cancel_num->setFont(label_button_font); + ui->label_enter->setFont(label_button_font); + ui->label_enter_shift->setFont(label_button_font); + ui->label_enter_num->setFont(label_button_font); + + for (auto* button : all_buttons) { + if (button == ui->button_return || button == ui->button_return_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || + button == ui->button_ok_num) { + button->setFont(label_button_font); + continue; + } + + button->setFont(char_button_font); + } +} + +void QtSoftwareKeyboardDialog::SetKeyboardType() { + switch (initialize_parameters.type) { + case SwkbdType::Normal: + case SwkbdType::Qwerty: + case SwkbdType::Unknown3: + case SwkbdType::Latin: + case SwkbdType::SimplifiedChinese: + case SwkbdType::TraditionalChinese: + case SwkbdType::Korean: + default: { + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 320); + ui->verticalLayout_2->setStretch(1, 400); + + ui->gridLineOSK->setRowStretch(5, 94); + ui->gridBoxOSK->setRowStretch(2, 81); + break; + } + case SwkbdType::NumberPad: { + bottom_osk_index = BottomOSKIndex::NumberPad; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 370); + ui->verticalLayout_2->setStretch(1, 350); + + ui->gridLineOSK->setRowStretch(5, 144); + ui->gridBoxOSK->setRowStretch(2, 131); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetPasswordMode() { + switch (initialize_parameters.password_mode) { + case SwkbdPasswordMode::Disabled: + default: + ui->line_edit_osk->setEchoMode(QLineEdit::Normal); + break; + case SwkbdPasswordMode::Enabled: + ui->line_edit_osk->setEchoMode(QLineEdit::Password); + break; + } +} + +void QtSoftwareKeyboardDialog::SetTextDrawType() { + switch (initialize_parameters.text_draw_type) { + case SwkbdTextDrawType::Line: + case SwkbdTextDrawType::DownloadCode: { + ui->topOSK->setCurrentIndex(0); + + if (initialize_parameters.max_text_length <= 10) { + ui->gridLineOSK->setColumnStretch(0, 390); + ui->gridLineOSK->setColumnStretch(1, 500); + ui->gridLineOSK->setColumnStretch(2, 390); + } else { + ui->gridLineOSK->setColumnStretch(0, 130); + ui->gridLineOSK->setColumnStretch(1, 1020); + ui->gridLineOSK->setColumnStretch(2, 130); + } + + if (is_inline) { + return; + } + + connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) { + const auto is_valid = ValidateInputText(changed_string); + + const auto text_length = static_cast(changed_string.length()); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->line_edit_osk->setFocus(); + }); + + connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged, + [this](int old_cursor_position, int new_cursor_position) { + ui->button_backspace->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_shift->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_num->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + + ui->line_edit_osk->setFocus(); + }); + + connect( + ui->line_edit_osk, &QLineEdit::returnPressed, this, + [this] { TranslateButtonPress(HIDButton::Plus); }, Qt::QueuedConnection); + + ui->line_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length); + ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + case SwkbdTextDrawType::Box: + default: { + ui->topOSK->setCurrentIndex(1); + + if (is_inline) { + return; + } + + connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] { + if (static_cast(ui->text_edit_osk->toPlainText().length()) > + initialize_parameters.max_text_length) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } + + const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText()); + + const auto text_length = static_cast(ui->text_edit_osk->toPlainText().length()); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->text_edit_osk->setFocus(); + }); + + connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] { + const auto new_cursor_position = ui->text_edit_osk->textCursor().position(); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + + ui->text_edit_osk->setFocus(); + }); + + ui->text_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0 + ? QTextCursor::Start + : QTextCursor::End); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetControllerImage() { + const auto controller_type = Settings::values.players.GetValue()[8].connected + ? Settings::values.players.GetValue()[8].controller_type + : Settings::values.players.GetValue()[0].controller_type; + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark")) || + QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_dark"); + } else { + return QString{}; + } + }(); + + switch (controller_type) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::GameCube: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + break; + case Settings::ControllerType::DualJoyconDetached: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + break; + case Settings::ControllerType::LeftJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::RightJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::Handheld: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::DisableKeyboardButtons() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + default: { + for (const auto& keys : keyboard_buttons) { + for (const auto& rows : keys) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + } + + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + ui->button_space->setDisabled(key_disable_flags.space); + ui->button_space_shift->setDisabled(key_disable_flags.space); + + ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username); + + ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username); + + ui->button_slash->setDisabled(key_disable_flags.slash); + + ui->button_1->setDisabled(key_disable_flags.numbers); + ui->button_2->setDisabled(key_disable_flags.numbers); + ui->button_3->setDisabled(key_disable_flags.numbers); + ui->button_4->setDisabled(key_disable_flags.numbers); + ui->button_5->setDisabled(key_disable_flags.numbers); + ui->button_6->setDisabled(key_disable_flags.numbers); + ui->button_7->setDisabled(key_disable_flags.numbers); + ui->button_8->setDisabled(key_disable_flags.numbers); + ui->button_9->setDisabled(key_disable_flags.numbers); + ui->button_0->setDisabled(key_disable_flags.numbers); + + ui->button_return->setEnabled(initialize_parameters.enable_return_button); + ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button); + break; + } + case BottomOSKIndex::NumberPad: { + for (const auto& rows : numberpad_buttons) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() { + if (is_inline) { + ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(current_text.size() >= + initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + } else { + const auto text_length = [this] { + if (ui->topOSK->currentIndex() == 1) { + return static_cast(ui->text_edit_osk->toPlainText().length()); + } else { + return static_cast(ui->line_edit_osk->text().length()); + } + }(); + + const auto normal_cursor_position = [this] { + if (ui->topOSK->currentIndex() == 1) { + return ui->text_edit_osk->textCursor().position(); + } else { + return ui->line_edit_osk->cursorPosition(); + } + }(); + + ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + } +} + +bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) { + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + const auto input_text_length = static_cast(input_text.length()); + + if (input_text_length < initialize_parameters.min_text_length || + input_text_length > initialize_parameters.max_text_length) { + return false; + } + + if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) { + return false; + } + + if ((key_disable_flags.at || key_disable_flags.username) && + input_text.contains(QLatin1Char{'@'})) { + return false; + } + + if ((key_disable_flags.percent || key_disable_flags.username) && + input_text.contains(QLatin1Char{'%'})) { + return false; + } + + if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) { + return false; + } + + if ((key_disable_flags.backslash || key_disable_flags.username) && + input_text.contains(QLatin1Char('\\'))) { + return false; + } + + if (key_disable_flags.numbers && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) { + return false; + } + + if (bottom_osk_index == BottomOSKIndex::NumberPad && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) { + return false; + } + + return true; +} + +void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + bottom_osk_index = BottomOSKIndex::UpperCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nimage-position: left;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + break; + case BottomOSKIndex::UpperCase: + if (caps_lock_enabled) { + caps_lock_enabled = false; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nimage-position: left;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock")); + + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + } else { + caps_lock_enabled = true; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/osk_button_shift_lock_on.png);" + "\nimage-position: left;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off")); + } + break; + case BottomOSKIndex::NumberPad: + default: + break; + } +} + +void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) { + if (button == ui->button_ampersand) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral("&")); + } else { + ui->line_edit_osk->insert(QStringLiteral("&")); + } + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral("\n")); + } else { + ui->line_edit_osk->insert(QStringLiteral("\n")); + } + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral(" ")); + } else { + ui->line_edit_osk->insert(QStringLiteral(" ")); + } + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (ui->topOSK->currentIndex() == 1) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } else { + ui->line_edit_osk->backspace(); + } + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + auto text = ui->topOSK->currentIndex() == 1 + ? ui->text_edit_osk->toPlainText().toStdU16String() + : ui->line_edit_osk->text().toStdU16String(); + + emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); + return; + } + + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(button->text()); + } else { + ui->line_edit_osk->insert(button->text()); + } + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) { + if (!button->isEnabled()) { + return; + } + + if (button == ui->button_ampersand) { + InlineTextInsertString(u"&"); + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + InlineTextInsertString(u"\n"); + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + InlineTextInsertString(u" "); + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (cursor_position <= 0 || current_text.empty()) { + cursor_position = 0; + return; + } + + --cursor_position; + + current_text.erase(cursor_position, 1); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position); + return; + } + + InlineTextInsertString(button->text().toStdU16String()); + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) { + if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) { + return; + } + + current_text.insert(cursor_position, string); + + cursor_position += static_cast(string.size()); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); +} + +void QtSoftwareKeyboardDialog::SetupMouseHover() { + // setFocus() has a bug where continuously changing focus will cause the focus UI to + // mysteriously disappear. A workaround we have found is using the mouse to hover over + // the buttons to act in place of the button focus. As a result, we will have to set + // a blank cursor when hovering over all the buttons and set a no focus policy so the + // buttons do not stay in focus in addition to the mouse hover. + for (auto* button : all_buttons) { + button->setCursor(QCursor(Qt::BlankCursor)); + button->setFocusPolicy(Qt::NoFocus); + } +} + +template +void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +template +void QtSoftwareKeyboardDialog::HandleButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { + switch (button) { + case HIDButton::A: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + keyboard_buttons[static_cast(bottom_osk_index)][row][column]->click(); + break; + case BottomOSKIndex::NumberPad: + numberpad_buttons[row][column]->click(); + break; + default: + break; + } + break; + case HIDButton::B: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + break; + case HIDButton::X: + if (is_inline) { + emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); + } else { + auto text = ui->topOSK->currentIndex() == 1 + ? ui->text_edit_osk->toPlainText().toStdU16String() + : ui->line_edit_osk->text().toStdU16String(); + + emit SubmitNormalText(SwkbdResult::Cancel, std::move(text)); + } + break; + case HIDButton::Y: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_space->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_space_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::LStick: + case HIDButton::RStick: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_shift->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_shift_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::L: + MoveTextCursorDirection(Direction::Left); + break; + case HIDButton::R: + MoveTextCursorDirection(Direction::Right); + break; + case HIDButton::Plus: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + break; + case HIDButton::DLeft: + case HIDButton::LStickLeft: + case HIDButton::RStickLeft: + MoveButtonDirection(Direction::Left); + break; + case HIDButton::DUp: + case HIDButton::LStickUp: + case HIDButton::RStickUp: + MoveButtonDirection(Direction::Up); + break; + case HIDButton::DRight: + case HIDButton::LStickRight: + case HIDButton::RStickRight: + MoveButtonDirection(Direction::Right); + break; + case HIDButton::DDown: + case HIDButton::LStickDown: + case HIDButton::RStickDown: + MoveButtonDirection(Direction::Down); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { + // Changes the row or column index depending on the direction. + auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) { + switch (direction) { + case Direction::Left: + column = (column + max_columns - 1) % max_columns; + break; + case Direction::Up: + row = (row + max_rows - 1) % max_rows; + break; + case Direction::Right: + column = (column + 1) % max_columns; + break; + case Direction::Down: + row = (row + 1) % max_rows; + break; + default: + break; + } + }; + + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: { + const auto index = static_cast(bottom_osk_index); + + const auto* const prev_button = keyboard_buttons[index][row][column]; + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + auto* curr_button = keyboard_buttons[index][row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + curr_button = keyboard_buttons[index][row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + case BottomOSKIndex::NumberPad: { + const auto* const prev_button = numberpad_buttons[row][column]; + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + auto* curr_button = numberpad_buttons[row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + curr_button = numberpad_buttons[row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) { + switch (direction) { + case Direction::Left: + if (is_inline) { + if (cursor_position <= 0) { + cursor_position = 0; + } else { + --cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Left); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1); + } + } + break; + case Direction::Right: + if (is_inline) { + if (cursor_position >= static_cast(current_text.size())) { + cursor_position = static_cast(current_text.size()); + } else { + ++cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Right); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1); + } + } + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + + input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this); +} + +void QtSoftwareKeyboardDialog::StopInputThread() { + input_thread_running = false; + + if (input_thread.joinable()) { + input_thread.join(); + } + + if (input_interpreter) { + input_interpreter->ResetButtonStates(); + } +} + +void QtSoftwareKeyboardDialog::InputThread() { + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleButtonPressedOnce(); + + HandleButtonHold(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} + +QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { + connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window, + &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window, + &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this, + &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this, + &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection); +} + +QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; + +void QtSoftwareKeyboard::InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function submit_normal_callback_, + std::function + submit_inline_callback_) { + if (is_inline) { + submit_inline_callback = std::move(submit_inline_callback_); + } else { + submit_normal_callback = std::move(submit_normal_callback_); + } + + LOG_INFO(Service_AM, + "\nKeyboardInitializeParameters:" + "\nok_text={}" + "\nheader_text={}" + "\nsub_text={}" + "\nguide_text={}" + "\ninitial_text={}" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\ninitial_cursor_position={}" + "\ntype={}" + "\npassword_mode={}" + "\ntext_draw_type={}" + "\nkey_disable_flags={}" + "\nuse_blur_background={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + Common::UTF16ToUTF8(initialize_parameters.ok_text), + Common::UTF16ToUTF8(initialize_parameters.header_text), + Common::UTF16ToUTF8(initialize_parameters.sub_text), + Common::UTF16ToUTF8(initialize_parameters.guide_text), + Common::UTF16ToUTF8(initialize_parameters.initial_text), + initialize_parameters.max_text_length, initialize_parameters.min_text_length, + initialize_parameters.initial_cursor_position, initialize_parameters.type, + initialize_parameters.password_mode, initialize_parameters.text_draw_type, + initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background, + initialize_parameters.enable_backspace_button, + initialize_parameters.enable_return_button, + initialize_parameters.disable_cancel_button); + + emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters)); +} + +void QtSoftwareKeyboard::ShowNormalKeyboard() const { + emit MainWindowShowNormalKeyboard(); +} + +void QtSoftwareKeyboard::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const { + emit MainWindowShowTextCheckDialog(text_check_result, std::move(text_check_message)); +} + +void QtSoftwareKeyboard::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const { + LOG_INFO(Service_AM, + "\nInlineAppearParameters:" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\nkey_top_scale_x={}" + "\nkey_top_scale_y={}" + "\nkey_top_translate_x={}" + "\nkey_top_translate_y={}" + "\ntype={}" + "\nkey_disable_flags={}" + "\nkey_top_as_floating={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + appear_parameters.max_text_length, appear_parameters.min_text_length, + appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, + appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, + appear_parameters.type, appear_parameters.key_disable_flags.raw, + appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, + appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); + + emit MainWindowShowInlineKeyboard(std::move(appear_parameters)); +} + +void QtSoftwareKeyboard::HideInlineKeyboard() const { + emit MainWindowHideInlineKeyboard(); +} + +void QtSoftwareKeyboard::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) const { + LOG_INFO(Service_AM, + "\nInlineTextParameters:" + "\ninput_text={}" + "\ncursor_position={}", + Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + + emit MainWindowInlineTextChanged(std::move(text_parameters)); +} + +void QtSoftwareKeyboard::ExitKeyboard() const { + emit MainWindowExitKeyboard(); +} + +void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const { + submit_normal_callback(result, submitted_text); +} + +void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, + s32 cursor_position) const { + submit_inline_callback(reply_type, submitted_text, cursor_position); +} diff --git a/src/yuzu/applets/qt_software_keyboard.h b/src/yuzu/applets/qt_software_keyboard.h new file mode 100644 index 000000000..1a03c098c --- /dev/null +++ b/src/yuzu/applets/qt_software_keyboard.h @@ -0,0 +1,285 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "core/frontend/applets/software_keyboard.h" + +enum class HIDButton : u8; + +class InputInterpreter; + +namespace Core { +class System; +} + +namespace Ui { +class QtSoftwareKeyboardDialog; +} + +class GMainWindow; + +class QtSoftwareKeyboardDialog final : public QDialog { + Q_OBJECT + +public: + QtSoftwareKeyboardDialog(QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_); + ~QtSoftwareKeyboardDialog() override; + + void ShowNormalKeyboard(QPoint pos, QSize size); + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + + void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, + QSize size); + + void HideInlineKeyboard(); + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); + + void ExitKeyboard(); + +signals: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + +public slots: + void open() override; + void reject() override; + +protected: + /// We override the keyPressEvent for inputting text into the inline software keyboard. + void keyPressEvent(QKeyEvent* event) override; + +private: + enum class Direction { + Left, + Up, + Right, + Down, + }; + + enum class BottomOSKIndex { + LowerCase, + UpperCase, + NumberPad, + }; + + /** + * Moves and resizes the window to a specified position and size. + * + * @param pos Top-left window position + * @param size Window size + */ + void MoveAndResizeWindow(QPoint pos, QSize size); + + /** + * Rescales all keyboard elements to account for High DPI displays. + * + * @param width Window width + * @param height Window height + * @param dpi_scale Display scaling factor + */ + void RescaleKeyboardElements(float width, float height, float dpi_scale); + + /// Sets the keyboard type based on initialize_parameters. + void SetKeyboardType(); + + /// Sets the password mode based on initialize_parameters. + void SetPasswordMode(); + + /// Sets the text draw type based on initialize_parameters. + void SetTextDrawType(); + + /// Sets the controller image at the bottom left of the software keyboard. + void SetControllerImage(); + + /// Disables buttons based on initialize_parameters. + void DisableKeyboardButtons(); + + /// Changes whether the backspace or/and ok buttons should be enabled or disabled. + void SetBackspaceOkEnabled(); + + /** + * Validates the input text sent in based on the parameters in initialize_parameters. + * + * @param input_text Input text + * + * @returns True if the input text is valid, false otherwise. + */ + bool ValidateInputText(const QString& input_text); + + /// Switches between LowerCase and UpperCase (Shift and Caps Lock) + void ChangeBottomOSKIndex(); + + /// Processes a keyboard button click from the UI as normal keyboard input. + void NormalKeyboardButtonClicked(QPushButton* button); + + /// Processes a keyboard button click from the UI as inline keyboard input. + void InlineKeyboardButtonClicked(QPushButton* button); + + /** + * Inserts a string of arbitrary length into the current_text at the current cursor position. + * This is only used for the inline software keyboard. + */ + void InlineTextInsertString(std::u16string_view string); + + /// Setup the mouse hover workaround for "focusing" buttons. This should only be called once. + void SetupMouseHover(); + + /** + * Handles button presses and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonHold(); + + /** + * Translates a button press to focus or click a keyboard button. + * + * @param button The button press to process. + */ + void TranslateButtonPress(HIDButton button); + + /** + * Moves the focus of a button in a certain direction. + * + * @param direction The direction to move. + */ + void MoveButtonDirection(Direction direction); + + /** + * Moves the text cursor in a certain direction. + * + * @param direction The direction to move. + */ + void MoveTextCursorDirection(Direction direction); + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + std::unique_ptr ui; + + Core::System& system; + + // True if it is the inline software keyboard. + bool is_inline; + + // Common software keyboard initialize parameters. + Core::Frontend::KeyboardInitializeParameters initialize_parameters; + + // Used only by the inline software keyboard since the QLineEdit or QTextEdit is hidden. + std::u16string current_text; + s32 cursor_position{0}; + + static constexpr std::size_t NUM_ROWS_NORMAL = 5; + static constexpr std::size_t NUM_COLUMNS_NORMAL = 12; + static constexpr std::size_t NUM_ROWS_NUMPAD = 4; + static constexpr std::size_t NUM_COLUMNS_NUMPAD = 4; + + // Stores the normal keyboard layout. + std::array, NUM_ROWS_NORMAL>, 2> + keyboard_buttons; + // Stores the numberpad keyboard layout. + std::array, NUM_ROWS_NUMPAD> numberpad_buttons; + + // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons. + std::array all_buttons; + + std::size_t row{0}; + std::size_t column{0}; + + BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase}; + std::atomic caps_lock_enabled{false}; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; +}; + +class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { + Q_OBJECT + +public: + explicit QtSoftwareKeyboard(GMainWindow& parent); + ~QtSoftwareKeyboard() override; + + void InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) override; + + void ShowNormalKeyboard() const override; + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const override; + + void ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const override; + + void HideInlineKeyboard() const override; + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; + + void ExitKeyboard() const override; + +signals: + void MainWindowInitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const; + + void MainWindowShowNormalKeyboard() const; + + void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const; + + void MainWindowShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const; + + void MainWindowHideInlineKeyboard() const; + + void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const; + + void MainWindowExitKeyboard() const; + +private: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + + mutable std::function + submit_normal_callback; + mutable std::function + submit_inline_callback; +}; diff --git a/src/yuzu/applets/qt_software_keyboard.ui b/src/yuzu/applets/qt_software_keyboard.ui new file mode 100644 index 000000000..b0a1fcde9 --- /dev/null +++ b/src/yuzu/applets/qt_software_keyboard.ui @@ -0,0 +1,3503 @@ + + + QtSoftwareKeyboardDialog + + + + 0 + 0 + 1280 + 720 + + + + Software Keyboard + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + 100 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 17 + + + + 0/32 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 26 + 50 + false + + + + Qt::StrongFocus + + + + + + 32 + + + Enter Text + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 23 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 17 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 17 + + + + 0/500 + + + + + + + + + + + 0 + + + 14 + + + 9 + + + 14 + + + 9 + + + + + + 26 + + + + Qt::StrongFocus + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + + + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Shift + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + - + + + + + + + + 1 + 1 + + + + + 28 + + + + ' + + + + + + + + 1 + 1 + + + + + 28 + + + + / + + + + + + + + 1 + 1 + + + + + 28 + + + + ! + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + w + + + + + + + + 1 + 1 + + + + + 28 + + + + r + + + + + + + + 1 + 1 + + + + + 28 + + + + e + + + + + + + + 1 + 1 + + + + + 28 + + + + q + + + + + + + + 1 + 1 + + + + + 28 + + + + u + + + + + + + + 1 + 1 + + + + + 28 + + + + y + + + + + + + + 1 + 1 + + + + + 28 + + + + t + + + + + + + + 1 + 1 + + + + + 28 + + + + o + + + + + + + + 1 + 1 + + + + + 28 + + + + p + + + + + + + + 1 + 1 + + + + + 28 + + + + i + + + + + + + + 1 + 1 + + + + + 28 + + + + a + + + + + + + + 1 + 1 + + + + + 28 + + + + s + + + + + + + + 1 + 1 + + + + + 28 + + + + d + + + + + + + + 1 + 1 + + + + + 28 + + + + f + + + + + + + + 1 + 1 + + + + + 28 + + + + h + + + + + + + + 1 + 1 + + + + + 28 + + + + j + + + + + + + + 1 + 1 + + + + + 28 + + + + g + + + + + + + + 1 + 1 + + + + + 28 + + + + k + + + + + + + + 1 + 1 + + + + + 28 + + + + l + + + + + + + + 1 + 1 + + + + + 28 + + + + : + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + z + + + + + + + + 1 + 1 + + + + + 28 + + + + c + + + + + + + + 1 + 1 + + + + + 28 + + + + x + + + + + + + + 1 + 1 + + + + + 28 + + + + v + + + + + + + + 1 + 1 + + + + + 28 + + + + m + + + + + + + + 1 + 1 + + + + + 28 + + + + , + + + + + + + + 1 + 1 + + + + + 28 + + + + n + + + + + + + + 1 + 1 + + + + + 28 + + + + b + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + ? + + + + + + + + 1 + 1 + + + + + 28 + + + + . + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Caps Lock + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + _ + + + + + + + + 1 + 1 + + + + + 28 + + + + " + + + + + + + + 1 + 1 + + + + + 28 + + + + @ + + + + + + + + 1 + 1 + + + + + 28 + + + + = + + + + + + + + 1 + 1 + + + + + 28 + + + + && + + + + + + + + 1 + 1 + + + + + 28 + + + + * + + + + + + + + 1 + 1 + + + + + 28 + + + + ) + + + + + + + + 1 + 1 + + + + + 28 + + + + ( + + + + + + + + 1 + 1 + + + + + 28 + + + + W + + + + + + + + 1 + 1 + + + + + 28 + + + + R + + + + + + + + 1 + 1 + + + + + 28 + + + + E + + + + + + + + 1 + 1 + + + + + 28 + + + + Q + + + + + + + + 1 + 1 + + + + + 28 + + + + U + + + + + + + + 1 + 1 + + + + + 28 + + + + Y + + + + + + + + 1 + 1 + + + + + 28 + + + + T + + + + + + + + 1 + 1 + + + + + 28 + + + + O + + + + + + + + 1 + 1 + + + + + 28 + + + + P + + + + + + + + 1 + 1 + + + + + 28 + + + + I + + + + + + + + 1 + 1 + + + + + 28 + + + + A + + + + + + + + 1 + 1 + + + + + 28 + + + + S + + + + + + + + 1 + 1 + + + + + 28 + + + + D + + + + + + + + 1 + 1 + + + + + 28 + + + + F + + + + + + + + 1 + 1 + + + + + 28 + + + + H + + + + + + + + 1 + 1 + + + + + 28 + + + + J + + + + + + + + 1 + 1 + + + + + 28 + + + + G + + + + + + + + 1 + 1 + + + + + 28 + + + + K + + + + + + + + 1 + 1 + + + + + 28 + + + + L + + + + + + + + 1 + 1 + + + + + 28 + + + + ; + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + Z + + + + + + + + 1 + 1 + + + + + 28 + + + + C + + + + + + + + 1 + 1 + + + + + 28 + + + + X + + + + + + + + 1 + 1 + + + + + 28 + + + + V + + + + + + + + 1 + 1 + + + + + 28 + + + + M + + + + + + + + 1 + 1 + + + + + 28 + + + + < + + + + + + + + 1 + 1 + + + + + 28 + + + + N + + + + + + + + 1 + 1 + + + + + 28 + + + + B + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + > + + + + + + + + 1 + 1 + + + + + 28 + + + + # + + + + + + + + 1 + 1 + + + + + 28 + + + + ] + + + + + + + + 1 + 1 + + + + + 28 + + + + $ + + + + + + + + 1 + 1 + + + + + 28 + + + + [ + + + + + + + + 1 + 1 + + + + + 28 + + + + ^ + + + + + + + + 1 + 1 + + + + + 28 + + + + % + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + + + + + button_1 + button_2 + button_3 + button_4 + button_5 + button_6 + button_7 + button_8 + button_9 + button_0 + button_minus + button_backspace + button_q + button_w + button_e + button_r + button_t + button_y + button_u + button_i + button_o + button_p + button_slash + button_return + button_a + button_s + button_d + button_f + button_g + button_h + button_j + button_k + button_l + button_colon + button_apostrophe + button_z + button_x + button_c + button_v + button_b + button_n + button_m + button_comma + button_dot + button_question + button_exclamation + button_ok + button_shift + button_space + button_hash + button_left_bracket + button_right_bracket + button_dollar + button_percent + button_circumflex + button_ampersand + button_asterisk + button_left_parenthesis + button_right_parenthesis + button_underscore + button_backspace_shift + button_q_shift + button_w_shift + button_e_shift + button_r_shift + button_t_shift + button_y_shift + button_u_shift + button_i_shift + button_o_shift + button_p_shift + button_at + button_return_shift + button_a_shift + button_s_shift + button_d_shift + button_f_shift + button_g_shift + button_h_shift + button_j_shift + button_k_shift + button_l_shift + button_semicolon + button_quotation + button_z_shift + button_x_shift + button_c_shift + button_v_shift + button_b_shift + button_n_shift + button_m_shift + button_less_than + button_greater_than + button_plus + button_equal + button_ok_shift + button_shift_shift + button_space_shift + button_1_num + button_2_num + button_3_num + button_backspace_num + button_4_num + button_5_num + button_6_num + button_ok_num + button_7_num + button_8_num + button_9_num + button_0_num + + + + + + diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp new file mode 100644 index 000000000..b112dd7b0 --- /dev/null +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -0,0 +1,417 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef YUZU_USE_QT_WEB_ENGINE +#include + +#include +#include +#include +#include +#include +#endif + +#include "common/fs/path_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" +#include "yuzu/applets/qt_web_browser.h" +#include "yuzu/applets/qt_web_browser_scripts.h" +#include "yuzu/main.h" +#include "yuzu/util/url_request_interceptor.h" + +#ifdef YUZU_USE_QT_WEB_ENGINE + +namespace { + +constexpr int HIDButtonToKey(HIDButton button) { + switch (button) { + case HIDButton::DLeft: + case HIDButton::LStickLeft: + return Qt::Key_Left; + case HIDButton::DUp: + case HIDButton::LStickUp: + return Qt::Key_Up; + case HIDButton::DRight: + case HIDButton::LStickRight: + return Qt::Key_Right; + case HIDButton::DDown: + case HIDButton::LStickDown: + return Qt::Key_Down; + default: + return 0; + } +} + +} // Anonymous namespace + +QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_) + : QWebEngineView(parent), input_subsystem{input_subsystem_}, + url_interceptor(std::make_unique()), + input_interpreter(std::make_unique(system)), + default_profile{QWebEngineProfile::defaultProfile()}, + global_settings{QWebEngineSettings::globalSettings()} { + QWebEngineScript gamepad; + QWebEngineScript window_nx; + + gamepad.setName(QStringLiteral("gamepad_script.js")); + window_nx.setName(QStringLiteral("window_nx_script.js")); + + gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT)); + window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT)); + + gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation); + window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation); + + gamepad.setWorldId(QWebEngineScript::MainWorld); + window_nx.setWorldId(QWebEngineScript::MainWorld); + + gamepad.setRunsOnSubFrames(true); + window_nx.setRunsOnSubFrames(true); + + default_profile->scripts()->insert(gamepad); + default_profile->scripts()->insert(window_nx); + + default_profile->setRequestInterceptor(url_interceptor.get()); + + global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); + global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); + global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true); + global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false); + + global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto")); + + connect( + page(), &QWebEnginePage::windowCloseRequested, page(), + [this] { + if (page()->url() == url_interceptor->GetRequestedURL()) { + SetFinished(true); + SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed); + } + }, + Qt::QueuedConnection); +} + +QtNXWebEngineView::~QtNXWebEngineView() { + SetFinished(true); + StopInputThread(); +} + +void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url, + const std::string& additional_args) { + is_local = true; + + LoadExtractedFonts(); + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); + SetLastURL("http://localhost/"); + StartInputThread(); + + load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() + + QString::fromStdString(additional_args))); +} + +void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url, + const std::string& additional_args) { + is_local = false; + + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); + SetLastURL("http://localhost/"); + StartInputThread(); + + load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args))); +} + +void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) { + const QString user_agent_str = [user_agent] { + switch (user_agent) { + case UserAgent::WebApplet: + default: + return QStringLiteral("WebApplet"); + case UserAgent::ShopN: + return QStringLiteral("ShopN"); + case UserAgent::LoginApplet: + return QStringLiteral("LoginApplet"); + case UserAgent::ShareApplet: + return QStringLiteral("ShareApplet"); + case UserAgent::LobbyApplet: + return QStringLiteral("LobbyApplet"); + case UserAgent::WifiWebAuthApplet: + return QStringLiteral("WifiWebAuthApplet"); + } + }(); + + QWebEngineProfile::defaultProfile()->setHttpUserAgent( + QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 " + "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389") + .arg(user_agent_str)); +} + +bool QtNXWebEngineView::IsFinished() const { + return finished; +} + +void QtNXWebEngineView::SetFinished(bool finished_) { + finished = finished_; +} + +Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const { + return exit_reason; +} + +void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) { + exit_reason = exit_reason_; +} + +const std::string& QtNXWebEngineView::GetLastURL() const { + return last_url; +} + +void QtNXWebEngineView::SetLastURL(std::string last_url_) { + last_url = std::move(last_url_); +} + +QString QtNXWebEngineView::GetCurrentURL() const { + return url_interceptor->GetRequestedURL().toString(); +} + +void QtNXWebEngineView::hide() { + SetFinished(true); + StopInputThread(); + + QWidget::hide(); +} + +void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) { + if (is_local) { + input_subsystem->GetKeyboard()->PressKey(event->key()); + } +} + +void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) { + if (is_local) { + input_subsystem->GetKeyboard()->ReleaseKey(event->key()); + } +} + +template +void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + page()->runJavaScript( + QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast(button)), + [&](const QVariant& variant) { + if (variant.toBool()) { + switch (button) { + case HIDButton::A: + SendMultipleKeyPressEvents(); + break; + case HIDButton::B: + SendKeyPressEvent(Qt::Key_B); + break; + case HIDButton::X: + SendKeyPressEvent(Qt::Key_X); + break; + case HIDButton::Y: + SendKeyPressEvent(Qt::Key_Y); + break; + default: + break; + } + } + }); + + page()->runJavaScript( + QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }") + .arg(static_cast(button))); + } + }; + + (f(T), ...); +} + +template +void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); + } + }; + + (f(T), ...); +} + +template +void QtNXWebEngineView::HandleWindowKeyButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); + } + }; + + (f(T), ...); +} + +void QtNXWebEngineView::SendKeyPressEvent(int key) { + if (key == 0) { + return; + } + + QCoreApplication::postEvent(focusProxy(), + new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier)); + QCoreApplication::postEvent(focusProxy(), + new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier)); +} + +void QtNXWebEngineView::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + input_thread = std::thread(&QtNXWebEngineView::InputThread, this); +} + +void QtNXWebEngineView::StopInputThread() { + if (is_local) { + QWidget::releaseKeyboard(); + } + + input_thread_running = false; + if (input_thread.joinable()) { + input_thread.join(); + } +} + +void QtNXWebEngineView::InputThread() { + // Wait for 1 second before allowing any inputs to be processed. + std::this_thread::sleep_for(std::chrono::seconds(1)); + + if (is_local) { + QWidget::grabKeyboard(); + } + + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleWindowFooterButtonPressedOnce(); + + HandleWindowKeyButtonPressedOnce(); + + HandleWindowKeyButtonHold(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} + +void QtNXWebEngineView::LoadExtractedFonts() { + QWebEngineScript nx_font_css; + QWebEngineScript load_nx_font; + + auto fonts_dir_str = Common::FS::PathToUTF8String( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/"); + + std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/'); + + const auto fonts_dir = QString::fromStdString(fonts_dir_str); + + nx_font_css.setName(QStringLiteral("nx_font_css.js")); + load_nx_font.setName(QStringLiteral("load_nx_font.js")); + + nx_font_css.setSourceCode( + QString::fromStdString(NX_FONT_CSS) + .arg(fonts_dir + QStringLiteral("FontStandard.ttf")) + .arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf")) + .arg(fonts_dir + QStringLiteral("FontKorean.ttf")) + .arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf")) + .arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf"))); + load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); + + nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); + load_nx_font.setInjectionPoint(QWebEngineScript::Deferred); + + nx_font_css.setWorldId(QWebEngineScript::MainWorld); + load_nx_font.setWorldId(QWebEngineScript::MainWorld); + + nx_font_css.setRunsOnSubFrames(true); + load_nx_font.setRunsOnSubFrames(true); + + default_profile->scripts()->insert(nx_font_css); + default_profile->scripts()->insert(load_nx_font); + + connect( + url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(), + [this] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT)); + }, + Qt::QueuedConnection); +} + +#endif + +QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { + connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window, + &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this, + &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserClosed, this, + &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection); +} + +QtWebBrowser::~QtWebBrowser() = default; + +void QtWebBrowser::OpenLocalWebPage( + const std::string& local_url, std::function extract_romfs_callback_, + std::function callback_) const { + extract_romfs_callback = std::move(extract_romfs_callback_); + callback = std::move(callback_); + + const auto index = local_url.find('?'); + + if (index == std::string::npos) { + emit MainWindowOpenWebPage(local_url, "", true); + } else { + emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true); + } +} + +void QtWebBrowser::OpenExternalWebPage( + const std::string& external_url, + std::function callback_) const { + callback = std::move(callback_); + + const auto index = external_url.find('?'); + + if (index == std::string::npos) { + emit MainWindowOpenWebPage(external_url, "", false); + } else { + emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), + false); + } +} + +void QtWebBrowser::MainWindowExtractOfflineRomFS() { + extract_romfs_callback(); +} + +void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url) { + callback(exit_reason, last_url); +} diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h new file mode 100644 index 000000000..7ad07409f --- /dev/null +++ b/src/yuzu/applets/qt_web_browser.h @@ -0,0 +1,218 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include + +#ifdef YUZU_USE_QT_WEB_ENGINE +#include +#endif + +#include "core/frontend/applets/web_browser.h" + +enum class HIDButton : u8; + +class GMainWindow; +class InputInterpreter; +class UrlRequestInterceptor; + +namespace Core { +class System; +} + +namespace InputCommon { +class InputSubsystem; +} + +#ifdef YUZU_USE_QT_WEB_ENGINE + +enum class UserAgent { + WebApplet, + ShopN, + LoginApplet, + ShareApplet, + LobbyApplet, + WifiWebAuthApplet, +}; + +class QWebEngineProfile; +class QWebEngineSettings; + +class QtNXWebEngineView : public QWebEngineView { + Q_OBJECT + +public: + explicit QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_); + ~QtNXWebEngineView() override; + + /** + * Loads a HTML document that exists locally. Cannot be used to load external websites. + * + * @param main_url The url to the file. + * @param additional_args Additional arguments appended to the main url. + */ + void LoadLocalWebPage(const std::string& main_url, const std::string& additional_args); + + /** + * Loads an external website. Cannot be used to load local urls. + * + * @param main_url The url to the website. + * @param additional_args Additional arguments appended to the main url. + */ + void LoadExternalWebPage(const std::string& main_url, const std::string& additional_args); + + /** + * Sets the background color of the web page. + * + * @param color The color to set. + */ + void SetBackgroundColor(QColor color); + + /** + * Sets the user agent of the web browser. + * + * @param user_agent The user agent enum. + */ + void SetUserAgent(UserAgent user_agent); + + [[nodiscard]] bool IsFinished() const; + void SetFinished(bool finished_); + + [[nodiscard]] Service::AM::Applets::WebExitReason GetExitReason() const; + void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_); + + [[nodiscard]] const std::string& GetLastURL() const; + void SetLastURL(std::string last_url_); + + /** + * This gets the current URL that has been requested by the webpage. + * This only applies to the main frame. Sub frames and other resources are ignored. + * + * @return Currently requested URL + */ + [[nodiscard]] QString GetCurrentURL() const; + +public slots: + void hide(); + +protected: + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + +private: + /** + * Handles button presses to execute functions assigned in yuzu_key_callbacks. + * yuzu_key_callbacks contains specialized functions for the buttons in the window footer + * that can be overriden by games to achieve desired functionality. + * + * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks + */ + template + void HandleWindowFooterButtonPressedOnce(); + + /** + * Handles button presses and converts them into keyboard input. + * This should only be used to convert D-Pad or Analog Stick input into arrow keys. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleWindowKeyButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * This should only be used to convert D-Pad or Analog Stick input into arrow keys. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleWindowKeyButtonHold(); + + /** + * Sends a key press event to QWebEngineView. + * + * @param key Qt key code. + */ + void SendKeyPressEvent(int key); + + /** + * Sends multiple key press events to QWebEngineView. + * + * @tparam int Qt key code. + */ + template + void SendMultipleKeyPressEvents() { + (SendKeyPressEvent(T), ...); + } + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + /// Loads the extracted fonts using JavaScript. + void LoadExtractedFonts(); + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr url_interceptor; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; + + std::atomic finished{}; + + Service::AM::Applets::WebExitReason exit_reason{ + Service::AM::Applets::WebExitReason::EndButtonPressed}; + + std::string last_url{"http://localhost/"}; + + bool is_local{}; + + QWebEngineProfile* default_profile; + QWebEngineSettings* global_settings; +}; + +#endif + +class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { + Q_OBJECT + +public: + explicit QtWebBrowser(GMainWindow& parent); + ~QtWebBrowser() override; + + void OpenLocalWebPage(const std::string& local_url, + std::function extract_romfs_callback_, + std::function + callback_) const override; + + void OpenExternalWebPage(const std::string& external_url, + std::function + callback_) const override; + +signals: + void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args, + bool is_local) const; + +private: + void MainWindowExtractOfflineRomFS(); + + void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url); + + mutable std::function extract_romfs_callback; + + mutable std::function callback; +}; diff --git a/src/yuzu/applets/qt_web_browser_scripts.h b/src/yuzu/applets/qt_web_browser_scripts.h new file mode 100644 index 000000000..992837a85 --- /dev/null +++ b/src/yuzu/applets/qt_web_browser_scripts.h @@ -0,0 +1,193 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +constexpr char NX_FONT_CSS[] = R"( +(function() { + css = document.createElement('style'); + css.type = 'text/css'; + css.id = 'nx_font'; + css.innerText = ` +/* FontStandard */ +@font-face { + font-family: 'FontStandard'; + src: url('%1') format('truetype'); +} + +/* FontChineseSimplified */ +@font-face { + font-family: 'FontChineseSimplified'; + src: url('%2') format('truetype'); +} + +/* FontExtendedChineseSimplified */ +@font-face { + font-family: 'FontExtendedChineseSimplified'; + src: url('%3') format('truetype'); +} + +/* FontChineseTraditional */ +@font-face { + font-family: 'FontChineseTraditional'; + src: url('%4') format('truetype'); +} + +/* FontKorean */ +@font-face { + font-family: 'FontKorean'; + src: url('%5') format('truetype'); +} + +/* FontNintendoExtended */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%6') format('truetype'); +} + +/* FontNintendoExtended2 */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%7') format('truetype'); +} +`; + + document.head.appendChild(css); +})(); +)"; + +constexpr char LOAD_NX_FONT[] = R"( +(function() { + var elements = document.querySelectorAll("*"); + + for (var i = 0; i < elements.length; i++) { + var style = window.getComputedStyle(elements[i], null); + if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") || + style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) { + elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } else { + elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } + } +})(); +)"; + +constexpr char GAMEPAD_SCRIPT[] = R"( +window.addEventListener("gamepadconnected", function(e) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", + e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length); +}); + +window.addEventListener("gamepaddisconnected", function(e) { + console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id); +}); +)"; + +constexpr char WINDOW_NX_SCRIPT[] = R"( +var end_applet = false; +var yuzu_key_callbacks = []; + +(function() { + class WindowNX { + constructor() { + yuzu_key_callbacks[1] = function() { window.history.back(); }; + yuzu_key_callbacks[2] = function() { window.nx.endApplet(); }; + } + + addEventListener(type, listener, options) { + console.log("nx.addEventListener called, type=%s", type); + + window.addEventListener(type, listener, options); + } + + endApplet() { + console.log("nx.endApplet called"); + + end_applet = true; + } + + playSystemSe(system_se) { + console.log("nx.playSystemSe is not implemented, system_se=%s", system_se); + } + + sendMessage(message) { + console.log("nx.sendMessage is not implemented, message=%s", message); + } + + setCursorScrollSpeed(scroll_speed) { + console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed); + } + } + + class WindowNXFooter { + setAssign(key, label, func, option) { + console.log("nx.footer.setAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = func; + break; + case "B": + yuzu_key_callbacks[1] = func; + break; + case "X": + yuzu_key_callbacks[2] = func; + break; + case "Y": + yuzu_key_callbacks[3] = func; + break; + case "L": + yuzu_key_callbacks[6] = func; + break; + case "R": + yuzu_key_callbacks[7] = func; + break; + } + } + + setFixed(kind) { + console.log("nx.footer.setFixed is not implemented, kind=%s", kind); + } + + unsetAssign(key) { + console.log("nx.footer.unsetAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = function() {}; + break; + case "B": + yuzu_key_callbacks[1] = function() {}; + break; + case "X": + yuzu_key_callbacks[2] = function() {}; + break; + case "Y": + yuzu_key_callbacks[3] = function() {}; + break; + case "L": + yuzu_key_callbacks[6] = function() {}; + break; + case "R": + yuzu_key_callbacks[7] = function() {}; + break; + } + } + } + + class WindowNXPlayReport { + incrementCounter(counter_id) { + console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id); + } + + setCounterSetIdentifier(counter_id) { + console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id); + } + } + + window.nx = new WindowNX(); + window.nx.footer = new WindowNXFooter(); + window.nx.playReport = new WindowNXPlayReport(); +})(); +)"; diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp deleted file mode 100644 index aa453a79f..000000000 --- a/src/yuzu/applets/software_keyboard.cpp +++ /dev/null @@ -1,1620 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include - -#include "common/logging/log.h" -#include "common/settings.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/frontend/input_interpreter.h" -#include "ui_software_keyboard.h" -#include "yuzu/applets/software_keyboard.h" -#include "yuzu/main.h" -#include "yuzu/util/overlay_dialog.h" - -namespace { - -using namespace Service::AM::Applets; - -constexpr float BASE_HEADER_FONT_SIZE = 23.0f; -constexpr float BASE_SUB_FONT_SIZE = 17.0f; -constexpr float BASE_EDITOR_FONT_SIZE = 26.0f; -constexpr float BASE_CHAR_BUTTON_FONT_SIZE = 28.0f; -constexpr float BASE_LABEL_BUTTON_FONT_SIZE = 18.0f; -constexpr float BASE_ICON_BUTTON_SIZE = 36.0f; -[[maybe_unused]] constexpr float BASE_WIDTH = 1280.0f; -constexpr float BASE_HEIGHT = 720.0f; - -} // Anonymous namespace - -QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( - QWidget* parent, Core::System& system_, bool is_inline_, - Core::Frontend::KeyboardInitializeParameters initialize_parameters_) - : QDialog(parent), ui{std::make_unique()}, system{system_}, - is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} { - ui->setupUi(this); - - setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose); - setAttribute(Qt::WA_TranslucentBackground); - - keyboard_buttons = {{ - {{ - { - ui->button_1, - ui->button_2, - ui->button_3, - ui->button_4, - ui->button_5, - ui->button_6, - ui->button_7, - ui->button_8, - ui->button_9, - ui->button_0, - ui->button_minus, - ui->button_backspace, - }, - { - ui->button_q, - ui->button_w, - ui->button_e, - ui->button_r, - ui->button_t, - ui->button_y, - ui->button_u, - ui->button_i, - ui->button_o, - ui->button_p, - ui->button_slash, - ui->button_return, - }, - { - ui->button_a, - ui->button_s, - ui->button_d, - ui->button_f, - ui->button_g, - ui->button_h, - ui->button_j, - ui->button_k, - ui->button_l, - ui->button_colon, - ui->button_apostrophe, - ui->button_return, - }, - { - ui->button_z, - ui->button_x, - ui->button_c, - ui->button_v, - ui->button_b, - ui->button_n, - ui->button_m, - ui->button_comma, - ui->button_dot, - ui->button_question, - ui->button_exclamation, - ui->button_ok, - }, - { - ui->button_shift, - ui->button_shift, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_ok, - }, - }}, - {{ - { - ui->button_hash, - ui->button_left_bracket, - ui->button_right_bracket, - ui->button_dollar, - ui->button_percent, - ui->button_circumflex, - ui->button_ampersand, - ui->button_asterisk, - ui->button_left_parenthesis, - ui->button_right_parenthesis, - ui->button_underscore, - ui->button_backspace_shift, - }, - { - ui->button_q_shift, - ui->button_w_shift, - ui->button_e_shift, - ui->button_r_shift, - ui->button_t_shift, - ui->button_y_shift, - ui->button_u_shift, - ui->button_i_shift, - ui->button_o_shift, - ui->button_p_shift, - ui->button_at, - ui->button_return_shift, - }, - { - ui->button_a_shift, - ui->button_s_shift, - ui->button_d_shift, - ui->button_f_shift, - ui->button_g_shift, - ui->button_h_shift, - ui->button_j_shift, - ui->button_k_shift, - ui->button_l_shift, - ui->button_semicolon, - ui->button_quotation, - ui->button_return_shift, - }, - { - ui->button_z_shift, - ui->button_x_shift, - ui->button_c_shift, - ui->button_v_shift, - ui->button_b_shift, - ui->button_n_shift, - ui->button_m_shift, - ui->button_less_than, - ui->button_greater_than, - ui->button_plus, - ui->button_equal, - ui->button_ok_shift, - }, - { - ui->button_shift_shift, - ui->button_shift_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_ok_shift, - }, - }}, - }}; - - numberpad_buttons = {{ - { - ui->button_1_num, - ui->button_2_num, - ui->button_3_num, - ui->button_backspace_num, - }, - { - ui->button_4_num, - ui->button_5_num, - ui->button_6_num, - ui->button_ok_num, - }, - { - ui->button_7_num, - ui->button_8_num, - ui->button_9_num, - ui->button_ok_num, - }, - { - nullptr, - ui->button_0_num, - nullptr, - ui->button_ok_num, - }, - }}; - - all_buttons = { - ui->button_1, - ui->button_2, - ui->button_3, - ui->button_4, - ui->button_5, - ui->button_6, - ui->button_7, - ui->button_8, - ui->button_9, - ui->button_0, - ui->button_minus, - ui->button_backspace, - ui->button_q, - ui->button_w, - ui->button_e, - ui->button_r, - ui->button_t, - ui->button_y, - ui->button_u, - ui->button_i, - ui->button_o, - ui->button_p, - ui->button_slash, - ui->button_return, - ui->button_a, - ui->button_s, - ui->button_d, - ui->button_f, - ui->button_g, - ui->button_h, - ui->button_j, - ui->button_k, - ui->button_l, - ui->button_colon, - ui->button_apostrophe, - ui->button_z, - ui->button_x, - ui->button_c, - ui->button_v, - ui->button_b, - ui->button_n, - ui->button_m, - ui->button_comma, - ui->button_dot, - ui->button_question, - ui->button_exclamation, - ui->button_ok, - ui->button_shift, - ui->button_space, - ui->button_hash, - ui->button_left_bracket, - ui->button_right_bracket, - ui->button_dollar, - ui->button_percent, - ui->button_circumflex, - ui->button_ampersand, - ui->button_asterisk, - ui->button_left_parenthesis, - ui->button_right_parenthesis, - ui->button_underscore, - ui->button_backspace_shift, - ui->button_q_shift, - ui->button_w_shift, - ui->button_e_shift, - ui->button_r_shift, - ui->button_t_shift, - ui->button_y_shift, - ui->button_u_shift, - ui->button_i_shift, - ui->button_o_shift, - ui->button_p_shift, - ui->button_at, - ui->button_return_shift, - ui->button_a_shift, - ui->button_s_shift, - ui->button_d_shift, - ui->button_f_shift, - ui->button_g_shift, - ui->button_h_shift, - ui->button_j_shift, - ui->button_k_shift, - ui->button_l_shift, - ui->button_semicolon, - ui->button_quotation, - ui->button_z_shift, - ui->button_x_shift, - ui->button_c_shift, - ui->button_v_shift, - ui->button_b_shift, - ui->button_n_shift, - ui->button_m_shift, - ui->button_less_than, - ui->button_greater_than, - ui->button_plus, - ui->button_equal, - ui->button_ok_shift, - ui->button_shift_shift, - ui->button_space_shift, - ui->button_1_num, - ui->button_2_num, - ui->button_3_num, - ui->button_backspace_num, - ui->button_4_num, - ui->button_5_num, - ui->button_6_num, - ui->button_ok_num, - ui->button_7_num, - ui->button_8_num, - ui->button_9_num, - ui->button_0_num, - }; - - SetupMouseHover(); - - if (!initialize_parameters.ok_text.empty()) { - ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text)); - } - - ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text)); - ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text)); - - current_text = initialize_parameters.initial_text; - cursor_position = initialize_parameters.initial_cursor_position; - - SetTextDrawType(); - - for (auto* button : all_buttons) { - connect(button, &QPushButton::clicked, this, [this, button](bool) { - if (is_inline) { - InlineKeyboardButtonClicked(button); - } else { - NormalKeyboardButtonClicked(button); - } - }); - } - - // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend - if (system.IsPoweredOn()) { - input_interpreter = std::make_unique(system); - } -} - -QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() { - StopInputThread(); -} - -void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) { - if (isVisible()) { - return; - } - - MoveAndResizeWindow(pos, size); - - SetKeyboardType(); - SetPasswordMode(); - SetControllerImage(); - DisableKeyboardButtons(); - SetBackspaceOkEnabled(); - - open(); -} - -void QtSoftwareKeyboardDialog::ShowTextCheckDialog( - Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) { - switch (text_check_result) { - case SwkbdTextCheckResult::Success: - case SwkbdTextCheckResult::Silent: - default: - break; - case SwkbdTextCheckResult::Failure: { - StopInputThread(); - - OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), - QString{}, tr("OK"), Qt::AlignCenter); - dialog.exec(); - - StartInputThread(); - break; - } - case SwkbdTextCheckResult::Confirm: { - StopInputThread(); - - OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), - tr("Cancel"), tr("OK"), Qt::AlignCenter); - if (dialog.exec() != QDialog::Accepted) { - StartInputThread(); - break; - } - - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); - - emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); - break; - } - } -} - -void QtSoftwareKeyboardDialog::ShowInlineKeyboard( - Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) { - MoveAndResizeWindow(pos, size); - - ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);")); - - ui->headerOSK->hide(); - ui->subOSK->hide(); - ui->inputOSK->hide(); - ui->charactersOSK->hide(); - ui->inputBoxOSK->hide(); - ui->charactersBoxOSK->hide(); - - initialize_parameters.max_text_length = appear_parameters.max_text_length; - initialize_parameters.min_text_length = appear_parameters.min_text_length; - initialize_parameters.type = appear_parameters.type; - initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags; - initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button; - initialize_parameters.enable_return_button = appear_parameters.enable_return_button; - initialize_parameters.disable_cancel_button = initialize_parameters.disable_cancel_button; - - SetKeyboardType(); - SetControllerImage(); - DisableKeyboardButtons(); - SetBackspaceOkEnabled(); - - open(); -} - -void QtSoftwareKeyboardDialog::HideInlineKeyboard() { - StopInputThread(); - QDialog::hide(); -} - -void QtSoftwareKeyboardDialog::InlineTextChanged( - Core::Frontend::InlineTextParameters text_parameters) { - current_text = text_parameters.input_text; - cursor_position = text_parameters.cursor_position; - - SetBackspaceOkEnabled(); -} - -void QtSoftwareKeyboardDialog::ExitKeyboard() { - StopInputThread(); - QDialog::done(QDialog::Accepted); -} - -void QtSoftwareKeyboardDialog::open() { - QDialog::open(); - - row = 0; - column = 0; - - const auto* const curr_button = - keyboard_buttons[static_cast(bottom_osk_index)][row][column]; - - // This is a workaround for setFocus() randomly not showing focus in the UI - QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); - - StartInputThread(); -} - -void QtSoftwareKeyboardDialog::reject() { - // Pressing the ESC key in a dialog calls QDialog::reject(). - // We will override this behavior to the "Cancel" action on the software keyboard. - TranslateButtonPress(HIDButton::X); -} - -void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { - if (!is_inline) { - QDialog::keyPressEvent(event); - return; - } - - const auto entered_key = event->key(); - - switch (entered_key) { - case Qt::Key_Escape: - QDialog::keyPressEvent(event); - return; - case Qt::Key_Backspace: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_backspace->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_backspace_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_backspace_num->click(); - break; - default: - break; - } - return; - case Qt::Key_Return: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_ok->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_ok_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_ok_num->click(); - break; - default: - break; - } - return; - case Qt::Key_Left: - MoveTextCursorDirection(Direction::Left); - return; - case Qt::Key_Right: - MoveTextCursorDirection(Direction::Right); - return; - default: - break; - } - - const auto entered_text = event->text(); - - if (entered_text.isEmpty()) { - return; - } - - InlineTextInsertString(entered_text.toStdU16String()); -} - -void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { - QDialog::move(pos); - QDialog::resize(size); - - // High DPI - const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; - - RescaleKeyboardElements(size.width(), size.height(), dpi_scale); -} - -void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) { - const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - const auto char_button_font_size = - BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - const auto label_button_font_size = - BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - - QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal); - QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal); - QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal); - QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal); - QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size, - QFont::Normal); - - ui->label_header->setFont(header_font); - ui->label_sub->setFont(sub_font); - ui->line_edit_osk->setFont(editor_font); - ui->text_edit_osk->setFont(editor_font); - ui->label_characters->setFont(sub_font); - ui->label_characters_box->setFont(sub_font); - - ui->label_shift->setFont(label_button_font); - ui->label_shift_shift->setFont(label_button_font); - ui->label_cancel->setFont(label_button_font); - ui->label_cancel_shift->setFont(label_button_font); - ui->label_cancel_num->setFont(label_button_font); - ui->label_enter->setFont(label_button_font); - ui->label_enter_shift->setFont(label_button_font); - ui->label_enter_num->setFont(label_button_font); - - for (auto* button : all_buttons) { - if (button == ui->button_return || button == ui->button_return_shift) { - button->setFont(label_button_font); - continue; - } - - if (button == ui->button_space || button == ui->button_space_shift) { - button->setFont(label_button_font); - continue; - } - - if (button == ui->button_shift || button == ui->button_shift_shift) { - button->setFont(label_button_font); - button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * - (height / BASE_HEIGHT)); - continue; - } - - if (button == ui->button_backspace || button == ui->button_backspace_shift || - button == ui->button_backspace_num) { - button->setFont(label_button_font); - button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * - (height / BASE_HEIGHT)); - continue; - } - - if (button == ui->button_ok || button == ui->button_ok_shift || - button == ui->button_ok_num) { - button->setFont(label_button_font); - continue; - } - - button->setFont(char_button_font); - } -} - -void QtSoftwareKeyboardDialog::SetKeyboardType() { - switch (initialize_parameters.type) { - case SwkbdType::Normal: - case SwkbdType::Qwerty: - case SwkbdType::Unknown3: - case SwkbdType::Latin: - case SwkbdType::SimplifiedChinese: - case SwkbdType::TraditionalChinese: - case SwkbdType::Korean: - default: { - bottom_osk_index = BottomOSKIndex::LowerCase; - ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); - - ui->verticalLayout_2->setStretch(0, 320); - ui->verticalLayout_2->setStretch(1, 400); - - ui->gridLineOSK->setRowStretch(5, 94); - ui->gridBoxOSK->setRowStretch(2, 81); - break; - } - case SwkbdType::NumberPad: { - bottom_osk_index = BottomOSKIndex::NumberPad; - ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); - - ui->verticalLayout_2->setStretch(0, 370); - ui->verticalLayout_2->setStretch(1, 350); - - ui->gridLineOSK->setRowStretch(5, 144); - ui->gridBoxOSK->setRowStretch(2, 131); - break; - } - } -} - -void QtSoftwareKeyboardDialog::SetPasswordMode() { - switch (initialize_parameters.password_mode) { - case SwkbdPasswordMode::Disabled: - default: - ui->line_edit_osk->setEchoMode(QLineEdit::Normal); - break; - case SwkbdPasswordMode::Enabled: - ui->line_edit_osk->setEchoMode(QLineEdit::Password); - break; - } -} - -void QtSoftwareKeyboardDialog::SetTextDrawType() { - switch (initialize_parameters.text_draw_type) { - case SwkbdTextDrawType::Line: - case SwkbdTextDrawType::DownloadCode: { - ui->topOSK->setCurrentIndex(0); - - if (initialize_parameters.max_text_length <= 10) { - ui->gridLineOSK->setColumnStretch(0, 390); - ui->gridLineOSK->setColumnStretch(1, 500); - ui->gridLineOSK->setColumnStretch(2, 390); - } else { - ui->gridLineOSK->setColumnStretch(0, 130); - ui->gridLineOSK->setColumnStretch(1, 1020); - ui->gridLineOSK->setColumnStretch(2, 130); - } - - if (is_inline) { - return; - } - - connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) { - const auto is_valid = ValidateInputText(changed_string); - - const auto text_length = static_cast(changed_string.length()); - - ui->label_characters->setText(QStringLiteral("%1/%2") - .arg(text_length) - .arg(initialize_parameters.max_text_length)); - - ui->button_ok->setEnabled(is_valid); - ui->button_ok_shift->setEnabled(is_valid); - ui->button_ok_num->setEnabled(is_valid); - - ui->line_edit_osk->setFocus(); - }); - - connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged, - [this](int old_cursor_position, int new_cursor_position) { - ui->button_backspace->setEnabled( - initialize_parameters.enable_backspace_button && new_cursor_position > 0); - ui->button_backspace_shift->setEnabled( - initialize_parameters.enable_backspace_button && new_cursor_position > 0); - ui->button_backspace_num->setEnabled( - initialize_parameters.enable_backspace_button && new_cursor_position > 0); - - ui->line_edit_osk->setFocus(); - }); - - connect( - ui->line_edit_osk, &QLineEdit::returnPressed, this, - [this] { TranslateButtonPress(HIDButton::Plus); }, Qt::QueuedConnection); - - ui->line_edit_osk->setPlaceholderText( - QString::fromStdU16String(initialize_parameters.guide_text)); - ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); - ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length); - ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position); - - ui->label_characters->setText(QStringLiteral("%1/%2") - .arg(initialize_parameters.initial_text.size()) - .arg(initialize_parameters.max_text_length)); - break; - } - case SwkbdTextDrawType::Box: - default: { - ui->topOSK->setCurrentIndex(1); - - if (is_inline) { - return; - } - - connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] { - if (static_cast(ui->text_edit_osk->toPlainText().length()) > - initialize_parameters.max_text_length) { - auto text_cursor = ui->text_edit_osk->textCursor(); - ui->text_edit_osk->setTextCursor(text_cursor); - text_cursor.deletePreviousChar(); - } - - const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText()); - - const auto text_length = static_cast(ui->text_edit_osk->toPlainText().length()); - - ui->label_characters_box->setText(QStringLiteral("%1/%2") - .arg(text_length) - .arg(initialize_parameters.max_text_length)); - - ui->button_ok->setEnabled(is_valid); - ui->button_ok_shift->setEnabled(is_valid); - ui->button_ok_num->setEnabled(is_valid); - - ui->text_edit_osk->setFocus(); - }); - - connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] { - const auto new_cursor_position = ui->text_edit_osk->textCursor().position(); - - ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && - new_cursor_position > 0); - ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && - new_cursor_position > 0); - ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && - new_cursor_position > 0); - - ui->text_edit_osk->setFocus(); - }); - - ui->text_edit_osk->setPlaceholderText( - QString::fromStdU16String(initialize_parameters.guide_text)); - ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); - ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0 - ? QTextCursor::Start - : QTextCursor::End); - - ui->label_characters_box->setText(QStringLiteral("%1/%2") - .arg(initialize_parameters.initial_text.size()) - .arg(initialize_parameters.max_text_length)); - break; - } - } -} - -void QtSoftwareKeyboardDialog::SetControllerImage() { - const auto controller_type = Settings::values.players.GetValue()[8].connected - ? Settings::values.players.GetValue()[8].controller_type - : Settings::values.players.GetValue()[0].controller_type; - - const QString theme = [] { - if (QIcon::themeName().contains(QStringLiteral("dark")) || - QIcon::themeName().contains(QStringLiteral("midnight"))) { - return QStringLiteral("_dark"); - } else { - return QString{}; - } - }(); - - switch (controller_type) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::GameCube: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); - break; - case Settings::ControllerType::DualJoyconDetached: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); - break; - case Settings::ControllerType::LeftJoycon: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") - .arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") - .arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") - .arg(theme)); - break; - case Settings::ControllerType::RightJoycon: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") - .arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") - .arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") - .arg(theme)); - break; - case Settings::ControllerType::Handheld: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); - break; - default: - break; - } -} - -void QtSoftwareKeyboardDialog::DisableKeyboardButtons() { - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - case BottomOSKIndex::UpperCase: - default: { - for (const auto& keys : keyboard_buttons) { - for (const auto& rows : keys) { - for (auto* button : rows) { - if (!button) { - continue; - } - - button->setEnabled(true); - } - } - } - - const auto& key_disable_flags = initialize_parameters.key_disable_flags; - - ui->button_space->setDisabled(key_disable_flags.space); - ui->button_space_shift->setDisabled(key_disable_flags.space); - - ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username); - - ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username); - - ui->button_slash->setDisabled(key_disable_flags.slash); - - ui->button_1->setDisabled(key_disable_flags.numbers); - ui->button_2->setDisabled(key_disable_flags.numbers); - ui->button_3->setDisabled(key_disable_flags.numbers); - ui->button_4->setDisabled(key_disable_flags.numbers); - ui->button_5->setDisabled(key_disable_flags.numbers); - ui->button_6->setDisabled(key_disable_flags.numbers); - ui->button_7->setDisabled(key_disable_flags.numbers); - ui->button_8->setDisabled(key_disable_flags.numbers); - ui->button_9->setDisabled(key_disable_flags.numbers); - ui->button_0->setDisabled(key_disable_flags.numbers); - - ui->button_return->setEnabled(initialize_parameters.enable_return_button); - ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button); - break; - } - case BottomOSKIndex::NumberPad: { - for (const auto& rows : numberpad_buttons) { - for (auto* button : rows) { - if (!button) { - continue; - } - - button->setEnabled(true); - } - } - break; - } - } -} - -void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() { - if (is_inline) { - ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length); - ui->button_ok_shift->setEnabled(current_text.size() >= - initialize_parameters.min_text_length); - ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length); - - ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && - cursor_position > 0); - ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && - cursor_position > 0); - ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && - cursor_position > 0); - } else { - const auto text_length = [this] { - if (ui->topOSK->currentIndex() == 1) { - return static_cast(ui->text_edit_osk->toPlainText().length()); - } else { - return static_cast(ui->line_edit_osk->text().length()); - } - }(); - - const auto normal_cursor_position = [this] { - if (ui->topOSK->currentIndex() == 1) { - return ui->text_edit_osk->textCursor().position(); - } else { - return ui->line_edit_osk->cursorPosition(); - } - }(); - - ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length); - ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length); - ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length); - - ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && - normal_cursor_position > 0); - ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && - normal_cursor_position > 0); - ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && - normal_cursor_position > 0); - } -} - -bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) { - const auto& key_disable_flags = initialize_parameters.key_disable_flags; - - const auto input_text_length = static_cast(input_text.length()); - - if (input_text_length < initialize_parameters.min_text_length || - input_text_length > initialize_parameters.max_text_length) { - return false; - } - - if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) { - return false; - } - - if ((key_disable_flags.at || key_disable_flags.username) && - input_text.contains(QLatin1Char{'@'})) { - return false; - } - - if ((key_disable_flags.percent || key_disable_flags.username) && - input_text.contains(QLatin1Char{'%'})) { - return false; - } - - if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) { - return false; - } - - if ((key_disable_flags.backslash || key_disable_flags.username) && - input_text.contains(QLatin1Char('\\'))) { - return false; - } - - if (key_disable_flags.numbers && - std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) { - return false; - } - - if (bottom_osk_index == BottomOSKIndex::NumberPad && - std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) { - return false; - } - - return true; -} - -void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() { - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - bottom_osk_index = BottomOSKIndex::UpperCase; - ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); - - ui->button_shift_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);" - "\nimage-position: left;")); - - ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); - ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); - break; - case BottomOSKIndex::UpperCase: - if (caps_lock_enabled) { - caps_lock_enabled = false; - - ui->button_shift_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);" - "\nimage-position: left;")); - - ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); - ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); - - ui->label_shift_shift->setText(QStringLiteral("Caps Lock")); - - bottom_osk_index = BottomOSKIndex::LowerCase; - ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); - } else { - caps_lock_enabled = true; - - ui->button_shift_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/osk_button_shift_lock_on.png);" - "\nimage-position: left;")); - - ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); - ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); - - ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off")); - } - break; - case BottomOSKIndex::NumberPad: - default: - break; - } -} - -void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) { - if (button == ui->button_ampersand) { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->insertPlainText(QStringLiteral("&")); - } else { - ui->line_edit_osk->insert(QStringLiteral("&")); - } - return; - } - - if (button == ui->button_return || button == ui->button_return_shift) { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->insertPlainText(QStringLiteral("\n")); - } else { - ui->line_edit_osk->insert(QStringLiteral("\n")); - } - return; - } - - if (button == ui->button_space || button == ui->button_space_shift) { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->insertPlainText(QStringLiteral(" ")); - } else { - ui->line_edit_osk->insert(QStringLiteral(" ")); - } - return; - } - - if (button == ui->button_shift || button == ui->button_shift_shift) { - ChangeBottomOSKIndex(); - return; - } - - if (button == ui->button_backspace || button == ui->button_backspace_shift || - button == ui->button_backspace_num) { - if (ui->topOSK->currentIndex() == 1) { - auto text_cursor = ui->text_edit_osk->textCursor(); - ui->text_edit_osk->setTextCursor(text_cursor); - text_cursor.deletePreviousChar(); - } else { - ui->line_edit_osk->backspace(); - } - return; - } - - if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); - - emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); - return; - } - - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->insertPlainText(button->text()); - } else { - ui->line_edit_osk->insert(button->text()); - } - - // Revert the keyboard to lowercase if the shift key is active. - if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { - // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase - // if bottom_osk_index is UpperCase and caps_lock_enabled is true. - caps_lock_enabled = true; - ChangeBottomOSKIndex(); - } -} - -void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) { - if (!button->isEnabled()) { - return; - } - - if (button == ui->button_ampersand) { - InlineTextInsertString(u"&"); - return; - } - - if (button == ui->button_return || button == ui->button_return_shift) { - InlineTextInsertString(u"\n"); - return; - } - - if (button == ui->button_space || button == ui->button_space_shift) { - InlineTextInsertString(u" "); - return; - } - - if (button == ui->button_shift || button == ui->button_shift_shift) { - ChangeBottomOSKIndex(); - return; - } - - if (button == ui->button_backspace || button == ui->button_backspace_shift || - button == ui->button_backspace_num) { - if (cursor_position <= 0 || current_text.empty()) { - cursor_position = 0; - return; - } - - --cursor_position; - - current_text.erase(cursor_position, 1); - - SetBackspaceOkEnabled(); - - emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); - return; - } - - if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { - emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position); - return; - } - - InlineTextInsertString(button->text().toStdU16String()); - - // Revert the keyboard to lowercase if the shift key is active. - if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { - // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase - // if bottom_osk_index is UpperCase and caps_lock_enabled is true. - caps_lock_enabled = true; - ChangeBottomOSKIndex(); - } -} - -void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) { - if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) { - return; - } - - current_text.insert(cursor_position, string); - - cursor_position += static_cast(string.size()); - - SetBackspaceOkEnabled(); - - emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); -} - -void QtSoftwareKeyboardDialog::SetupMouseHover() { - // setFocus() has a bug where continuously changing focus will cause the focus UI to - // mysteriously disappear. A workaround we have found is using the mouse to hover over - // the buttons to act in place of the button focus. As a result, we will have to set - // a blank cursor when hovering over all the buttons and set a no focus policy so the - // buttons do not stay in focus in addition to the mouse hover. - for (auto* button : all_buttons) { - button->setCursor(QCursor(Qt::BlankCursor)); - button->setFocusPolicy(Qt::NoFocus); - } -} - -template -void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonPressedOnce(button)) { - TranslateButtonPress(button); - } - }; - - (f(T), ...); -} - -template -void QtSoftwareKeyboardDialog::HandleButtonHold() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonHeld(button)) { - TranslateButtonPress(button); - } - }; - - (f(T), ...); -} - -void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { - switch (button) { - case HIDButton::A: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - case BottomOSKIndex::UpperCase: - keyboard_buttons[static_cast(bottom_osk_index)][row][column]->click(); - break; - case BottomOSKIndex::NumberPad: - numberpad_buttons[row][column]->click(); - break; - default: - break; - } - break; - case HIDButton::B: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_backspace->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_backspace_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_backspace_num->click(); - break; - default: - break; - } - break; - case HIDButton::X: - if (is_inline) { - emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); - } else { - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); - - emit SubmitNormalText(SwkbdResult::Cancel, std::move(text)); - } - break; - case HIDButton::Y: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_space->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_space_shift->click(); - break; - case BottomOSKIndex::NumberPad: - default: - break; - } - break; - case HIDButton::LStick: - case HIDButton::RStick: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_shift->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_shift_shift->click(); - break; - case BottomOSKIndex::NumberPad: - default: - break; - } - break; - case HIDButton::L: - MoveTextCursorDirection(Direction::Left); - break; - case HIDButton::R: - MoveTextCursorDirection(Direction::Right); - break; - case HIDButton::Plus: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_ok->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_ok_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_ok_num->click(); - break; - default: - break; - } - break; - case HIDButton::DLeft: - case HIDButton::LStickLeft: - case HIDButton::RStickLeft: - MoveButtonDirection(Direction::Left); - break; - case HIDButton::DUp: - case HIDButton::LStickUp: - case HIDButton::RStickUp: - MoveButtonDirection(Direction::Up); - break; - case HIDButton::DRight: - case HIDButton::LStickRight: - case HIDButton::RStickRight: - MoveButtonDirection(Direction::Right); - break; - case HIDButton::DDown: - case HIDButton::LStickDown: - case HIDButton::RStickDown: - MoveButtonDirection(Direction::Down); - break; - default: - break; - } -} - -void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { - // Changes the row or column index depending on the direction. - auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) { - switch (direction) { - case Direction::Left: - column = (column + max_columns - 1) % max_columns; - break; - case Direction::Up: - row = (row + max_rows - 1) % max_rows; - break; - case Direction::Right: - column = (column + 1) % max_columns; - break; - case Direction::Down: - row = (row + 1) % max_rows; - break; - default: - break; - } - }; - - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - case BottomOSKIndex::UpperCase: { - const auto index = static_cast(bottom_osk_index); - - const auto* const prev_button = keyboard_buttons[index][row][column]; - move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); - auto* curr_button = keyboard_buttons[index][row][column]; - - while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { - move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); - curr_button = keyboard_buttons[index][row][column]; - } - - // This is a workaround for setFocus() randomly not showing focus in the UI - QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); - break; - } - case BottomOSKIndex::NumberPad: { - const auto* const prev_button = numberpad_buttons[row][column]; - move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); - auto* curr_button = numberpad_buttons[row][column]; - - while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { - move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); - curr_button = numberpad_buttons[row][column]; - } - - // This is a workaround for setFocus() randomly not showing focus in the UI - QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); - break; - } - default: - break; - } -} - -void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) { - switch (direction) { - case Direction::Left: - if (is_inline) { - if (cursor_position <= 0) { - cursor_position = 0; - } else { - --cursor_position; - emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); - } - } else { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->moveCursor(QTextCursor::Left); - } else { - ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1); - } - } - break; - case Direction::Right: - if (is_inline) { - if (cursor_position >= static_cast(current_text.size())) { - cursor_position = static_cast(current_text.size()); - } else { - ++cursor_position; - emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); - } - } else { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->moveCursor(QTextCursor::Right); - } else { - ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1); - } - } - break; - default: - break; - } -} - -void QtSoftwareKeyboardDialog::StartInputThread() { - if (input_thread_running) { - return; - } - - input_thread_running = true; - - input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this); -} - -void QtSoftwareKeyboardDialog::StopInputThread() { - input_thread_running = false; - - if (input_thread.joinable()) { - input_thread.join(); - } - - if (input_interpreter) { - input_interpreter->ResetButtonStates(); - } -} - -void QtSoftwareKeyboardDialog::InputThread() { - while (input_thread_running) { - input_interpreter->PollInput(); - - HandleButtonPressedOnce(); - - HandleButtonHold(); - - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } -} - -QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { - connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window, - &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window, - &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this, - &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this, - &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection); -} - -QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; - -void QtSoftwareKeyboard::InitializeKeyboard( - bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, - std::function submit_normal_callback_, - std::function - submit_inline_callback_) { - if (is_inline) { - submit_inline_callback = std::move(submit_inline_callback_); - } else { - submit_normal_callback = std::move(submit_normal_callback_); - } - - LOG_INFO(Service_AM, - "\nKeyboardInitializeParameters:" - "\nok_text={}" - "\nheader_text={}" - "\nsub_text={}" - "\nguide_text={}" - "\ninitial_text={}" - "\nmax_text_length={}" - "\nmin_text_length={}" - "\ninitial_cursor_position={}" - "\ntype={}" - "\npassword_mode={}" - "\ntext_draw_type={}" - "\nkey_disable_flags={}" - "\nuse_blur_background={}" - "\nenable_backspace_button={}" - "\nenable_return_button={}" - "\ndisable_cancel_button={}", - Common::UTF16ToUTF8(initialize_parameters.ok_text), - Common::UTF16ToUTF8(initialize_parameters.header_text), - Common::UTF16ToUTF8(initialize_parameters.sub_text), - Common::UTF16ToUTF8(initialize_parameters.guide_text), - Common::UTF16ToUTF8(initialize_parameters.initial_text), - initialize_parameters.max_text_length, initialize_parameters.min_text_length, - initialize_parameters.initial_cursor_position, initialize_parameters.type, - initialize_parameters.password_mode, initialize_parameters.text_draw_type, - initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background, - initialize_parameters.enable_backspace_button, - initialize_parameters.enable_return_button, - initialize_parameters.disable_cancel_button); - - emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters)); -} - -void QtSoftwareKeyboard::ShowNormalKeyboard() const { - emit MainWindowShowNormalKeyboard(); -} - -void QtSoftwareKeyboard::ShowTextCheckDialog( - Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) const { - emit MainWindowShowTextCheckDialog(text_check_result, std::move(text_check_message)); -} - -void QtSoftwareKeyboard::ShowInlineKeyboard( - Core::Frontend::InlineAppearParameters appear_parameters) const { - LOG_INFO(Service_AM, - "\nInlineAppearParameters:" - "\nmax_text_length={}" - "\nmin_text_length={}" - "\nkey_top_scale_x={}" - "\nkey_top_scale_y={}" - "\nkey_top_translate_x={}" - "\nkey_top_translate_y={}" - "\ntype={}" - "\nkey_disable_flags={}" - "\nkey_top_as_floating={}" - "\nenable_backspace_button={}" - "\nenable_return_button={}" - "\ndisable_cancel_button={}", - appear_parameters.max_text_length, appear_parameters.min_text_length, - appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, - appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, - appear_parameters.type, appear_parameters.key_disable_flags.raw, - appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, - appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); - - emit MainWindowShowInlineKeyboard(std::move(appear_parameters)); -} - -void QtSoftwareKeyboard::HideInlineKeyboard() const { - emit MainWindowHideInlineKeyboard(); -} - -void QtSoftwareKeyboard::InlineTextChanged( - Core::Frontend::InlineTextParameters text_parameters) const { - LOG_INFO(Service_AM, - "\nInlineTextParameters:" - "\ninput_text={}" - "\ncursor_position={}", - Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); - - emit MainWindowInlineTextChanged(std::move(text_parameters)); -} - -void QtSoftwareKeyboard::ExitKeyboard() const { - emit MainWindowExitKeyboard(); -} - -void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result, - std::u16string submitted_text) const { - submit_normal_callback(result, submitted_text); -} - -void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, - std::u16string submitted_text, - s32 cursor_position) const { - submit_inline_callback(reply_type, submitted_text, cursor_position); -} diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h deleted file mode 100644 index 1a03c098c..000000000 --- a/src/yuzu/applets/software_keyboard.h +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include - -#include -#include - -#include "core/frontend/applets/software_keyboard.h" - -enum class HIDButton : u8; - -class InputInterpreter; - -namespace Core { -class System; -} - -namespace Ui { -class QtSoftwareKeyboardDialog; -} - -class GMainWindow; - -class QtSoftwareKeyboardDialog final : public QDialog { - Q_OBJECT - -public: - QtSoftwareKeyboardDialog(QWidget* parent, Core::System& system_, bool is_inline_, - Core::Frontend::KeyboardInitializeParameters initialize_parameters_); - ~QtSoftwareKeyboardDialog() override; - - void ShowNormalKeyboard(QPoint pos, QSize size); - - void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message); - - void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, - QSize size); - - void HideInlineKeyboard(); - - void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); - - void ExitKeyboard(); - -signals: - void SubmitNormalText(Service::AM::Applets::SwkbdResult result, - std::u16string submitted_text) const; - - void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, - std::u16string submitted_text, s32 cursor_position) const; - -public slots: - void open() override; - void reject() override; - -protected: - /// We override the keyPressEvent for inputting text into the inline software keyboard. - void keyPressEvent(QKeyEvent* event) override; - -private: - enum class Direction { - Left, - Up, - Right, - Down, - }; - - enum class BottomOSKIndex { - LowerCase, - UpperCase, - NumberPad, - }; - - /** - * Moves and resizes the window to a specified position and size. - * - * @param pos Top-left window position - * @param size Window size - */ - void MoveAndResizeWindow(QPoint pos, QSize size); - - /** - * Rescales all keyboard elements to account for High DPI displays. - * - * @param width Window width - * @param height Window height - * @param dpi_scale Display scaling factor - */ - void RescaleKeyboardElements(float width, float height, float dpi_scale); - - /// Sets the keyboard type based on initialize_parameters. - void SetKeyboardType(); - - /// Sets the password mode based on initialize_parameters. - void SetPasswordMode(); - - /// Sets the text draw type based on initialize_parameters. - void SetTextDrawType(); - - /// Sets the controller image at the bottom left of the software keyboard. - void SetControllerImage(); - - /// Disables buttons based on initialize_parameters. - void DisableKeyboardButtons(); - - /// Changes whether the backspace or/and ok buttons should be enabled or disabled. - void SetBackspaceOkEnabled(); - - /** - * Validates the input text sent in based on the parameters in initialize_parameters. - * - * @param input_text Input text - * - * @returns True if the input text is valid, false otherwise. - */ - bool ValidateInputText(const QString& input_text); - - /// Switches between LowerCase and UpperCase (Shift and Caps Lock) - void ChangeBottomOSKIndex(); - - /// Processes a keyboard button click from the UI as normal keyboard input. - void NormalKeyboardButtonClicked(QPushButton* button); - - /// Processes a keyboard button click from the UI as inline keyboard input. - void InlineKeyboardButtonClicked(QPushButton* button); - - /** - * Inserts a string of arbitrary length into the current_text at the current cursor position. - * This is only used for the inline software keyboard. - */ - void InlineTextInsertString(std::u16string_view string); - - /// Setup the mouse hover workaround for "focusing" buttons. This should only be called once. - void SetupMouseHover(); - - /** - * Handles button presses and converts them into keyboard input. - * - * @tparam HIDButton The list of buttons that can be converted into keyboard input. - */ - template - void HandleButtonPressedOnce(); - - /** - * Handles button holds and converts them into keyboard input. - * - * @tparam HIDButton The list of buttons that can be converted into keyboard input. - */ - template - void HandleButtonHold(); - - /** - * Translates a button press to focus or click a keyboard button. - * - * @param button The button press to process. - */ - void TranslateButtonPress(HIDButton button); - - /** - * Moves the focus of a button in a certain direction. - * - * @param direction The direction to move. - */ - void MoveButtonDirection(Direction direction); - - /** - * Moves the text cursor in a certain direction. - * - * @param direction The direction to move. - */ - void MoveTextCursorDirection(Direction direction); - - void StartInputThread(); - void StopInputThread(); - - /// The thread where input is being polled and processed. - void InputThread(); - - std::unique_ptr ui; - - Core::System& system; - - // True if it is the inline software keyboard. - bool is_inline; - - // Common software keyboard initialize parameters. - Core::Frontend::KeyboardInitializeParameters initialize_parameters; - - // Used only by the inline software keyboard since the QLineEdit or QTextEdit is hidden. - std::u16string current_text; - s32 cursor_position{0}; - - static constexpr std::size_t NUM_ROWS_NORMAL = 5; - static constexpr std::size_t NUM_COLUMNS_NORMAL = 12; - static constexpr std::size_t NUM_ROWS_NUMPAD = 4; - static constexpr std::size_t NUM_COLUMNS_NUMPAD = 4; - - // Stores the normal keyboard layout. - std::array, NUM_ROWS_NORMAL>, 2> - keyboard_buttons; - // Stores the numberpad keyboard layout. - std::array, NUM_ROWS_NUMPAD> numberpad_buttons; - - // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons. - std::array all_buttons; - - std::size_t row{0}; - std::size_t column{0}; - - BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase}; - std::atomic caps_lock_enabled{false}; - - std::unique_ptr input_interpreter; - - std::thread input_thread; - - std::atomic input_thread_running{}; -}; - -class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { - Q_OBJECT - -public: - explicit QtSoftwareKeyboard(GMainWindow& parent); - ~QtSoftwareKeyboard() override; - - void InitializeKeyboard( - bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, - std::function - submit_normal_callback_, - std::function - submit_inline_callback_) override; - - void ShowNormalKeyboard() const override; - - void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) const override; - - void ShowInlineKeyboard( - Core::Frontend::InlineAppearParameters appear_parameters) const override; - - void HideInlineKeyboard() const override; - - void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; - - void ExitKeyboard() const override; - -signals: - void MainWindowInitializeKeyboard( - bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const; - - void MainWindowShowNormalKeyboard() const; - - void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) const; - - void MainWindowShowInlineKeyboard( - Core::Frontend::InlineAppearParameters appear_parameters) const; - - void MainWindowHideInlineKeyboard() const; - - void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const; - - void MainWindowExitKeyboard() const; - -private: - void SubmitNormalText(Service::AM::Applets::SwkbdResult result, - std::u16string submitted_text) const; - - void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, - std::u16string submitted_text, s32 cursor_position) const; - - mutable std::function - submit_normal_callback; - mutable std::function - submit_inline_callback; -}; diff --git a/src/yuzu/applets/software_keyboard.ui b/src/yuzu/applets/software_keyboard.ui deleted file mode 100644 index b0a1fcde9..000000000 --- a/src/yuzu/applets/software_keyboard.ui +++ /dev/null @@ -1,3503 +0,0 @@ - - - QtSoftwareKeyboardDialog - - - - 0 - 0 - 1280 - 720 - - - - Software Keyboard - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - 0 - 100 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 17 - - - - 0/32 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - 26 - 50 - false - - - - Qt::StrongFocus - - - - - - 32 - - - Enter Text - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 127 - 20 - - - - - - - - - 23 - - - - - - - - - - - Qt::Horizontal - - - - 127 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 127 - 20 - - - - - - - - - 17 - - - - - - - - - - - Qt::Horizontal - - - - 127 - 20 - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 17 - - - - 0/500 - - - - - - - - - - - 0 - - - 14 - - - 9 - - - 14 - - - 9 - - - - - - 26 - - - - Qt::StrongFocus - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - - - - - - - - - - - - - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - 0 - - - 2 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Shift - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Cancel - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Enter - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 1 - 1 - - - - - 28 - - - - - - - - - - - - - 1 - 1 - - - - - 28 - - - - ' - - - - - - - - 1 - 1 - - - - - 28 - - - - / - - - - - - - - 1 - 1 - - - - - 28 - - - - ! - - - - - - - - 1 - 1 - - - - - 28 - - - - 7 - - - - - - - - 1 - 1 - - - - - 28 - - - - 8 - - - - - - - - 1 - 1 - - - - - 28 - - - - 0 - - - - - - - - 1 - 1 - - - - - 28 - - - - 9 - - - - - - - - 1 - 1 - - - - - 28 - - - - w - - - - - - - - 1 - 1 - - - - - 28 - - - - r - - - - - - - - 1 - 1 - - - - - 28 - - - - e - - - - - - - - 1 - 1 - - - - - 28 - - - - q - - - - - - - - 1 - 1 - - - - - 28 - - - - u - - - - - - - - 1 - 1 - - - - - 28 - - - - y - - - - - - - - 1 - 1 - - - - - 28 - - - - t - - - - - - - - 1 - 1 - - - - - 28 - - - - o - - - - - - - - 1 - 1 - - - - - 28 - - - - p - - - - - - - - 1 - 1 - - - - - 28 - - - - i - - - - - - - - 1 - 1 - - - - - 28 - - - - a - - - - - - - - 1 - 1 - - - - - 28 - - - - s - - - - - - - - 1 - 1 - - - - - 28 - - - - d - - - - - - - - 1 - 1 - - - - - 28 - - - - f - - - - - - - - 1 - 1 - - - - - 28 - - - - h - - - - - - - - 1 - 1 - - - - - 28 - - - - j - - - - - - - - 1 - 1 - - - - - 28 - - - - g - - - - - - - - 1 - 1 - - - - - 28 - - - - k - - - - - - - - 1 - 1 - - - - - 28 - - - - l - - - - - - - - 1 - 1 - - - - - 28 - - - - : - - - - - - - - 1 - 1 - - - - - 18 - - - - Return - - - - - - - - 1 - 1 - - - - - 18 - - - - OK - - - - - - - - 1 - 1 - - - - - 28 - - - - z - - - - - - - - 1 - 1 - - - - - 28 - - - - c - - - - - - - - 1 - 1 - - - - - 28 - - - - x - - - - - - - - 1 - 1 - - - - - 28 - - - - v - - - - - - - - 1 - 1 - - - - - 28 - - - - m - - - - - - - - 1 - 1 - - - - - 28 - - - - , - - - - - - - - 1 - 1 - - - - - 28 - - - - n - - - - - - - - 1 - 1 - - - - - 28 - - - - b - - - - - - - - 1 - 1 - - - - - 18 - - - - - - - true - - - false - - - - - - - - 1 - 1 - - - - - 28 - - - - ? - - - - - - - - 1 - 1 - - - - - 28 - - - - . - - - - - - - - 1 - 1 - - - - - 28 - - - - 1 - - - - - - - - 1 - 1 - - - - - 28 - - - - 3 - - - - - - - - 1 - 1 - - - - - 28 - - - - 4 - - - - - - - - 1 - 1 - - - - - 28 - - - - 2 - - - - - - - - 1 - 1 - - - - - 28 - - - - 6 - - - - - - - - 1 - 1 - - - - - 28 - - - - 5 - - - - - - - - 1 - 1 - - - - - 18 - - - - Space - - - - - - - - 1 - 1 - - - - - 18 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - 0 - - - 2 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Caps Lock - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Cancel - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Enter - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 1 - 1 - - - - - 28 - - - - _ - - - - - - - - 1 - 1 - - - - - 28 - - - - " - - - - - - - - 1 - 1 - - - - - 28 - - - - @ - - - - - - - - 1 - 1 - - - - - 28 - - - - = - - - - - - - - 1 - 1 - - - - - 28 - - - - && - - - - - - - - 1 - 1 - - - - - 28 - - - - * - - - - - - - - 1 - 1 - - - - - 28 - - - - ) - - - - - - - - 1 - 1 - - - - - 28 - - - - ( - - - - - - - - 1 - 1 - - - - - 28 - - - - W - - - - - - - - 1 - 1 - - - - - 28 - - - - R - - - - - - - - 1 - 1 - - - - - 28 - - - - E - - - - - - - - 1 - 1 - - - - - 28 - - - - Q - - - - - - - - 1 - 1 - - - - - 28 - - - - U - - - - - - - - 1 - 1 - - - - - 28 - - - - Y - - - - - - - - 1 - 1 - - - - - 28 - - - - T - - - - - - - - 1 - 1 - - - - - 28 - - - - O - - - - - - - - 1 - 1 - - - - - 28 - - - - P - - - - - - - - 1 - 1 - - - - - 28 - - - - I - - - - - - - - 1 - 1 - - - - - 28 - - - - A - - - - - - - - 1 - 1 - - - - - 28 - - - - S - - - - - - - - 1 - 1 - - - - - 28 - - - - D - - - - - - - - 1 - 1 - - - - - 28 - - - - F - - - - - - - - 1 - 1 - - - - - 28 - - - - H - - - - - - - - 1 - 1 - - - - - 28 - - - - J - - - - - - - - 1 - 1 - - - - - 28 - - - - G - - - - - - - - 1 - 1 - - - - - 28 - - - - K - - - - - - - - 1 - 1 - - - - - 28 - - - - L - - - - - - - - 1 - 1 - - - - - 28 - - - - ; - - - - - - - - 1 - 1 - - - - - 18 - - - - Return - - - - - - - - 1 - 1 - - - - - 18 - - - - OK - - - - - - - - 1 - 1 - - - - - 28 - - - - Z - - - - - - - - 1 - 1 - - - - - 28 - - - - C - - - - - - - - 1 - 1 - - - - - 28 - - - - X - - - - - - - - 1 - 1 - - - - - 28 - - - - V - - - - - - - - 1 - 1 - - - - - 28 - - - - M - - - - - - - - 1 - 1 - - - - - 28 - - - - < - - - - - - - - 1 - 1 - - - - - 28 - - - - N - - - - - - - - 1 - 1 - - - - - 28 - - - - B - - - - - - - - 1 - 1 - - - - - 18 - - - - - - - true - - - false - - - - - - - - 1 - 1 - - - - - 28 - - - - + - - - - - - - - 1 - 1 - - - - - 28 - - - - > - - - - - - - - 1 - 1 - - - - - 28 - - - - # - - - - - - - - 1 - 1 - - - - - 28 - - - - ] - - - - - - - - 1 - 1 - - - - - 28 - - - - $ - - - - - - - - 1 - 1 - - - - - 28 - - - - [ - - - - - - - - 1 - 1 - - - - - 28 - - - - ^ - - - - - - - - 1 - 1 - - - - - 28 - - - - % - - - - - - - - 1 - 1 - - - - - 18 - - - - Space - - - - - - - - 1 - 1 - - - - - 18 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - 0 - - - - - - 1 - 1 - - - - - 18 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Cancel - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Enter - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 1 - 1 - - - - - 28 - - - - 6 - - - - - - - - 1 - 1 - - - - - 28 - - - - 4 - - - - - - - - 1 - 1 - - - - - 28 - - - - 9 - - - - - - - - 1 - 1 - - - - - 28 - - - - 5 - - - - - - - - 1 - 1 - - - - - 18 - - - - OK - - - - - - - - 1 - 1 - - - - - 28 - - - - 7 - - - - - - - - 1 - 1 - - - - - 28 - - - - 8 - - - - - - - - 1 - 1 - - - - - 28 - - - - 2 - - - - - - - - 1 - 1 - - - - - 28 - - - - 1 - - - - - - - - 1 - 1 - - - - - 28 - - - - 0 - - - - - - - - 1 - 1 - - - - - 28 - - - - 3 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - - - - - - - - - button_1 - button_2 - button_3 - button_4 - button_5 - button_6 - button_7 - button_8 - button_9 - button_0 - button_minus - button_backspace - button_q - button_w - button_e - button_r - button_t - button_y - button_u - button_i - button_o - button_p - button_slash - button_return - button_a - button_s - button_d - button_f - button_g - button_h - button_j - button_k - button_l - button_colon - button_apostrophe - button_z - button_x - button_c - button_v - button_b - button_n - button_m - button_comma - button_dot - button_question - button_exclamation - button_ok - button_shift - button_space - button_hash - button_left_bracket - button_right_bracket - button_dollar - button_percent - button_circumflex - button_ampersand - button_asterisk - button_left_parenthesis - button_right_parenthesis - button_underscore - button_backspace_shift - button_q_shift - button_w_shift - button_e_shift - button_r_shift - button_t_shift - button_y_shift - button_u_shift - button_i_shift - button_o_shift - button_p_shift - button_at - button_return_shift - button_a_shift - button_s_shift - button_d_shift - button_f_shift - button_g_shift - button_h_shift - button_j_shift - button_k_shift - button_l_shift - button_semicolon - button_quotation - button_z_shift - button_x_shift - button_c_shift - button_v_shift - button_b_shift - button_n_shift - button_m_shift - button_less_than - button_greater_than - button_plus - button_equal - button_ok_shift - button_shift_shift - button_space_shift - button_1_num - button_2_num - button_3_num - button_backspace_num - button_4_num - button_5_num - button_6_num - button_ok_num - button_7_num - button_8_num - button_9_num - button_0_num - - - - - - diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp deleted file mode 100644 index 34d3feb55..000000000 --- a/src/yuzu/applets/web_browser.cpp +++ /dev/null @@ -1,417 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#ifdef YUZU_USE_QT_WEB_ENGINE -#include - -#include -#include -#include -#include -#include -#endif - -#include "common/fs/path_util.h" -#include "core/core.h" -#include "core/frontend/input_interpreter.h" -#include "input_common/keyboard.h" -#include "input_common/main.h" -#include "yuzu/applets/web_browser.h" -#include "yuzu/applets/web_browser_scripts.h" -#include "yuzu/main.h" -#include "yuzu/util/url_request_interceptor.h" - -#ifdef YUZU_USE_QT_WEB_ENGINE - -namespace { - -constexpr int HIDButtonToKey(HIDButton button) { - switch (button) { - case HIDButton::DLeft: - case HIDButton::LStickLeft: - return Qt::Key_Left; - case HIDButton::DUp: - case HIDButton::LStickUp: - return Qt::Key_Up; - case HIDButton::DRight: - case HIDButton::LStickRight: - return Qt::Key_Right; - case HIDButton::DDown: - case HIDButton::LStickDown: - return Qt::Key_Down; - default: - return 0; - } -} - -} // Anonymous namespace - -QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system, - InputCommon::InputSubsystem* input_subsystem_) - : QWebEngineView(parent), input_subsystem{input_subsystem_}, - url_interceptor(std::make_unique()), - input_interpreter(std::make_unique(system)), - default_profile{QWebEngineProfile::defaultProfile()}, - global_settings{QWebEngineSettings::globalSettings()} { - QWebEngineScript gamepad; - QWebEngineScript window_nx; - - gamepad.setName(QStringLiteral("gamepad_script.js")); - window_nx.setName(QStringLiteral("window_nx_script.js")); - - gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT)); - window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT)); - - gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation); - window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation); - - gamepad.setWorldId(QWebEngineScript::MainWorld); - window_nx.setWorldId(QWebEngineScript::MainWorld); - - gamepad.setRunsOnSubFrames(true); - window_nx.setRunsOnSubFrames(true); - - default_profile->scripts()->insert(gamepad); - default_profile->scripts()->insert(window_nx); - - default_profile->setRequestInterceptor(url_interceptor.get()); - - global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); - global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); - global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); - global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); - global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true); - global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false); - - global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto")); - - connect( - page(), &QWebEnginePage::windowCloseRequested, page(), - [this] { - if (page()->url() == url_interceptor->GetRequestedURL()) { - SetFinished(true); - SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed); - } - }, - Qt::QueuedConnection); -} - -QtNXWebEngineView::~QtNXWebEngineView() { - SetFinished(true); - StopInputThread(); -} - -void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url, - const std::string& additional_args) { - is_local = true; - - LoadExtractedFonts(); - SetUserAgent(UserAgent::WebApplet); - SetFinished(false); - SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); - SetLastURL("http://localhost/"); - StartInputThread(); - - load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() + - QString::fromStdString(additional_args))); -} - -void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url, - const std::string& additional_args) { - is_local = false; - - SetUserAgent(UserAgent::WebApplet); - SetFinished(false); - SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); - SetLastURL("http://localhost/"); - StartInputThread(); - - load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args))); -} - -void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) { - const QString user_agent_str = [user_agent] { - switch (user_agent) { - case UserAgent::WebApplet: - default: - return QStringLiteral("WebApplet"); - case UserAgent::ShopN: - return QStringLiteral("ShopN"); - case UserAgent::LoginApplet: - return QStringLiteral("LoginApplet"); - case UserAgent::ShareApplet: - return QStringLiteral("ShareApplet"); - case UserAgent::LobbyApplet: - return QStringLiteral("LobbyApplet"); - case UserAgent::WifiWebAuthApplet: - return QStringLiteral("WifiWebAuthApplet"); - } - }(); - - QWebEngineProfile::defaultProfile()->setHttpUserAgent( - QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 " - "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389") - .arg(user_agent_str)); -} - -bool QtNXWebEngineView::IsFinished() const { - return finished; -} - -void QtNXWebEngineView::SetFinished(bool finished_) { - finished = finished_; -} - -Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const { - return exit_reason; -} - -void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) { - exit_reason = exit_reason_; -} - -const std::string& QtNXWebEngineView::GetLastURL() const { - return last_url; -} - -void QtNXWebEngineView::SetLastURL(std::string last_url_) { - last_url = std::move(last_url_); -} - -QString QtNXWebEngineView::GetCurrentURL() const { - return url_interceptor->GetRequestedURL().toString(); -} - -void QtNXWebEngineView::hide() { - SetFinished(true); - StopInputThread(); - - QWidget::hide(); -} - -void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) { - if (is_local) { - input_subsystem->GetKeyboard()->PressKey(event->key()); - } -} - -void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) { - if (is_local) { - input_subsystem->GetKeyboard()->ReleaseKey(event->key()); - } -} - -template -void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonPressedOnce(button)) { - page()->runJavaScript( - QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast(button)), - [&](const QVariant& variant) { - if (variant.toBool()) { - switch (button) { - case HIDButton::A: - SendMultipleKeyPressEvents(); - break; - case HIDButton::B: - SendKeyPressEvent(Qt::Key_B); - break; - case HIDButton::X: - SendKeyPressEvent(Qt::Key_X); - break; - case HIDButton::Y: - SendKeyPressEvent(Qt::Key_Y); - break; - default: - break; - } - } - }); - - page()->runJavaScript( - QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }") - .arg(static_cast(button))); - } - }; - - (f(T), ...); -} - -template -void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonPressedOnce(button)) { - SendKeyPressEvent(HIDButtonToKey(button)); - } - }; - - (f(T), ...); -} - -template -void QtNXWebEngineView::HandleWindowKeyButtonHold() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonHeld(button)) { - SendKeyPressEvent(HIDButtonToKey(button)); - } - }; - - (f(T), ...); -} - -void QtNXWebEngineView::SendKeyPressEvent(int key) { - if (key == 0) { - return; - } - - QCoreApplication::postEvent(focusProxy(), - new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier)); - QCoreApplication::postEvent(focusProxy(), - new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier)); -} - -void QtNXWebEngineView::StartInputThread() { - if (input_thread_running) { - return; - } - - input_thread_running = true; - input_thread = std::thread(&QtNXWebEngineView::InputThread, this); -} - -void QtNXWebEngineView::StopInputThread() { - if (is_local) { - QWidget::releaseKeyboard(); - } - - input_thread_running = false; - if (input_thread.joinable()) { - input_thread.join(); - } -} - -void QtNXWebEngineView::InputThread() { - // Wait for 1 second before allowing any inputs to be processed. - std::this_thread::sleep_for(std::chrono::seconds(1)); - - if (is_local) { - QWidget::grabKeyboard(); - } - - while (input_thread_running) { - input_interpreter->PollInput(); - - HandleWindowFooterButtonPressedOnce(); - - HandleWindowKeyButtonPressedOnce(); - - HandleWindowKeyButtonHold(); - - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } -} - -void QtNXWebEngineView::LoadExtractedFonts() { - QWebEngineScript nx_font_css; - QWebEngineScript load_nx_font; - - auto fonts_dir_str = Common::FS::PathToUTF8String( - Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/"); - - std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/'); - - const auto fonts_dir = QString::fromStdString(fonts_dir_str); - - nx_font_css.setName(QStringLiteral("nx_font_css.js")); - load_nx_font.setName(QStringLiteral("load_nx_font.js")); - - nx_font_css.setSourceCode( - QString::fromStdString(NX_FONT_CSS) - .arg(fonts_dir + QStringLiteral("FontStandard.ttf")) - .arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf")) - .arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf")) - .arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf")) - .arg(fonts_dir + QStringLiteral("FontKorean.ttf")) - .arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf")) - .arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf"))); - load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); - - nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); - load_nx_font.setInjectionPoint(QWebEngineScript::Deferred); - - nx_font_css.setWorldId(QWebEngineScript::MainWorld); - load_nx_font.setWorldId(QWebEngineScript::MainWorld); - - nx_font_css.setRunsOnSubFrames(true); - load_nx_font.setRunsOnSubFrames(true); - - default_profile->scripts()->insert(nx_font_css); - default_profile->scripts()->insert(load_nx_font); - - connect( - url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(), - [this] { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT)); - }, - Qt::QueuedConnection); -} - -#endif - -QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { - connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window, - &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this, - &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserClosed, this, - &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection); -} - -QtWebBrowser::~QtWebBrowser() = default; - -void QtWebBrowser::OpenLocalWebPage( - const std::string& local_url, std::function extract_romfs_callback_, - std::function callback_) const { - extract_romfs_callback = std::move(extract_romfs_callback_); - callback = std::move(callback_); - - const auto index = local_url.find('?'); - - if (index == std::string::npos) { - emit MainWindowOpenWebPage(local_url, "", true); - } else { - emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true); - } -} - -void QtWebBrowser::OpenExternalWebPage( - const std::string& external_url, - std::function callback_) const { - callback = std::move(callback_); - - const auto index = external_url.find('?'); - - if (index == std::string::npos) { - emit MainWindowOpenWebPage(external_url, "", false); - } else { - emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), - false); - } -} - -void QtWebBrowser::MainWindowExtractOfflineRomFS() { - extract_romfs_callback(); -} - -void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, - std::string last_url) { - callback(exit_reason, last_url); -} diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h deleted file mode 100644 index 7ad07409f..000000000 --- a/src/yuzu/applets/web_browser.h +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include - -#include - -#ifdef YUZU_USE_QT_WEB_ENGINE -#include -#endif - -#include "core/frontend/applets/web_browser.h" - -enum class HIDButton : u8; - -class GMainWindow; -class InputInterpreter; -class UrlRequestInterceptor; - -namespace Core { -class System; -} - -namespace InputCommon { -class InputSubsystem; -} - -#ifdef YUZU_USE_QT_WEB_ENGINE - -enum class UserAgent { - WebApplet, - ShopN, - LoginApplet, - ShareApplet, - LobbyApplet, - WifiWebAuthApplet, -}; - -class QWebEngineProfile; -class QWebEngineSettings; - -class QtNXWebEngineView : public QWebEngineView { - Q_OBJECT - -public: - explicit QtNXWebEngineView(QWidget* parent, Core::System& system, - InputCommon::InputSubsystem* input_subsystem_); - ~QtNXWebEngineView() override; - - /** - * Loads a HTML document that exists locally. Cannot be used to load external websites. - * - * @param main_url The url to the file. - * @param additional_args Additional arguments appended to the main url. - */ - void LoadLocalWebPage(const std::string& main_url, const std::string& additional_args); - - /** - * Loads an external website. Cannot be used to load local urls. - * - * @param main_url The url to the website. - * @param additional_args Additional arguments appended to the main url. - */ - void LoadExternalWebPage(const std::string& main_url, const std::string& additional_args); - - /** - * Sets the background color of the web page. - * - * @param color The color to set. - */ - void SetBackgroundColor(QColor color); - - /** - * Sets the user agent of the web browser. - * - * @param user_agent The user agent enum. - */ - void SetUserAgent(UserAgent user_agent); - - [[nodiscard]] bool IsFinished() const; - void SetFinished(bool finished_); - - [[nodiscard]] Service::AM::Applets::WebExitReason GetExitReason() const; - void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_); - - [[nodiscard]] const std::string& GetLastURL() const; - void SetLastURL(std::string last_url_); - - /** - * This gets the current URL that has been requested by the webpage. - * This only applies to the main frame. Sub frames and other resources are ignored. - * - * @return Currently requested URL - */ - [[nodiscard]] QString GetCurrentURL() const; - -public slots: - void hide(); - -protected: - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; - -private: - /** - * Handles button presses to execute functions assigned in yuzu_key_callbacks. - * yuzu_key_callbacks contains specialized functions for the buttons in the window footer - * that can be overriden by games to achieve desired functionality. - * - * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks - */ - template - void HandleWindowFooterButtonPressedOnce(); - - /** - * Handles button presses and converts them into keyboard input. - * This should only be used to convert D-Pad or Analog Stick input into arrow keys. - * - * @tparam HIDButton The list of buttons that can be converted into keyboard input. - */ - template - void HandleWindowKeyButtonPressedOnce(); - - /** - * Handles button holds and converts them into keyboard input. - * This should only be used to convert D-Pad or Analog Stick input into arrow keys. - * - * @tparam HIDButton The list of buttons that can be converted into keyboard input. - */ - template - void HandleWindowKeyButtonHold(); - - /** - * Sends a key press event to QWebEngineView. - * - * @param key Qt key code. - */ - void SendKeyPressEvent(int key); - - /** - * Sends multiple key press events to QWebEngineView. - * - * @tparam int Qt key code. - */ - template - void SendMultipleKeyPressEvents() { - (SendKeyPressEvent(T), ...); - } - - void StartInputThread(); - void StopInputThread(); - - /// The thread where input is being polled and processed. - void InputThread(); - - /// Loads the extracted fonts using JavaScript. - void LoadExtractedFonts(); - - InputCommon::InputSubsystem* input_subsystem; - - std::unique_ptr url_interceptor; - - std::unique_ptr input_interpreter; - - std::thread input_thread; - - std::atomic input_thread_running{}; - - std::atomic finished{}; - - Service::AM::Applets::WebExitReason exit_reason{ - Service::AM::Applets::WebExitReason::EndButtonPressed}; - - std::string last_url{"http://localhost/"}; - - bool is_local{}; - - QWebEngineProfile* default_profile; - QWebEngineSettings* global_settings; -}; - -#endif - -class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { - Q_OBJECT - -public: - explicit QtWebBrowser(GMainWindow& parent); - ~QtWebBrowser() override; - - void OpenLocalWebPage(const std::string& local_url, - std::function extract_romfs_callback_, - std::function - callback_) const override; - - void OpenExternalWebPage(const std::string& external_url, - std::function - callback_) const override; - -signals: - void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args, - bool is_local) const; - -private: - void MainWindowExtractOfflineRomFS(); - - void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, - std::string last_url); - - mutable std::function extract_romfs_callback; - - mutable std::function callback; -}; diff --git a/src/yuzu/applets/web_browser_scripts.h b/src/yuzu/applets/web_browser_scripts.h deleted file mode 100644 index 992837a85..000000000 --- a/src/yuzu/applets/web_browser_scripts.h +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -constexpr char NX_FONT_CSS[] = R"( -(function() { - css = document.createElement('style'); - css.type = 'text/css'; - css.id = 'nx_font'; - css.innerText = ` -/* FontStandard */ -@font-face { - font-family: 'FontStandard'; - src: url('%1') format('truetype'); -} - -/* FontChineseSimplified */ -@font-face { - font-family: 'FontChineseSimplified'; - src: url('%2') format('truetype'); -} - -/* FontExtendedChineseSimplified */ -@font-face { - font-family: 'FontExtendedChineseSimplified'; - src: url('%3') format('truetype'); -} - -/* FontChineseTraditional */ -@font-face { - font-family: 'FontChineseTraditional'; - src: url('%4') format('truetype'); -} - -/* FontKorean */ -@font-face { - font-family: 'FontKorean'; - src: url('%5') format('truetype'); -} - -/* FontNintendoExtended */ -@font-face { - font-family: 'NintendoExt003'; - src: url('%6') format('truetype'); -} - -/* FontNintendoExtended2 */ -@font-face { - font-family: 'NintendoExt003'; - src: url('%7') format('truetype'); -} -`; - - document.head.appendChild(css); -})(); -)"; - -constexpr char LOAD_NX_FONT[] = R"( -(function() { - var elements = document.querySelectorAll("*"); - - for (var i = 0; i < elements.length; i++) { - var style = window.getComputedStyle(elements[i], null); - if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") || - style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) { - elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; - } else { - elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; - } - } -})(); -)"; - -constexpr char GAMEPAD_SCRIPT[] = R"( -window.addEventListener("gamepadconnected", function(e) { - console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", - e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length); -}); - -window.addEventListener("gamepaddisconnected", function(e) { - console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id); -}); -)"; - -constexpr char WINDOW_NX_SCRIPT[] = R"( -var end_applet = false; -var yuzu_key_callbacks = []; - -(function() { - class WindowNX { - constructor() { - yuzu_key_callbacks[1] = function() { window.history.back(); }; - yuzu_key_callbacks[2] = function() { window.nx.endApplet(); }; - } - - addEventListener(type, listener, options) { - console.log("nx.addEventListener called, type=%s", type); - - window.addEventListener(type, listener, options); - } - - endApplet() { - console.log("nx.endApplet called"); - - end_applet = true; - } - - playSystemSe(system_se) { - console.log("nx.playSystemSe is not implemented, system_se=%s", system_se); - } - - sendMessage(message) { - console.log("nx.sendMessage is not implemented, message=%s", message); - } - - setCursorScrollSpeed(scroll_speed) { - console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed); - } - } - - class WindowNXFooter { - setAssign(key, label, func, option) { - console.log("nx.footer.setAssign called, key=%s", key); - - switch (key) { - case "A": - yuzu_key_callbacks[0] = func; - break; - case "B": - yuzu_key_callbacks[1] = func; - break; - case "X": - yuzu_key_callbacks[2] = func; - break; - case "Y": - yuzu_key_callbacks[3] = func; - break; - case "L": - yuzu_key_callbacks[6] = func; - break; - case "R": - yuzu_key_callbacks[7] = func; - break; - } - } - - setFixed(kind) { - console.log("nx.footer.setFixed is not implemented, kind=%s", kind); - } - - unsetAssign(key) { - console.log("nx.footer.unsetAssign called, key=%s", key); - - switch (key) { - case "A": - yuzu_key_callbacks[0] = function() {}; - break; - case "B": - yuzu_key_callbacks[1] = function() {}; - break; - case "X": - yuzu_key_callbacks[2] = function() {}; - break; - case "Y": - yuzu_key_callbacks[3] = function() {}; - break; - case "L": - yuzu_key_callbacks[6] = function() {}; - break; - case "R": - yuzu_key_callbacks[7] = function() {}; - break; - } - } - } - - class WindowNXPlayReport { - incrementCounter(counter_id) { - console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id); - } - - setCounterSetIdentifier(counter_id) { - console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id); - } - } - - window.nx = new WindowNX(); - window.nx.footer = new WindowNXFooter(); - window.nx.playReport = new WindowNXPlayReport(); -})(); -)"; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index fbd5001e9..b7fd33ae7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -11,11 +11,11 @@ #endif // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. -#include "applets/controller.h" -#include "applets/error.h" -#include "applets/profile_select.h" -#include "applets/software_keyboard.h" -#include "applets/web_browser.h" +#include "applets/qt_controller.h" +#include "applets/qt_error.h" +#include "applets/qt_profile_select.h" +#include "applets/qt_software_keyboard.h" +#include "applets/qt_web_browser.h" #include "common/nvidia_flags.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" -- cgit v1.2.3 From 79824d7d1b69382571e5c05fe3e66485212d06f5 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 14 Jul 2021 00:38:24 -0400 Subject: applets: Append applet_ prefix to backend applets --- src/core/CMakeLists.txt | 28 +- src/core/frontend/applets/software_keyboard.h | 2 +- src/core/frontend/applets/web_browser.h | 2 +- src/core/hle/service/am/am.cpp | 6 +- .../hle/service/am/applets/applet_controller.cpp | 253 +++++ .../hle/service/am/applets/applet_controller.h | 137 +++ src/core/hle/service/am/applets/applet_error.cpp | 194 ++++ src/core/hle/service/am/applets/applet_error.h | 53 + .../service/am/applets/applet_general_backend.cpp | 255 +++++ .../service/am/applets/applet_general_backend.h | 90 ++ .../service/am/applets/applet_profile_select.cpp | 78 ++ .../hle/service/am/applets/applet_profile_select.h | 59 ++ .../am/applets/applet_software_keyboard.cpp | 1082 ++++++++++++++++++++ .../service/am/applets/applet_software_keyboard.h | 166 +++ .../am/applets/applet_software_keyboard_types.h | 295 ++++++ .../hle/service/am/applets/applet_web_browser.cpp | 474 +++++++++ .../hle/service/am/applets/applet_web_browser.h | 88 ++ .../service/am/applets/applet_web_browser_types.h | 178 ++++ src/core/hle/service/am/applets/applets.cpp | 12 +- src/core/hle/service/am/applets/controller.cpp | 253 ----- src/core/hle/service/am/applets/controller.h | 137 --- src/core/hle/service/am/applets/error.cpp | 194 ---- src/core/hle/service/am/applets/error.h | 53 - .../hle/service/am/applets/general_backend.cpp | 255 ----- src/core/hle/service/am/applets/general_backend.h | 90 -- src/core/hle/service/am/applets/profile_select.cpp | 78 -- src/core/hle/service/am/applets/profile_select.h | 59 -- .../hle/service/am/applets/software_keyboard.cpp | 1082 -------------------- .../hle/service/am/applets/software_keyboard.h | 166 --- .../service/am/applets/software_keyboard_types.h | 295 ------ src/core/hle/service/am/applets/web_browser.cpp | 474 --------- src/core/hle/service/am/applets/web_browser.h | 88 -- src/core/hle/service/am/applets/web_types.h | 178 ---- 33 files changed, 3427 insertions(+), 3427 deletions(-) create mode 100644 src/core/hle/service/am/applets/applet_controller.cpp create mode 100644 src/core/hle/service/am/applets/applet_controller.h create mode 100644 src/core/hle/service/am/applets/applet_error.cpp create mode 100644 src/core/hle/service/am/applets/applet_error.h create mode 100644 src/core/hle/service/am/applets/applet_general_backend.cpp create mode 100644 src/core/hle/service/am/applets/applet_general_backend.h create mode 100644 src/core/hle/service/am/applets/applet_profile_select.cpp create mode 100644 src/core/hle/service/am/applets/applet_profile_select.h create mode 100644 src/core/hle/service/am/applets/applet_software_keyboard.cpp create mode 100644 src/core/hle/service/am/applets/applet_software_keyboard.h create mode 100644 src/core/hle/service/am/applets/applet_software_keyboard_types.h create mode 100644 src/core/hle/service/am/applets/applet_web_browser.cpp create mode 100644 src/core/hle/service/am/applets/applet_web_browser.h create mode 100644 src/core/hle/service/am/applets/applet_web_browser_types.h delete mode 100644 src/core/hle/service/am/applets/controller.cpp delete mode 100644 src/core/hle/service/am/applets/controller.h delete mode 100644 src/core/hle/service/am/applets/error.cpp delete mode 100644 src/core/hle/service/am/applets/error.h delete mode 100644 src/core/hle/service/am/applets/general_backend.cpp delete mode 100644 src/core/hle/service/am/applets/general_backend.h delete mode 100644 src/core/hle/service/am/applets/profile_select.cpp delete mode 100644 src/core/hle/service/am/applets/profile_select.h delete mode 100644 src/core/hle/service/am/applets/software_keyboard.cpp delete mode 100644 src/core/hle/service/am/applets/software_keyboard.h delete mode 100644 src/core/hle/service/am/applets/software_keyboard_types.h delete mode 100644 src/core/hle/service/am/applets/web_browser.cpp delete mode 100644 src/core/hle/service/am/applets/web_browser.h delete mode 100644 src/core/hle/service/am/applets/web_types.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b2b0dbe05..48b16f8f7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -272,22 +272,22 @@ add_library(core STATIC hle/service/am/applet_ae.h hle/service/am/applet_oe.cpp hle/service/am/applet_oe.h + hle/service/am/applets/applet_controller.cpp + hle/service/am/applets/applet_controller.h + hle/service/am/applets/applet_error.cpp + hle/service/am/applets/applet_error.h + hle/service/am/applets/applet_general_backend.cpp + hle/service/am/applets/applet_general_backend.h + hle/service/am/applets/applet_profile_select.cpp + hle/service/am/applets/applet_profile_select.h + hle/service/am/applets/applet_software_keyboard.cpp + hle/service/am/applets/applet_software_keyboard.h + hle/service/am/applets/applet_software_keyboard_types.h + hle/service/am/applets/applet_web_browser.cpp + hle/service/am/applets/applet_web_browser.h + hle/service/am/applets/applet_web_browser_types.h hle/service/am/applets/applets.cpp hle/service/am/applets/applets.h - hle/service/am/applets/controller.cpp - hle/service/am/applets/controller.h - hle/service/am/applets/error.cpp - hle/service/am/applets/error.h - hle/service/am/applets/general_backend.cpp - hle/service/am/applets/general_backend.h - hle/service/am/applets/profile_select.cpp - hle/service/am/applets/profile_select.h - hle/service/am/applets/software_keyboard.cpp - hle/service/am/applets/software_keyboard.h - hle/service/am/applets/software_keyboard_types.h - hle/service/am/applets/web_browser.cpp - hle/service/am/applets/web_browser.h - hle/service/am/applets/web_types.h hle/service/am/idle.cpp hle/service/am/idle.h hle/service/am/omm.cpp diff --git a/src/core/frontend/applets/software_keyboard.h b/src/core/frontend/applets/software_keyboard.h index 506eb35bb..228a548d4 100644 --- a/src/core/frontend/applets/software_keyboard.h +++ b/src/core/frontend/applets/software_keyboard.h @@ -9,7 +9,7 @@ #include "common/common_types.h" -#include "core/hle/service/am/applets/software_keyboard_types.h" +#include "core/hle/service/am/applets/applet_software_keyboard_types.h" namespace Core::Frontend { diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h index d7bd44c27..915dde677 100644 --- a/src/core/frontend/applets/web_browser.h +++ b/src/core/frontend/applets/web_browser.h @@ -7,7 +7,7 @@ #include #include -#include "core/hle/service/am/applets/web_types.h" +#include "core/hle/service/am/applets/applet_web_browser_types.h" namespace Core::Frontend { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 23ebc1138..41f1e6b68 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -24,10 +24,10 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/am/applets/applet_profile_select.h" +#include "core/hle/service/am/applets/applet_software_keyboard.h" +#include "core/hle/service/am/applets/applet_web_browser.h" #include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/am/applets/profile_select.h" -#include "core/hle/service/am/applets/software_keyboard.h" -#include "core/hle/service/am/applets/web_browser.h" #include "core/hle/service/am/idle.h" #include "core/hle/service/am/omm.h" #include "core/hle/service/am/spsm.h" diff --git a/src/core/hle/service/am/applets/applet_controller.cpp b/src/core/hle/service/am/applets/applet_controller.cpp new file mode 100644 index 000000000..12682effe --- /dev/null +++ b/src/core/hle/service/am/applets/applet_controller.cpp @@ -0,0 +1,253 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/controller.h" +#include "core/hle/result.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applet_controller.h" +#include "core/hle/service/hid/controllers/npad.h" + +namespace Service::AM::Applets { + +// This error code (0x183ACA) is thrown when the applet fails to initialize. +[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101}; +// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2. +[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102}; + +static Core::Frontend::ControllerParameters ConvertToFrontendParameters( + ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text, + std::vector identification_colors, std::vector text) { + HID::Controller_NPad::NpadStyleSet npad_style_set; + npad_style_set.raw = private_arg.style_set; + + return { + .min_players = std::max(s8{1}, header.player_count_min), + .max_players = header.player_count_max, + .keep_controllers_connected = header.enable_take_over_connection, + .enable_single_mode = header.enable_single_mode, + .enable_border_color = header.enable_identification_color, + .border_colors = std::move(identification_colors), + .enable_explain_text = enable_text, + .explain_text = std::move(text), + .allow_pro_controller = npad_style_set.fullkey == 1, + .allow_handheld = npad_style_set.handheld == 1, + .allow_dual_joycons = npad_style_set.joycon_dual == 1, + .allow_left_joycon = npad_style_set.joycon_left == 1, + .allow_right_joycon = npad_style_set.joycon_right == 1, + }; +} + +Controller::Controller(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ControllerApplet& frontend_) + : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} + +Controller::~Controller() = default; + +void Controller::Initialize() { + Applet::Initialize(); + + LOG_INFO(Service_HID, "Initializing Controller Applet."); + + LOG_DEBUG(Service_HID, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); + + controller_applet_version = ControllerAppletVersion{common_args.library_version}; + + const auto private_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(private_arg_storage != nullptr); + + const auto& private_arg = private_arg_storage->GetData(); + ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate)); + + std::memcpy(&controller_private_arg, private_arg.data(), private_arg.size()); + ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate), + "Unknown ControllerSupportArgPrivate revision={} with size={}", + controller_applet_version, controller_private_arg.arg_private_size); + + // Some games such as Cave Story+ set invalid values for the ControllerSupportMode. + // Defer to arg_size to set the ControllerSupportMode. + if (controller_private_arg.mode >= ControllerSupportMode::MaxControllerSupportMode) { + switch (controller_private_arg.arg_size) { + case sizeof(ControllerSupportArgOld): + case sizeof(ControllerSupportArgNew): + controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; + break; + case sizeof(ControllerUpdateFirmwareArg): + controller_private_arg.mode = ControllerSupportMode::ShowControllerFirmwareUpdate; + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerPrivateArg mode={} with arg_size={}", + controller_private_arg.mode, controller_private_arg.arg_size); + controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; + break; + } + } + + // Some games such as Cave Story+ set invalid values for the ControllerSupportCaller. + // This is always 0 (Application) except with ShowControllerFirmwareUpdateForSystem. + if (controller_private_arg.caller >= ControllerSupportCaller::MaxControllerSupportCaller) { + if (controller_private_arg.flag_1 && + controller_private_arg.mode == ControllerSupportMode::ShowControllerFirmwareUpdate) { + controller_private_arg.caller = ControllerSupportCaller::System; + } else { + controller_private_arg.caller = ControllerSupportCaller::Application; + } + } + + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: + case ControllerSupportMode::ShowControllerStrapGuide: { + const auto user_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(user_arg_storage != nullptr); + + const auto& user_arg = user_arg_storage->GetData(); + switch (controller_applet_version) { + case ControllerAppletVersion::Version3: + case ControllerAppletVersion::Version4: + case ControllerAppletVersion::Version5: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld)); + std::memcpy(&controller_user_arg_old, user_arg.data(), user_arg.size()); + break; + case ControllerAppletVersion::Version7: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size()); + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}", + controller_applet_version, controller_private_arg.arg_size); + ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + break; + } + break; + } + case ControllerSupportMode::ShowControllerFirmwareUpdate: { + const auto update_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(update_arg_storage != nullptr); + + const auto& update_arg = update_arg_storage->GetData(); + ASSERT(update_arg.size() == sizeof(ControllerUpdateFirmwareArg)); + + std::memcpy(&controller_update_arg, update_arg.data(), update_arg.size()); + break; + } + default: { + UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode); + break; + } + } +} + +bool Controller::TransactionComplete() const { + return complete; +} + +ResultCode Controller::GetStatus() const { + return status; +} + +void Controller::ExecuteInteractive() { + UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); +} + +void Controller::Execute() { + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: { + const auto parameters = [this] { + switch (controller_applet_version) { + case ControllerAppletVersion::Version3: + case ControllerAppletVersion::Version4: + case ControllerAppletVersion::Version5: + return ConvertToFrontendParameters( + controller_private_arg, controller_user_arg_old.header, + controller_user_arg_old.enable_explain_text, + std::vector( + controller_user_arg_old.identification_colors.begin(), + controller_user_arg_old.identification_colors.end()), + std::vector(controller_user_arg_old.explain_text.begin(), + controller_user_arg_old.explain_text.end())); + case ControllerAppletVersion::Version7: + default: + return ConvertToFrontendParameters( + controller_private_arg, controller_user_arg_new.header, + controller_user_arg_new.enable_explain_text, + std::vector( + controller_user_arg_new.identification_colors.begin(), + controller_user_arg_new.identification_colors.end()), + std::vector(controller_user_arg_new.explain_text.begin(), + controller_user_arg_new.explain_text.end())); + } + }(); + + is_single_mode = parameters.enable_single_mode; + + LOG_DEBUG(Service_HID, + "Controller Parameters: min_players={}, max_players={}, " + "keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, " + "enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, " + "allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}", + parameters.min_players, parameters.max_players, + parameters.keep_controllers_connected, parameters.enable_single_mode, + parameters.enable_border_color, parameters.enable_explain_text, + parameters.allow_pro_controller, parameters.allow_handheld, + parameters.allow_dual_joycons, parameters.allow_left_joycon, + parameters.allow_right_joycon); + + frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters); + break; + } + case ControllerSupportMode::ShowControllerStrapGuide: + case ControllerSupportMode::ShowControllerFirmwareUpdate: + UNIMPLEMENTED_MSG("ControllerSupportMode={} is not implemented", + controller_private_arg.mode); + ConfigurationComplete(); + break; + default: { + ConfigurationComplete(); + break; + } + } +} + +void Controller::ConfigurationComplete() { + ControllerSupportResultInfo result_info{}; + + const auto& players = Settings::values.players.GetValue(); + + // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters. + // Otherwise, only count connected players from P1-P8. + result_info.player_count = + is_single_mode + ? 1 + : static_cast(std::count_if(players.begin(), players.end() - 2, + [](const auto& player) { return player.connected; })); + + result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance( + players.begin(), std::find_if(players.begin(), players.end(), + [](const auto& player) { return player.connected; }))); + + result_info.result = 0; + + LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}", + result_info.player_count, result_info.selected_id, result_info.result); + + complete = true; + out_data = std::vector(sizeof(ControllerSupportResultInfo)); + std::memcpy(out_data.data(), &result_info, out_data.size()); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_controller.h b/src/core/hle/service/am/applets/applet_controller.h new file mode 100644 index 000000000..20617e91f --- /dev/null +++ b/src/core/hle/service/am/applets/applet_controller.h @@ -0,0 +1,137 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Applets { + +using IdentificationColor = std::array; +using ExplainText = std::array; + +enum class ControllerAppletVersion : u32_le { + Version3 = 0x3, // 1.0.0 - 2.3.0 + Version4 = 0x4, // 3.0.0 - 5.1.0 + Version5 = 0x5, // 6.0.0 - 7.0.1 + Version7 = 0x7, // 8.0.0+ +}; + +enum class ControllerSupportMode : u8 { + ShowControllerSupport, + ShowControllerStrapGuide, + ShowControllerFirmwareUpdate, + + MaxControllerSupportMode, +}; + +enum class ControllerSupportCaller : u8 { + Application, + System, + + MaxControllerSupportCaller, +}; + +struct ControllerSupportArgPrivate { + u32 arg_private_size{}; + u32 arg_size{}; + bool flag_0{}; + bool flag_1{}; + ControllerSupportMode mode{}; + ControllerSupportCaller caller{}; + u32 style_set{}; + u32 joy_hold_type{}; +}; +static_assert(sizeof(ControllerSupportArgPrivate) == 0x14, + "ControllerSupportArgPrivate has incorrect size."); + +struct ControllerSupportArgHeader { + s8 player_count_min{}; + s8 player_count_max{}; + bool enable_take_over_connection{}; + bool enable_left_justify{}; + bool enable_permit_joy_dual{}; + bool enable_single_mode{}; + bool enable_identification_color{}; +}; +static_assert(sizeof(ControllerSupportArgHeader) == 0x7, + "ControllerSupportArgHeader has incorrect size."); + +// LibraryAppletVersion 0x3, 0x4, 0x5 +struct ControllerSupportArgOld { + ControllerSupportArgHeader header{}; + std::array identification_colors{}; + bool enable_explain_text{}; + std::array explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgOld) == 0x21C, + "ControllerSupportArgOld has incorrect size."); + +// LibraryAppletVersion 0x7 +struct ControllerSupportArgNew { + ControllerSupportArgHeader header{}; + std::array identification_colors{}; + bool enable_explain_text{}; + std::array explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgNew) == 0x430, + "ControllerSupportArgNew has incorrect size."); + +struct ControllerUpdateFirmwareArg { + bool enable_force_update{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(ControllerUpdateFirmwareArg) == 0x4, + "ControllerUpdateFirmwareArg has incorrect size."); + +struct ControllerSupportResultInfo { + s8 player_count{}; + INSERT_PADDING_BYTES(3); + u32 selected_id{}; + u32 result{}; +}; +static_assert(sizeof(ControllerSupportResultInfo) == 0xC, + "ControllerSupportResultInfo has incorrect size."); + +class Controller final : public Applet { +public: + explicit Controller(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ControllerApplet& frontend_); + ~Controller() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void ConfigurationComplete(); + +private: + const Core::Frontend::ControllerApplet& frontend; + Core::System& system; + + ControllerAppletVersion controller_applet_version; + ControllerSupportArgPrivate controller_private_arg; + ControllerSupportArgOld controller_user_arg_old; + ControllerSupportArgNew controller_user_arg_new; + ControllerUpdateFirmwareArg controller_update_arg; + bool complete{false}; + ResultCode status{ResultSuccess}; + bool is_single_mode{false}; + std::vector out_data; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp new file mode 100644 index 000000000..ef6854d62 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_error.cpp @@ -0,0 +1,194 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/error.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applet_error.h" +#include "core/reporter.h" + +namespace Service::AM::Applets { + +#pragma pack(push, 4) +struct ShowError { + u8 mode; + bool jump; + INSERT_PADDING_BYTES_NOINIT(4); + bool use_64bit_error_code; + INSERT_PADDING_BYTES_NOINIT(1); + u64 error_code_64; + u32 error_code_32; +}; +static_assert(sizeof(ShowError) == 0x14, "ShowError has incorrect size."); +#pragma pack(pop) + +struct ShowErrorRecord { + u8 mode; + bool jump; + INSERT_PADDING_BYTES_NOINIT(6); + u64 error_code_64; + u64 posix_time; +}; +static_assert(sizeof(ShowErrorRecord) == 0x18, "ShowErrorRecord has incorrect size."); + +struct SystemErrorArg { + u8 mode; + bool jump; + INSERT_PADDING_BYTES_NOINIT(6); + u64 error_code_64; + std::array language_code; + std::array main_text; + std::array detail_text; +}; +static_assert(sizeof(SystemErrorArg) == 0x1018, "SystemErrorArg has incorrect size."); + +struct ApplicationErrorArg { + u8 mode; + bool jump; + INSERT_PADDING_BYTES_NOINIT(6); + u32 error_code; + std::array language_code; + std::array main_text; + std::array detail_text; +}; +static_assert(sizeof(ApplicationErrorArg) == 0x1014, "ApplicationErrorArg has incorrect size."); + +union Error::ErrorArguments { + ShowError error; + ShowErrorRecord error_record; + SystemErrorArg system_error; + ApplicationErrorArg application_error; + std::array raw{}; +}; + +namespace { +template +void CopyArgumentData(const std::vector& data, T& variable) { + ASSERT(data.size() >= sizeof(T)); + std::memcpy(&variable, data.data(), sizeof(T)); +} + +ResultCode Decode64BitError(u64 error) { + const auto description = (error >> 32) & 0x1FFF; + auto module = error & 0x3FF; + if (module >= 2000) + module -= 2000; + module &= 0x1FF; + return {static_cast(module), static_cast(description)}; +} + +} // Anonymous namespace + +Error::Error(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ErrorApplet& frontend_) + : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} + +Error::~Error() = default; + +void Error::Initialize() { + Applet::Initialize(); + args = std::make_unique(); + complete = false; + + const auto storage = broker.PopNormalDataToApplet(); + ASSERT(storage != nullptr); + const auto data = storage->GetData(); + + ASSERT(!data.empty()); + std::memcpy(&mode, data.data(), sizeof(ErrorAppletMode)); + + switch (mode) { + case ErrorAppletMode::ShowError: + CopyArgumentData(data, args->error); + if (args->error.use_64bit_error_code) { + error_code = Decode64BitError(args->error.error_code_64); + } else { + error_code = ResultCode(args->error.error_code_32); + } + break; + case ErrorAppletMode::ShowSystemError: + CopyArgumentData(data, args->system_error); + error_code = ResultCode(Decode64BitError(args->system_error.error_code_64)); + break; + case ErrorAppletMode::ShowApplicationError: + CopyArgumentData(data, args->application_error); + error_code = ResultCode(args->application_error.error_code); + break; + case ErrorAppletMode::ShowErrorRecord: + CopyArgumentData(data, args->error_record); + error_code = Decode64BitError(args->error_record.error_code_64); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); + } +} + +bool Error::TransactionComplete() const { + return complete; +} + +ResultCode Error::GetStatus() const { + return ResultSuccess; +} + +void Error::ExecuteInteractive() { + UNREACHABLE_MSG("Unexpected interactive applet data!"); +} + +void Error::Execute() { + if (complete) { + return; + } + + const auto callback = [this] { DisplayCompleted(); }; + const auto title_id = system.CurrentProcess()->GetTitleID(); + const auto& reporter{system.GetReporter()}; + + switch (mode) { + case ErrorAppletMode::ShowError: + reporter.SaveErrorReport(title_id, error_code); + frontend.ShowError(error_code, callback); + break; + case ErrorAppletMode::ShowSystemError: + case ErrorAppletMode::ShowApplicationError: { + const auto is_system = mode == ErrorAppletMode::ShowSystemError; + const auto& main_text = + is_system ? args->system_error.main_text : args->application_error.main_text; + const auto& detail_text = + is_system ? args->system_error.detail_text : args->application_error.detail_text; + + 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; + default: + UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); + DisplayCompleted(); + } +} + +void Error::DisplayCompleted() { + complete = true; + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{})); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_error.h b/src/core/hle/service/am/applets/applet_error.h new file mode 100644 index 000000000..8aa9046a5 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_error.h @@ -0,0 +1,53 @@ +// 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" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Applets { + +enum class ErrorAppletMode : u8 { + ShowError = 0, + ShowSystemError = 1, + ShowApplicationError = 2, + ShowEula = 3, + ShowErrorPctl = 4, + ShowErrorRecord = 5, + ShowUpdateEula = 8, +}; + +class Error final : public Applet { +public: + explicit Error(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ErrorApplet& frontend_); + ~Error() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void DisplayCompleted(); + +private: + union ErrorArguments; + + const Core::Frontend::ErrorApplet& frontend; + ResultCode error_code = ResultSuccess; + ErrorAppletMode mode = ErrorAppletMode::ShowError; + std::unique_ptr args; + + bool complete = false; + Core::System& system; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_general_backend.cpp b/src/core/hle/service/am/applets/applet_general_backend.cpp new file mode 100644 index 000000000..0f413f9a0 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_general_backend.cpp @@ -0,0 +1,255 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/assert.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/frontend/applets/general_frontend.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/result.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applet_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::shared_ptr storage = broker.PopNormalDataToApplet(); + for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) { + const auto data = storage->GetData(); + LOG_INFO(Service_AM, + "called (STUBBED), during {} received normal data with size={:08X}, data={}", + prefix, data.size(), Common::HexToString(data)); + } + + storage = broker.PopInteractiveDataToApplet(); + for (; storage != nullptr; storage = broker.PopInteractiveDataToApplet()) { + const auto data = storage->GetData(); + LOG_INFO(Service_AM, + "called (STUBBED), during {} received interactive data with size={:08X}, data={}", + prefix, data.size(), Common::HexToString(data)); + } +} + +Auth::Auth(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::ParentalControlsApplet& frontend_) + : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} + +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 ? ResultSuccess : 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}", + type, arg0, arg1, arg2); + }; + + switch (type) { + case AuthAppletType::ShowParentalAuthentication: { + const auto callback = [this](bool is_successful) { AuthFinished(is_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(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 is_successful) { + successful = is_successful; + + struct Return { + ResultCode result_code; + }; + static_assert(sizeof(Return) == 0x4, "Return (AuthApplet) has incorrect size."); + + Return return_{GetStatus()}; + + std::vector out(sizeof(Return)); + std::memcpy(out.data(), &return_, sizeof(Return)); + + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out))); + broker.SignalStateChanged(); +} + +PhotoViewer::PhotoViewer(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::PhotoViewerApplet& frontend_) + : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} + +PhotoViewer::~PhotoViewer() = default; + +void PhotoViewer::Initialize() { + Applet::Initialize(); + complete = false; + + const auto storage = broker.PopNormalDataToApplet(); + ASSERT(storage != nullptr); + const auto data = storage->GetData(); + ASSERT(!data.empty()); + mode = static_cast(data[0]); +} + +bool PhotoViewer::TransactionComplete() const { + return complete; +} + +ResultCode PhotoViewer::GetStatus() const { + return ResultSuccess; +} + +void PhotoViewer::ExecuteInteractive() { + UNREACHABLE_MSG("Unexpected interactive applet data."); +} + +void PhotoViewer::Execute() { + if (complete) + return; + + const auto callback = [this] { ViewFinished(); }; + switch (mode) { + case PhotoViewerAppletMode::CurrentApp: + frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback); + break; + case PhotoViewerAppletMode::AllApps: + frontend.ShowAllPhotos(callback); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode); + } +} + +void PhotoViewer::ViewFinished() { + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{})); + broker.SignalStateChanged(); +} + +StubApplet::StubApplet(Core::System& system_, AppletId id_, LibraryAppletMode applet_mode_) + : Applet{system_, applet_mode_}, id{id_}, system{system_} {} + +StubApplet::~StubApplet() = default; + +void StubApplet::Initialize() { + LOG_WARNING(Service_AM, "called (STUBBED)"); + Applet::Initialize(); + + const auto data = broker.PeekDataToAppletForDebug(); + system.GetReporter().SaveUnimplementedAppletReport( + static_cast(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"); +} + +bool StubApplet::TransactionComplete() const { + LOG_WARNING(Service_AM, "called (STUBBED)"); + return true; +} + +ResultCode StubApplet::GetStatus() const { + LOG_WARNING(Service_AM, "called (STUBBED)"); + return ResultSuccess; +} + +void StubApplet::ExecuteInteractive() { + LOG_WARNING(Service_AM, "called (STUBBED)"); + LogCurrentStorage(broker, "ExecuteInteractive"); + + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector(0x1000))); + broker.PushInteractiveDataFromApplet( + std::make_shared(system, std::vector(0x1000))); + broker.SignalStateChanged(); +} + +void StubApplet::Execute() { + LOG_WARNING(Service_AM, "called (STUBBED)"); + LogCurrentStorage(broker, "Execute"); + + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector(0x1000))); + broker.PushInteractiveDataFromApplet( + std::make_shared(system, std::vector(0x1000))); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_general_backend.h b/src/core/hle/service/am/applets/applet_general_backend.h new file mode 100644 index 000000000..7496ded88 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_general_backend.h @@ -0,0 +1,90 @@ +// 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/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Applets { + +enum class AuthAppletType : u32 { + ShowParentalAuthentication, + RegisterParentalPasscode, + ChangeParentalPasscode, +}; + +class Auth final : public Applet { +public: + explicit Auth(Core::System& system_, LibraryAppletMode applet_mode_, + 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 is_successful = true); + +private: + Core::Frontend::ParentalControlsApplet& frontend; + Core::System& system; + 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, +}; + +class PhotoViewer final : public Applet { +public: + explicit PhotoViewer(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::PhotoViewerApplet& frontend_); + ~PhotoViewer() override; + + void Initialize() override; + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void ViewFinished(); + +private: + const Core::Frontend::PhotoViewerApplet& frontend; + bool complete = false; + PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; + Core::System& system; +}; + +class StubApplet final : public Applet { +public: + explicit StubApplet(Core::System& system_, AppletId id_, LibraryAppletMode applet_mode_); + ~StubApplet() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + +private: + AppletId id; + Core::System& system; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_profile_select.cpp b/src/core/hle/service/am/applets/applet_profile_select.cpp new file mode 100644 index 000000000..bdc21778e --- /dev/null +++ b/src/core/hle/service/am/applets/applet_profile_select.cpp @@ -0,0 +1,78 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/profile_select.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applet_profile_select.h" + +namespace Service::AM::Applets { + +constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; + +ProfileSelect::ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ProfileSelectApplet& frontend_) + : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} + +ProfileSelect::~ProfileSelect() = default; + +void ProfileSelect::Initialize() { + complete = false; + status = ResultSuccess; + final_data.clear(); + + Applet::Initialize(); + + const auto user_config_storage = broker.PopNormalDataToApplet(); + ASSERT(user_config_storage != nullptr); + const auto& user_config = user_config_storage->GetData(); + + ASSERT(user_config.size() >= sizeof(UserSelectionConfig)); + std::memcpy(&config, user_config.data(), sizeof(UserSelectionConfig)); +} + +bool ProfileSelect::TransactionComplete() const { + return complete; +} + +ResultCode ProfileSelect::GetStatus() const { + return status; +} + +void ProfileSelect::ExecuteInteractive() { + UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); +} + +void ProfileSelect::Execute() { + if (complete) { + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); + return; + } + + frontend.SelectProfile([this](std::optional uuid) { SelectionComplete(uuid); }); +} + +void ProfileSelect::SelectionComplete(std::optional uuid) { + UserSelectionOutput output{}; + + if (uuid.has_value() && uuid->uuid != Common::INVALID_UUID) { + output.result = 0; + output.uuid_selected = uuid->uuid; + } else { + status = ERR_USER_CANCELLED_SELECTION; + output.result = ERR_USER_CANCELLED_SELECTION.raw; + output.uuid_selected = Common::INVALID_UUID; + } + + final_data = std::vector(sizeof(UserSelectionOutput)); + std::memcpy(final_data.data(), &output, final_data.size()); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_profile_select.h b/src/core/hle/service/am/applets/applet_profile_select.h new file mode 100644 index 000000000..8fb76e6c4 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_profile_select.h @@ -0,0 +1,59 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/common_funcs.h" +#include "common/uuid.h" +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Applets { + +struct UserSelectionConfig { + // TODO(DarkLordZach): RE this structure + // It seems to be flags and the like that determine the UI of the applet on the switch... from + // my research this is safe to ignore for now. + INSERT_PADDING_BYTES(0xA0); +}; +static_assert(sizeof(UserSelectionConfig) == 0xA0, "UserSelectionConfig has incorrect size."); + +struct UserSelectionOutput { + u64 result; + u128 uuid_selected; +}; +static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has incorrect size."); + +class ProfileSelect final : public Applet { +public: + explicit ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ProfileSelectApplet& frontend_); + ~ProfileSelect() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void SelectionComplete(std::optional uuid); + +private: + const Core::Frontend::ProfileSelectApplet& frontend; + + UserSelectionConfig config; + bool complete = false; + ResultCode status = ResultSuccess; + std::vector final_data; + Core::System& system; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.cpp b/src/core/hle/service/am/applets/applet_software_keyboard.cpp new file mode 100644 index 000000000..7cae90609 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_software_keyboard.cpp @@ -0,0 +1,1082 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applet_software_keyboard.h" + +namespace Service::AM::Applets { + +namespace { + +// The maximum number of UTF-16 characters that can be input into the swkbd text field. +constexpr u32 DEFAULT_MAX_TEXT_LENGTH = 500; + +constexpr std::size_t REPLY_BASE_SIZE = sizeof(SwkbdState) + sizeof(SwkbdReplyType); +constexpr std::size_t REPLY_UTF8_SIZE = 0x7D4; +constexpr std::size_t REPLY_UTF16_SIZE = 0x3EC; + +constexpr const char* GetTextCheckResultName(SwkbdTextCheckResult text_check_result) { + switch (text_check_result) { + case SwkbdTextCheckResult::Success: + return "Success"; + case SwkbdTextCheckResult::Failure: + return "Failure"; + case SwkbdTextCheckResult::Confirm: + return "Confirm"; + case SwkbdTextCheckResult::Silent: + return "Silent"; + default: + UNIMPLEMENTED_MSG("Unknown TextCheckResult={}", text_check_result); + return "Unknown"; + } +} + +void SetReplyBase(std::vector& reply, SwkbdState state, SwkbdReplyType reply_type) { + std::memcpy(reply.data(), &state, sizeof(SwkbdState)); + std::memcpy(reply.data() + sizeof(SwkbdState), &reply_type, sizeof(SwkbdReplyType)); +} + +} // Anonymous namespace + +SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::SoftwareKeyboardApplet& frontend_) + : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} + +SoftwareKeyboard::~SoftwareKeyboard() = default; + +void SoftwareKeyboard::Initialize() { + Applet::Initialize(); + + LOG_INFO(Service_AM, "Initializing Software Keyboard Applet with LibraryAppletMode={}", + applet_mode); + + LOG_DEBUG(Service_AM, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); + + swkbd_applet_version = SwkbdAppletVersion{common_args.library_version}; + + switch (applet_mode) { + case LibraryAppletMode::AllForeground: + InitializeForeground(); + break; + case LibraryAppletMode::Background: + case LibraryAppletMode::BackgroundIndirectDisplay: + InitializeBackground(applet_mode); + break; + default: + UNREACHABLE_MSG("Invalid LibraryAppletMode={}", applet_mode); + break; + } +} + +bool SoftwareKeyboard::TransactionComplete() const { + return complete; +} + +ResultCode SoftwareKeyboard::GetStatus() const { + return status; +} + +void SoftwareKeyboard::ExecuteInteractive() { + if (complete) { + return; + } + + if (is_background) { + ProcessInlineKeyboardRequest(); + } else { + ProcessTextCheck(); + } +} + +void SoftwareKeyboard::Execute() { + if (complete) { + return; + } + + if (is_background) { + return; + } + + ShowNormalKeyboard(); +} + +void SoftwareKeyboard::SubmitTextNormal(SwkbdResult result, std::u16string submitted_text) { + if (complete) { + return; + } + + if (swkbd_config_common.use_text_check && result == SwkbdResult::Ok) { + SubmitForTextCheck(submitted_text); + } else { + SubmitNormalOutputAndExit(result, submitted_text); + } +} + +void SoftwareKeyboard::SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position) { + if (complete) { + return; + } + + current_text = std::move(submitted_text); + current_cursor_position = cursor_position; + + if (inline_use_utf8) { + switch (reply_type) { + case SwkbdReplyType::ChangedString: + reply_type = SwkbdReplyType::ChangedStringUtf8; + break; + case SwkbdReplyType::MovedCursor: + reply_type = SwkbdReplyType::MovedCursorUtf8; + break; + case SwkbdReplyType::DecidedEnter: + reply_type = SwkbdReplyType::DecidedEnterUtf8; + break; + default: + break; + } + } + + if (use_changed_string_v2) { + switch (reply_type) { + case SwkbdReplyType::ChangedString: + reply_type = SwkbdReplyType::ChangedStringV2; + break; + case SwkbdReplyType::ChangedStringUtf8: + reply_type = SwkbdReplyType::ChangedStringUtf8V2; + break; + default: + break; + } + } + + if (use_moved_cursor_v2) { + switch (reply_type) { + case SwkbdReplyType::MovedCursor: + reply_type = SwkbdReplyType::MovedCursorV2; + break; + case SwkbdReplyType::MovedCursorUtf8: + reply_type = SwkbdReplyType::MovedCursorUtf8V2; + break; + default: + break; + } + } + + SendReply(reply_type); +} + +void SoftwareKeyboard::InitializeForeground() { + LOG_INFO(Service_AM, "Initializing Normal Software Keyboard Applet."); + + is_background = false; + + const auto swkbd_config_storage = broker.PopNormalDataToApplet(); + ASSERT(swkbd_config_storage != nullptr); + + const auto& swkbd_config_data = swkbd_config_storage->GetData(); + ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon)); + + std::memcpy(&swkbd_config_common, swkbd_config_data.data(), sizeof(SwkbdConfigCommon)); + + switch (swkbd_applet_version) { + case SwkbdAppletVersion::Version5: + case SwkbdAppletVersion::Version65542: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld)); + std::memcpy(&swkbd_config_old, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigOld)); + break; + case SwkbdAppletVersion::Version196615: + case SwkbdAppletVersion::Version262152: + case SwkbdAppletVersion::Version327689: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld2)); + std::memcpy(&swkbd_config_old2, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigOld2)); + break; + case SwkbdAppletVersion::Version393227: + case SwkbdAppletVersion::Version524301: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew)); + std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigNew)); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdConfig revision={} with size={}", swkbd_applet_version, + swkbd_config_data.size()); + ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew)); + std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigNew)); + break; + } + + const auto work_buffer_storage = broker.PopNormalDataToApplet(); + ASSERT(work_buffer_storage != nullptr); + + if (swkbd_config_common.initial_string_length == 0) { + InitializeFrontendKeyboard(); + return; + } + + const auto& work_buffer = work_buffer_storage->GetData(); + + std::vector initial_string(swkbd_config_common.initial_string_length); + + std::memcpy(initial_string.data(), + work_buffer.data() + swkbd_config_common.initial_string_offset, + swkbd_config_common.initial_string_length * sizeof(char16_t)); + + initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(initial_string.data(), + initial_string.size()); + + LOG_DEBUG(Service_AM, "\nInitial Text: {}", Common::UTF16ToUTF8(initial_text)); + + InitializeFrontendKeyboard(); +} + +void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mode) { + LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet."); + + is_background = true; + + const auto swkbd_inline_initialize_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(swkbd_inline_initialize_arg_storage != nullptr); + + const auto& swkbd_inline_initialize_arg = swkbd_inline_initialize_arg_storage->GetData(); + ASSERT(swkbd_inline_initialize_arg.size() == sizeof(SwkbdInitializeArg)); + + std::memcpy(&swkbd_initialize_arg, swkbd_inline_initialize_arg.data(), + swkbd_inline_initialize_arg.size()); + + if (swkbd_initialize_arg.library_applet_mode_flag) { + ASSERT(library_applet_mode == LibraryAppletMode::Background); + } else { + ASSERT(library_applet_mode == LibraryAppletMode::BackgroundIndirectDisplay); + } +} + +void SoftwareKeyboard::ProcessTextCheck() { + const auto text_check_storage = broker.PopInteractiveDataToApplet(); + ASSERT(text_check_storage != nullptr); + + const auto& text_check_data = text_check_storage->GetData(); + ASSERT(text_check_data.size() == sizeof(SwkbdTextCheck)); + + SwkbdTextCheck swkbd_text_check; + + std::memcpy(&swkbd_text_check, text_check_data.data(), sizeof(SwkbdTextCheck)); + + std::u16string text_check_message = + swkbd_text_check.text_check_result == SwkbdTextCheckResult::Failure || + swkbd_text_check.text_check_result == SwkbdTextCheckResult::Confirm + ? Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_text_check.text_check_message.data(), + swkbd_text_check.text_check_message.size()) + : u""; + + LOG_INFO(Service_AM, "\nTextCheckResult: {}\nTextCheckMessage: {}", + GetTextCheckResultName(swkbd_text_check.text_check_result), + Common::UTF16ToUTF8(text_check_message)); + + switch (swkbd_text_check.text_check_result) { + case SwkbdTextCheckResult::Success: + SubmitNormalOutputAndExit(SwkbdResult::Ok, current_text); + break; + case SwkbdTextCheckResult::Failure: + ShowTextCheckDialog(SwkbdTextCheckResult::Failure, std::move(text_check_message)); + break; + case SwkbdTextCheckResult::Confirm: + ShowTextCheckDialog(SwkbdTextCheckResult::Confirm, std::move(text_check_message)); + break; + case SwkbdTextCheckResult::Silent: + default: + break; + } +} + +void SoftwareKeyboard::ProcessInlineKeyboardRequest() { + const auto request_data_storage = broker.PopInteractiveDataToApplet(); + ASSERT(request_data_storage != nullptr); + + const auto& request_data = request_data_storage->GetData(); + ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand)); + + SwkbdRequestCommand request_command; + + std::memcpy(&request_command, request_data.data(), sizeof(SwkbdRequestCommand)); + + switch (request_command) { + case SwkbdRequestCommand::Finalize: + RequestFinalize(request_data); + break; + case SwkbdRequestCommand::SetUserWordInfo: + RequestSetUserWordInfo(request_data); + break; + case SwkbdRequestCommand::SetCustomizeDic: + RequestSetCustomizeDic(request_data); + break; + case SwkbdRequestCommand::Calc: + RequestCalc(request_data); + break; + case SwkbdRequestCommand::SetCustomizedDictionaries: + RequestSetCustomizedDictionaries(request_data); + break; + case SwkbdRequestCommand::UnsetCustomizedDictionaries: + RequestUnsetCustomizedDictionaries(request_data); + break; + case SwkbdRequestCommand::SetChangedStringV2Flag: + RequestSetChangedStringV2Flag(request_data); + break; + case SwkbdRequestCommand::SetMovedCursorV2Flag: + RequestSetMovedCursorV2Flag(request_data); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdRequestCommand={}", request_command); + break; + } +} + +void SoftwareKeyboard::SubmitNormalOutputAndExit(SwkbdResult result, + std::u16string submitted_text) { + std::vector out_data(sizeof(SwkbdResult) + STRING_BUFFER_SIZE); + + if (swkbd_config_common.use_utf8) { + std::string utf8_submitted_text = Common::UTF16ToUTF8(submitted_text); + + LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-8 Submitted Text: {}", result, + utf8_submitted_text); + + std::memcpy(out_data.data(), &result, sizeof(SwkbdResult)); + std::memcpy(out_data.data() + sizeof(SwkbdResult), utf8_submitted_text.data(), + utf8_submitted_text.size()); + } else { + LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-16 Submitted Text: {}", result, + Common::UTF16ToUTF8(submitted_text)); + + std::memcpy(out_data.data(), &result, sizeof(SwkbdResult)); + std::memcpy(out_data.data() + sizeof(SwkbdResult), submitted_text.data(), + submitted_text.size() * sizeof(char16_t)); + } + + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); + + ExitKeyboard(); +} + +void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) { + current_text = std::move(submitted_text); + + std::vector out_data(sizeof(u64) + STRING_BUFFER_SIZE); + + if (swkbd_config_common.use_utf8) { + std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text); + const u64 buffer_size = sizeof(u64) + utf8_submitted_text.size(); + + LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size, + utf8_submitted_text); + + std::memcpy(out_data.data(), &buffer_size, sizeof(u64)); + std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(), + utf8_submitted_text.size()); + } else { + const u64 buffer_size = sizeof(u64) + current_text.size() * sizeof(char16_t); + + LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size, + Common::UTF16ToUTF8(current_text)); + + std::memcpy(out_data.data(), &buffer_size, sizeof(u64)); + std::memcpy(out_data.data() + sizeof(u64), current_text.data(), + current_text.size() * sizeof(char16_t)); + } + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(out_data))); +} + +void SoftwareKeyboard::SendReply(SwkbdReplyType reply_type) { + switch (reply_type) { + case SwkbdReplyType::FinishedInitialize: + ReplyFinishedInitialize(); + break; + case SwkbdReplyType::Default: + ReplyDefault(); + break; + case SwkbdReplyType::ChangedString: + ReplyChangedString(); + break; + case SwkbdReplyType::MovedCursor: + ReplyMovedCursor(); + break; + case SwkbdReplyType::MovedTab: + ReplyMovedTab(); + break; + case SwkbdReplyType::DecidedEnter: + ReplyDecidedEnter(); + break; + case SwkbdReplyType::DecidedCancel: + ReplyDecidedCancel(); + break; + case SwkbdReplyType::ChangedStringUtf8: + ReplyChangedStringUtf8(); + break; + case SwkbdReplyType::MovedCursorUtf8: + ReplyMovedCursorUtf8(); + break; + case SwkbdReplyType::DecidedEnterUtf8: + ReplyDecidedEnterUtf8(); + break; + case SwkbdReplyType::UnsetCustomizeDic: + ReplyUnsetCustomizeDic(); + break; + case SwkbdReplyType::ReleasedUserWordInfo: + ReplyReleasedUserWordInfo(); + break; + case SwkbdReplyType::UnsetCustomizedDictionaries: + ReplyUnsetCustomizedDictionaries(); + break; + case SwkbdReplyType::ChangedStringV2: + ReplyChangedStringV2(); + break; + case SwkbdReplyType::MovedCursorV2: + ReplyMovedCursorV2(); + break; + case SwkbdReplyType::ChangedStringUtf8V2: + ReplyChangedStringUtf8V2(); + break; + case SwkbdReplyType::MovedCursorUtf8V2: + ReplyMovedCursorUtf8V2(); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdReplyType={}", reply_type); + ReplyDefault(); + break; + } +} + +void SoftwareKeyboard::ChangeState(SwkbdState state) { + swkbd_state = state; + + ReplyDefault(); +} + +void SoftwareKeyboard::InitializeFrontendKeyboard() { + if (is_background) { + const auto& appear_arg = swkbd_calc_arg.appear_arg; + + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + appear_arg.ok_text.data(), appear_arg.ok_text.size()); + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + const s32 initial_cursor_position = + current_cursor_position > 0 ? current_cursor_position : 0; + + const auto text_draw_type = + max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{std::move(ok_text)}, + .header_text{}, + .sub_text{}, + .guide_text{}, + .initial_text{current_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{appear_arg.type}, + .password_mode{SwkbdPasswordMode::Disabled}, + .text_draw_type{text_draw_type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .use_blur_background{false}, + .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + frontend.InitializeKeyboard( + true, std::move(initialize_parameters), {}, + [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) { + SubmitTextInline(reply_type, submitted_text, cursor_position); + }); + } else { + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size()); + + std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size()); + + std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size()); + + std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size()); + + const u32 max_text_length = + swkbd_config_common.max_text_length > 0 && + swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? swkbd_config_common.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length + ? swkbd_config_common.min_text_length + : 0; + + const s32 initial_cursor_position = [this] { + switch (swkbd_config_common.initial_cursor_position) { + case SwkbdInitialCursorPosition::Start: + default: + return 0; + case SwkbdInitialCursorPosition::End: + return static_cast(initial_text.size()); + } + }(); + + const auto text_draw_type = [this, max_text_length] { + switch (swkbd_config_common.text_draw_type) { + case SwkbdTextDrawType::Line: + default: + return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + case SwkbdTextDrawType::Box: + case SwkbdTextDrawType::DownloadCode: + return swkbd_config_common.text_draw_type; + } + }(); + + const auto enable_return_button = text_draw_type == SwkbdTextDrawType::Box + ? swkbd_config_common.enable_return_button + : false; + + const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227 + ? swkbd_config_new.disable_cancel_button + : false; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{std::move(ok_text)}, + .header_text{std::move(header_text)}, + .sub_text{std::move(sub_text)}, + .guide_text{std::move(guide_text)}, + .initial_text{initial_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{swkbd_config_common.type}, + .password_mode{swkbd_config_common.password_mode}, + .text_draw_type{text_draw_type}, + .key_disable_flags{swkbd_config_common.key_disable_flags}, + .use_blur_background{swkbd_config_common.use_blur_background}, + .enable_backspace_button{true}, + .enable_return_button{enable_return_button}, + .disable_cancel_button{disable_cancel_button}, + }; + + frontend.InitializeKeyboard(false, std::move(initialize_parameters), + [this](SwkbdResult result, std::u16string submitted_text) { + SubmitTextNormal(result, submitted_text); + }, + {}); + } +} + +void SoftwareKeyboard::ShowNormalKeyboard() { + frontend.ShowNormalKeyboard(); +} + +void SoftwareKeyboard::ShowTextCheckDialog(SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + frontend.ShowTextCheckDialog(text_check_result, std::move(text_check_message)); +} + +void SoftwareKeyboard::ShowInlineKeyboard() { + if (swkbd_state != SwkbdState::InitializedIsHidden) { + return; + } + + ChangeState(SwkbdState::InitializedIsAppearing); + + const auto& appear_arg = swkbd_calc_arg.appear_arg; + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + Core::Frontend::InlineAppearParameters appear_parameters{ + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .key_top_scale_x{swkbd_calc_arg.key_top_scale_x}, + .key_top_scale_y{swkbd_calc_arg.key_top_scale_y}, + .key_top_translate_x{swkbd_calc_arg.key_top_translate_x}, + .key_top_translate_y{swkbd_calc_arg.key_top_translate_y}, + .type{appear_arg.type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .key_top_as_floating{swkbd_calc_arg.key_top_as_floating}, + .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + frontend.ShowInlineKeyboard(std::move(appear_parameters)); + + ChangeState(SwkbdState::InitializedIsShown); +} + +void SoftwareKeyboard::HideInlineKeyboard() { + if (swkbd_state != SwkbdState::InitializedIsShown) { + return; + } + + ChangeState(SwkbdState::InitializedIsDisappearing); + + frontend.HideInlineKeyboard(); + + ChangeState(SwkbdState::InitializedIsHidden); +} + +void SoftwareKeyboard::InlineTextChanged() { + Core::Frontend::InlineTextParameters text_parameters{ + .input_text{current_text}, + .cursor_position{current_cursor_position}, + }; + + frontend.InlineTextChanged(std::move(text_parameters)); +} + +void SoftwareKeyboard::ExitKeyboard() { + complete = true; + status = ResultSuccess; + + frontend.ExitKeyboard(); + + broker.SignalStateChanged(); +} + +// Inline Software Keyboard Requests + +void SoftwareKeyboard::RequestFinalize(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: Finalize"); + + ChangeState(SwkbdState::NotInitialized); + + ExitKeyboard(); +} + +void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented."); +} + +void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetCustomizeDic is not implemented."); +} + +void SoftwareKeyboard::RequestCalc(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: Calc"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArg)); + + std::memcpy(&swkbd_calc_arg, request_data.data() + sizeof(SwkbdRequestCommand), + sizeof(SwkbdCalcArg)); + + if (swkbd_calc_arg.flags.set_input_text) { + current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_calc_arg.input_text.data(), swkbd_calc_arg.input_text.size()); + } + + if (swkbd_calc_arg.flags.set_cursor_position) { + current_cursor_position = swkbd_calc_arg.cursor_position; + } + + if (swkbd_calc_arg.flags.set_utf8_mode) { + inline_use_utf8 = swkbd_calc_arg.utf8_mode; + } + + if (swkbd_state <= SwkbdState::InitializedIsHidden && + swkbd_calc_arg.flags.unset_customize_dic) { + ReplyUnsetCustomizeDic(); + } + + if (swkbd_state <= SwkbdState::InitializedIsHidden && + swkbd_calc_arg.flags.unset_user_word_info) { + ReplyReleasedUserWordInfo(); + } + + if (swkbd_state == SwkbdState::NotInitialized && swkbd_calc_arg.flags.set_initialize_arg) { + InitializeFrontendKeyboard(); + + ChangeState(SwkbdState::InitializedIsHidden); + + ReplyFinishedInitialize(); + } + + if (!swkbd_calc_arg.flags.set_initialize_arg && + (swkbd_calc_arg.flags.set_input_text || swkbd_calc_arg.flags.set_cursor_position)) { + InlineTextChanged(); + } + + if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg.flags.appear) { + ShowInlineKeyboard(); + return; + } + + if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg.flags.disappear) { + HideInlineKeyboard(); + return; + } +} + +void SoftwareKeyboard::RequestSetCustomizedDictionaries(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetCustomizedDictionaries is not implemented."); +} + +void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(const std::vector& request_data) { + LOG_WARNING(Service_AM, "(STUBBED) Processing Request: UnsetCustomizedDictionaries"); + + ReplyUnsetCustomizedDictionaries(); +} + +void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: SetChangedStringV2Flag"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1); + + std::memcpy(&use_changed_string_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1); +} + +void SoftwareKeyboard::RequestSetMovedCursorV2Flag(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: SetMovedCursorV2Flag"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1); + + std::memcpy(&use_moved_cursor_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1); +} + +// Inline Software Keyboard Replies + +void SoftwareKeyboard::ReplyFinishedInitialize() { + LOG_DEBUG(Service_AM, "Sending Reply: FinishedInitialize"); + + std::vector reply(REPLY_BASE_SIZE + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::FinishedInitialize); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDefault() { + LOG_DEBUG(Service_AM, "Sending Reply: Default"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::Default); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedString() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedString"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedString); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursor() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursor"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursor); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedTab() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedTab"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedTabArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedTab); + + const SwkbdMovedTabArg moved_tab_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_tab_arg, + sizeof(SwkbdMovedTabArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDecidedEnter() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnter"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdDecidedEnterArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnter); + + const SwkbdDecidedEnterArg decided_enter_arg{ + .text_length{static_cast(current_text.size())}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &decided_enter_arg, + sizeof(SwkbdDecidedEnterArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyDecidedCancel() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedCancel"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedCancel); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyChangedStringUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDecidedEnterUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnterUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdDecidedEnterArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnterUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdDecidedEnterArg decided_enter_arg{ + .text_length{static_cast(current_text.size())}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &decided_enter_arg, + sizeof(SwkbdDecidedEnterArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyUnsetCustomizeDic() { + LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizeDic"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizeDic); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyReleasedUserWordInfo() { + LOG_DEBUG(Service_AM, "Sending Reply: ReleasedUserWordInfo"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ReleasedUserWordInfo); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyUnsetCustomizedDictionaries() { + LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizedDictionaries"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizedDictionaries); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedStringV2() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringV2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringV2); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorV2() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorV2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorV2); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedStringUtf8V2() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8V2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8V2); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorUtf8V2() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8V2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8V2); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.h b/src/core/hle/service/am/applets/applet_software_keyboard.h new file mode 100644 index 000000000..9aef1bf11 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_software_keyboard.h @@ -0,0 +1,166 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applet_software_keyboard_types.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Applets { + +class SoftwareKeyboard final : public Applet { +public: + explicit SoftwareKeyboard(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::SoftwareKeyboardApplet& frontend_); + ~SoftwareKeyboard() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + /** + * Submits the input text to the application. + * If text checking is enabled, the application will verify the input text. + * If use_utf8 is enabled, the input text will be converted to UTF-8 prior to being submitted. + * This should only be used by the normal software keyboard. + * + * @param result SwkbdResult enum + * @param submitted_text UTF-16 encoded string + */ + void SubmitTextNormal(SwkbdResult result, std::u16string submitted_text); + + /** + * Submits the input text to the application. + * If utf8_mode is enabled, the input text will be converted to UTF-8 prior to being submitted. + * This should only be used by the inline software keyboard. + * + * @param reply_type SwkbdReplyType enum + * @param submitted_text UTF-16 encoded string + * @param cursor_position The current position of the text cursor + */ + void SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position); + +private: + /// Initializes the normal software keyboard. + void InitializeForeground(); + + /// Initializes the inline software keyboard. + void InitializeBackground(LibraryAppletMode library_applet_mode); + + /// Processes the text check sent by the application. + void ProcessTextCheck(); + + /// Processes the inline software keyboard request command sent by the application. + void ProcessInlineKeyboardRequest(); + + /// Submits the input text and exits the applet. + void SubmitNormalOutputAndExit(SwkbdResult result, std::u16string submitted_text); + + /// Submits the input text for text checking. + void SubmitForTextCheck(std::u16string submitted_text); + + /// Sends a reply to the application after processing a request command. + void SendReply(SwkbdReplyType reply_type); + + /// Changes the inline keyboard state. + void ChangeState(SwkbdState state); + + /** + * Signals the frontend to initialize the software keyboard with common parameters. + * This initializes either the normal software keyboard or the inline software keyboard + * depending on the state of is_background. + * Note that this does not cause the keyboard to appear. + * Use the respective Show*Keyboard() functions to cause the respective keyboards to appear. + */ + void InitializeFrontendKeyboard(); + + /// Signals the frontend to show the normal software keyboard. + void ShowNormalKeyboard(); + + /// Signals the frontend to show the text check dialog. + void ShowTextCheckDialog(SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + + /// Signals the frontend to show the inline software keyboard. + void ShowInlineKeyboard(); + + /// Signals the frontend to hide the inline software keyboard. + void HideInlineKeyboard(); + + /// Signals the frontend that the current inline keyboard text has changed. + void InlineTextChanged(); + + /// Signals both the frontend and application that the software keyboard is exiting. + void ExitKeyboard(); + + // Inline Software Keyboard Requests + + void RequestFinalize(const std::vector& request_data); + void RequestSetUserWordInfo(const std::vector& request_data); + void RequestSetCustomizeDic(const std::vector& request_data); + void RequestCalc(const std::vector& request_data); + void RequestSetCustomizedDictionaries(const std::vector& request_data); + void RequestUnsetCustomizedDictionaries(const std::vector& request_data); + void RequestSetChangedStringV2Flag(const std::vector& request_data); + void RequestSetMovedCursorV2Flag(const std::vector& request_data); + + // Inline Software Keyboard Replies + + void ReplyFinishedInitialize(); + void ReplyDefault(); + void ReplyChangedString(); + void ReplyMovedCursor(); + void ReplyMovedTab(); + void ReplyDecidedEnter(); + void ReplyDecidedCancel(); + void ReplyChangedStringUtf8(); + void ReplyMovedCursorUtf8(); + void ReplyDecidedEnterUtf8(); + void ReplyUnsetCustomizeDic(); + void ReplyReleasedUserWordInfo(); + void ReplyUnsetCustomizedDictionaries(); + void ReplyChangedStringV2(); + void ReplyMovedCursorV2(); + void ReplyChangedStringUtf8V2(); + void ReplyMovedCursorUtf8V2(); + + Core::Frontend::SoftwareKeyboardApplet& frontend; + Core::System& system; + + SwkbdAppletVersion swkbd_applet_version; + + SwkbdConfigCommon swkbd_config_common; + SwkbdConfigOld swkbd_config_old; + SwkbdConfigOld2 swkbd_config_old2; + SwkbdConfigNew swkbd_config_new; + std::u16string initial_text; + + SwkbdState swkbd_state{SwkbdState::NotInitialized}; + SwkbdInitializeArg swkbd_initialize_arg; + SwkbdCalcArg swkbd_calc_arg; + bool use_changed_string_v2{false}; + bool use_moved_cursor_v2{false}; + bool inline_use_utf8{false}; + s32 current_cursor_position{}; + + std::u16string current_text; + + bool is_background{false}; + + bool complete{false}; + ResultCode status{ResultSuccess}; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_software_keyboard_types.h b/src/core/hle/service/am/applets/applet_software_keyboard_types.h new file mode 100644 index 000000000..21aa8e800 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_software_keyboard_types.h @@ -0,0 +1,295 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::AM::Applets { + +constexpr std::size_t MAX_OK_TEXT_LENGTH = 8; +constexpr std::size_t MAX_HEADER_TEXT_LENGTH = 64; +constexpr std::size_t MAX_SUB_TEXT_LENGTH = 128; +constexpr std::size_t MAX_GUIDE_TEXT_LENGTH = 256; +constexpr std::size_t STRING_BUFFER_SIZE = 0x7D4; + +enum class SwkbdAppletVersion : u32_le { + Version5 = 0x5, // 1.0.0 + Version65542 = 0x10006, // 2.0.0 - 2.3.0 + Version196615 = 0x30007, // 3.0.0 - 3.0.2 + Version262152 = 0x40008, // 4.0.0 - 4.1.0 + Version327689 = 0x50009, // 5.0.0 - 5.1.0 + Version393227 = 0x6000B, // 6.0.0 - 7.0.1 + Version524301 = 0x8000D, // 8.0.0+ +}; + +enum class SwkbdType : u32 { + Normal, + NumberPad, + Qwerty, + Unknown3, + Latin, + SimplifiedChinese, + TraditionalChinese, + Korean, +}; + +enum class SwkbdInitialCursorPosition : u32 { + Start, + End, +}; + +enum class SwkbdPasswordMode : u32 { + Disabled, + Enabled, +}; + +enum class SwkbdTextDrawType : u32 { + Line, + Box, + DownloadCode, +}; + +enum class SwkbdResult : u32 { + Ok, + Cancel, +}; + +enum class SwkbdTextCheckResult : u32 { + Success, + Failure, + Confirm, + Silent, +}; + +enum class SwkbdState : u32 { + NotInitialized = 0x0, + InitializedIsHidden = 0x1, + InitializedIsAppearing = 0x2, + InitializedIsShown = 0x3, + InitializedIsDisappearing = 0x4, +}; + +enum class SwkbdRequestCommand : u32 { + Finalize = 0x4, + SetUserWordInfo = 0x6, + SetCustomizeDic = 0x7, + Calc = 0xA, + SetCustomizedDictionaries = 0xB, + UnsetCustomizedDictionaries = 0xC, + SetChangedStringV2Flag = 0xD, + SetMovedCursorV2Flag = 0xE, +}; + +enum class SwkbdReplyType : u32 { + FinishedInitialize = 0x0, + Default = 0x1, + ChangedString = 0x2, + MovedCursor = 0x3, + MovedTab = 0x4, + DecidedEnter = 0x5, + DecidedCancel = 0x6, + ChangedStringUtf8 = 0x7, + MovedCursorUtf8 = 0x8, + DecidedEnterUtf8 = 0x9, + UnsetCustomizeDic = 0xA, + ReleasedUserWordInfo = 0xB, + UnsetCustomizedDictionaries = 0xC, + ChangedStringV2 = 0xD, + MovedCursorV2 = 0xE, + ChangedStringUtf8V2 = 0xF, + MovedCursorUtf8V2 = 0x10, +}; + +struct SwkbdKeyDisableFlags { + union { + u32 raw{}; + + BitField<1, 1, u32> space; + BitField<2, 1, u32> at; + BitField<3, 1, u32> percent; + BitField<4, 1, u32> slash; + BitField<5, 1, u32> backslash; + BitField<6, 1, u32> numbers; + BitField<7, 1, u32> download_code; + BitField<8, 1, u32> username; + }; +}; +static_assert(sizeof(SwkbdKeyDisableFlags) == 0x4, "SwkbdKeyDisableFlags has incorrect size."); + +struct SwkbdConfigCommon { + SwkbdType type{}; + std::array ok_text{}; + char16_t left_optional_symbol_key{}; + char16_t right_optional_symbol_key{}; + bool use_prediction{}; + INSERT_PADDING_BYTES(1); + SwkbdKeyDisableFlags key_disable_flags{}; + SwkbdInitialCursorPosition initial_cursor_position{}; + std::array header_text{}; + std::array sub_text{}; + std::array guide_text{}; + u32 max_text_length{}; + u32 min_text_length{}; + SwkbdPasswordMode password_mode{}; + SwkbdTextDrawType text_draw_type{}; + bool enable_return_button{}; + bool use_utf8{}; + bool use_blur_background{}; + INSERT_PADDING_BYTES(1); + u32 initial_string_offset{}; + u32 initial_string_length{}; + u32 user_dictionary_offset{}; + u32 user_dictionary_entries{}; + bool use_text_check{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(SwkbdConfigCommon) == 0x3D4, "SwkbdConfigCommon has incorrect size."); + +#pragma pack(push, 4) +// SwkbdAppletVersion 0x5, 0x10006 +struct SwkbdConfigOld { + INSERT_PADDING_WORDS(1); + VAddr text_check_callback{}; +}; +static_assert(sizeof(SwkbdConfigOld) == 0x3E0 - sizeof(SwkbdConfigCommon), + "SwkbdConfigOld has incorrect size."); + +// SwkbdAppletVersion 0x30007, 0x40008, 0x50009 +struct SwkbdConfigOld2 { + INSERT_PADDING_WORDS(1); + VAddr text_check_callback{}; + std::array text_grouping{}; +}; +static_assert(sizeof(SwkbdConfigOld2) == 0x400 - sizeof(SwkbdConfigCommon), + "SwkbdConfigOld2 has incorrect size."); + +// SwkbdAppletVersion 0x6000B, 0x8000D +struct SwkbdConfigNew { + std::array text_grouping{}; + std::array customized_dictionary_set_entries{}; + u8 total_customized_dictionary_set_entries{}; + bool disable_cancel_button{}; + INSERT_PADDING_BYTES(18); +}; +static_assert(sizeof(SwkbdConfigNew) == 0x4C8 - sizeof(SwkbdConfigCommon), + "SwkbdConfigNew has incorrect size."); +#pragma pack(pop) + +struct SwkbdTextCheck { + SwkbdTextCheckResult text_check_result{}; + std::array text_check_message{}; +}; +static_assert(sizeof(SwkbdTextCheck) == 0x7D8, "SwkbdTextCheck has incorrect size."); + +struct SwkbdCalcArgFlags { + union { + u64 raw{}; + + BitField<0, 1, u64> set_initialize_arg; + BitField<1, 1, u64> set_volume; + BitField<2, 1, u64> appear; + BitField<3, 1, u64> set_input_text; + BitField<4, 1, u64> set_cursor_position; + BitField<5, 1, u64> set_utf8_mode; + BitField<6, 1, u64> unset_customize_dic; + BitField<7, 1, u64> disappear; + BitField<8, 1, u64> unknown; + BitField<9, 1, u64> set_key_top_translate_scale; + BitField<10, 1, u64> unset_user_word_info; + BitField<11, 1, u64> set_disable_hardware_keyboard; + }; +}; +static_assert(sizeof(SwkbdCalcArgFlags) == 0x8, "SwkbdCalcArgFlags has incorrect size."); + +struct SwkbdInitializeArg { + u32 unknown{}; + bool library_applet_mode_flag{}; + bool is_above_hos_500{}; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(SwkbdInitializeArg) == 0x8, "SwkbdInitializeArg has incorrect size."); + +struct SwkbdAppearArg { + SwkbdType type{}; + std::array ok_text{}; + char16_t left_optional_symbol_key{}; + char16_t right_optional_symbol_key{}; + bool use_prediction{}; + bool disable_cancel_button{}; + SwkbdKeyDisableFlags key_disable_flags{}; + u32 max_text_length{}; + u32 min_text_length{}; + bool enable_return_button{}; + INSERT_PADDING_BYTES(3); + u32 flags{}; + INSERT_PADDING_WORDS(6); +}; +static_assert(sizeof(SwkbdAppearArg) == 0x48, "SwkbdAppearArg has incorrect size."); + +struct SwkbdCalcArg { + u32 unknown{}; + u16 calc_arg_size{}; + INSERT_PADDING_BYTES(2); + SwkbdCalcArgFlags flags{}; + SwkbdInitializeArg initialize_arg{}; + f32 volume{}; + s32 cursor_position{}; + SwkbdAppearArg appear_arg{}; + std::array input_text{}; + bool utf8_mode{}; + INSERT_PADDING_BYTES(1); + bool enable_backspace_button{}; + INSERT_PADDING_BYTES(3); + bool key_top_as_floating{}; + bool footer_scalable{}; + bool alpha_enabled_in_input_mode{}; + u8 input_mode_fade_type{}; + bool disable_touch{}; + bool disable_hardware_keyboard{}; + INSERT_PADDING_BYTES(8); + f32 key_top_scale_x{}; + f32 key_top_scale_y{}; + f32 key_top_translate_x{}; + f32 key_top_translate_y{}; + f32 key_top_bg_alpha{}; + f32 footer_bg_alpha{}; + f32 balloon_scale{}; + INSERT_PADDING_WORDS(4); + u8 se_group{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(SwkbdCalcArg) == 0x4A0, "SwkbdCalcArg has incorrect size."); + +struct SwkbdChangedStringArg { + u32 text_length{}; + s32 dictionary_start_cursor_position{}; + s32 dictionary_end_cursor_position{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdChangedStringArg) == 0x10, "SwkbdChangedStringArg has incorrect size."); + +struct SwkbdMovedCursorArg { + u32 text_length{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdMovedCursorArg) == 0x8, "SwkbdMovedCursorArg has incorrect size."); + +struct SwkbdMovedTabArg { + u32 text_length{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdMovedTabArg) == 0x8, "SwkbdMovedTabArg has incorrect size."); + +struct SwkbdDecidedEnterArg { + u32 text_length{}; +}; +static_assert(sizeof(SwkbdDecidedEnterArg) == 0x4, "SwkbdDecidedEnterArg has incorrect size."); + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp new file mode 100644 index 000000000..f96f0fee3 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp @@ -0,0 +1,474 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/mode.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.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_vector.h" +#include "core/frontend/applets/web_browser.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/result.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applet_web_browser.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/ns/pl_u.h" + +namespace Service::AM::Applets { + +namespace { + +template +void ParseRawValue(T& value, const std::vector& data) { + static_assert(std::is_trivially_copyable_v, + "It's undefined behavior to use memcpy with non-trivially copyable objects"); + std::memcpy(&value, data.data(), data.size()); +} + +template +T ParseRawValue(const std::vector& data) { + T value; + ParseRawValue(value, data); + return value; +} + +std::string ParseStringValue(const std::vector& data) { + return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast(data.data()), + data.size()); +} + +std::string GetMainURL(const std::string& url) { + const auto index = url.find('?'); + + if (index == std::string::npos) { + return url; + } + + return url.substr(0, index); +} + +WebArgInputTLVMap ReadWebArgs(const std::vector& web_arg, WebArgHeader& web_arg_header) { + std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader)); + + if (web_arg.size() == sizeof(WebArgHeader)) { + return {}; + } + + WebArgInputTLVMap input_tlv_map; + + u64 current_offset = sizeof(WebArgHeader); + + for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) { + if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) { + return input_tlv_map; + } + + WebArgInputTLV input_tlv; + std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV)); + + current_offset += sizeof(WebArgInputTLV); + + if (web_arg.size() < current_offset + input_tlv.arg_data_size) { + return input_tlv_map; + } + + std::vector data(input_tlv.arg_data_size); + std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size); + + current_offset += input_tlv.arg_data_size; + + input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data)); + } + + return input_tlv_map; +} + +FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, + FileSys::ContentRecordType nca_type) { + if (nca_type == FileSys::ContentRecordType::Data) { + const auto nca = + system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type); + + if (nca == nullptr) { + LOG_ERROR(Service_AM, + "NCA of type={} with title_id={:016X} is not found in the System NAND!", + nca_type, title_id); + return FileSys::SystemArchive::SynthesizeSystemArchive(title_id); + } + + return nca->GetRomFS(); + } else { + const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type); + + if (nca == nullptr) { + LOG_ERROR(Service_AM, + "NCA of type={} with title_id={:016X} is not found in the ContentProvider!", + nca_type, title_id); + return nullptr; + } + + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; + + return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); + } +} + +void ExtractSharedFonts(Core::System& system) { + static constexpr std::array DECRYPTED_SHARED_FONTS{ + "FontStandard.ttf", + "FontChineseSimplified.ttf", + "FontExtendedChineseSimplified.ttf", + "FontChineseTraditional.ttf", + "FontKorean.ttf", + "FontNintendoExtended.ttf", + "FontNintendoExtended2.ttf", + }; + + const auto fonts_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts"; + + for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { + const auto font_file_path = fonts_dir / DECRYPTED_SHARED_FONTS[i]; + + if (Common::FS::Exists(font_file_path)) { + continue; + } + + const auto font = NS::SHARED_FONTS[i]; + const auto font_title_id = static_cast(font.first); + + const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry( + font_title_id, FileSys::ContentRecordType::Data); + + FileSys::VirtualFile romfs; + + if (!nca) { + romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id); + } else { + romfs = nca->GetRomFS(); + } + + if (!romfs) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!", + font_title_id); + continue; + } + + const auto extracted_romfs = FileSys::ExtractRomFS(romfs); + + if (!extracted_romfs) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!", + font_title_id); + continue; + } + + const auto font_file = extracted_romfs->GetFile(font.second); + + if (!font_file) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!", + font_title_id, font.second); + continue; + } + + std::vector font_data_u32(font_file->GetSize() / sizeof(u32)); + font_file->ReadBytes(font_data_u32.data(), font_file->GetSize()); + + std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), + Common::swap32); + + std::vector decrypted_data(font_file->GetSize() - 8); + + NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data); + + FileSys::VirtualFile decrypted_font = std::make_shared( + std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); + + const auto temp_dir = system.GetFilesystem()->CreateDirectory( + Common::FS::PathToUTF8String(fonts_dir), FileSys::Mode::ReadWrite); + + const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); + + FileSys::VfsRawCopy(decrypted_font, out_file); + } +} + +} // namespace + +WebBrowser::WebBrowser(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::WebBrowserApplet& frontend_) + : Applet{system_, applet_mode_}, frontend(frontend_), system{system_} {} + +WebBrowser::~WebBrowser() = default; + +void WebBrowser::Initialize() { + Applet::Initialize(); + + LOG_INFO(Service_AM, "Initializing Web Browser Applet."); + + LOG_DEBUG(Service_AM, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); + + web_applet_version = WebAppletVersion{common_args.library_version}; + + const auto web_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(web_arg_storage != nullptr); + + const auto& web_arg = web_arg_storage->GetData(); + ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; }); + + web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header); + + LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}", + web_arg_header.total_tlv_entries, web_arg_header.shim_kind); + + ExtractSharedFonts(system); + + switch (web_arg_header.shim_kind) { + case ShimKind::Shop: + InitializeShop(); + break; + case ShimKind::Login: + InitializeLogin(); + break; + case ShimKind::Offline: + InitializeOffline(); + break; + case ShimKind::Share: + InitializeShare(); + break; + case ShimKind::Web: + InitializeWeb(); + break; + case ShimKind::Wifi: + InitializeWifi(); + break; + case ShimKind::Lobby: + InitializeLobby(); + break; + default: + UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); + break; + } +} + +bool WebBrowser::TransactionComplete() const { + return complete; +} + +ResultCode WebBrowser::GetStatus() const { + return status; +} + +void WebBrowser::ExecuteInteractive() { + UNIMPLEMENTED_MSG("WebSession is not implemented"); +} + +void WebBrowser::Execute() { + switch (web_arg_header.shim_kind) { + case ShimKind::Shop: + ExecuteShop(); + break; + case ShimKind::Login: + ExecuteLogin(); + break; + case ShimKind::Offline: + ExecuteOffline(); + break; + case ShimKind::Share: + ExecuteShare(); + break; + case ShimKind::Web: + ExecuteWeb(); + break; + case ShimKind::Wifi: + ExecuteWifi(); + break; + case ShimKind::Lobby: + ExecuteLobby(); + break; + default: + UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); + WebBrowserExit(WebExitReason::EndButtonPressed); + break; + } +} + +void WebBrowser::ExtractOfflineRomFS() { + LOG_DEBUG(Service_AM, "Extracting RomFS to {}", + Common::FS::PathToUTF8String(offline_cache_dir)); + + const auto extracted_romfs_dir = + FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); + + const auto temp_dir = system.GetFilesystem()->CreateDirectory( + Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite); + + FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); +} + +void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { + if ((web_arg_header.shim_kind == ShimKind::Share && + web_applet_version >= WebAppletVersion::Version196608) || + (web_arg_header.shim_kind == ShimKind::Web && + web_applet_version >= WebAppletVersion::Version524288)) { + // TODO: Push Output TLVs instead of a WebCommonReturnValue + } + + WebCommonReturnValue web_common_return_value; + + web_common_return_value.exit_reason = exit_reason; + std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size()); + web_common_return_value.last_url_size = last_url.size(); + + LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}", + exit_reason, last_url, last_url.size()); + + complete = true; + std::vector out_data(sizeof(WebCommonReturnValue)); + std::memcpy(out_data.data(), &web_common_return_value, out_data.size()); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); + broker.SignalStateChanged(); +} + +bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const { + return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end(); +} + +std::optional> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) { + const auto map_it = web_arg_input_tlv_map.find(input_tlv_type); + + if (map_it == web_arg_input_tlv_map.end()) { + return std::nullopt; + } + + return map_it->second; +} + +void WebBrowser::InitializeShop() {} + +void WebBrowser::InitializeLogin() {} + +void WebBrowser::InitializeOffline() { + const auto document_path = + ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value()); + + const auto document_kind = + ParseRawValue(GetInputTLVData(WebArgInputTLVType::DocumentKind).value()); + + std::string additional_paths; + + switch (document_kind) { + case DocumentKind::OfflineHtmlPage: + default: + title_id = system.CurrentProcess()->GetTitleID(); + nca_type = FileSys::ContentRecordType::HtmlDocument; + additional_paths = "html-document"; + break; + case DocumentKind::ApplicationLegalInformation: + title_id = ParseRawValue(GetInputTLVData(WebArgInputTLVType::ApplicationID).value()); + nca_type = FileSys::ContentRecordType::LegalInformation; + break; + case DocumentKind::SystemDataPage: + title_id = ParseRawValue(GetInputTLVData(WebArgInputTLVType::SystemDataID).value()); + nca_type = FileSys::ContentRecordType::Data; + break; + } + + static constexpr std::array RESOURCE_TYPES{ + "manual", + "legal_information", + "system_data", + }; + + offline_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + fmt::format("offline_web_applet_{}/{:016X}", + RESOURCE_TYPES[static_cast(document_kind) - 1], title_id); + + offline_document = Common::FS::ConcatPathSafe( + offline_cache_dir, fmt::format("{}/{}", additional_paths, document_path)); +} + +void WebBrowser::InitializeShare() {} + +void WebBrowser::InitializeWeb() { + external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value()); +} + +void WebBrowser::InitializeWifi() {} + +void WebBrowser::InitializeLobby() {} + +void WebBrowser::ExecuteShop() { + LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +void WebBrowser::ExecuteLogin() { + LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +void WebBrowser::ExecuteOffline() { + const auto main_url = GetMainURL(Common::FS::PathToUTF8String(offline_document)); + + if (!Common::FS::Exists(main_url)) { + offline_romfs = GetOfflineRomFS(system, title_id, nca_type); + + if (offline_romfs == nullptr) { + LOG_ERROR(Service_AM, + "RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id, + nca_type); + WebBrowserExit(WebExitReason::WindowClosed); + return; + } + } + + LOG_INFO(Service_AM, "Opening offline document at {}", + Common::FS::PathToUTF8String(offline_document)); + + frontend.OpenLocalWebPage( + Common::FS::PathToUTF8String(offline_document), [this] { ExtractOfflineRomFS(); }, + [this](WebExitReason exit_reason, std::string last_url) { + WebBrowserExit(exit_reason, last_url); + }); +} + +void WebBrowser::ExecuteShare() { + LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +void WebBrowser::ExecuteWeb() { + LOG_INFO(Service_AM, "Opening external URL at {}", external_url); + + frontend.OpenExternalWebPage(external_url, + [this](WebExitReason exit_reason, std::string last_url) { + WebBrowserExit(exit_reason, last_url); + }); +} + +void WebBrowser::ExecuteWifi() { + LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +void WebBrowser::ExecuteLobby() { + LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_web_browser.h b/src/core/hle/service/am/applets/applet_web_browser.h new file mode 100644 index 000000000..4f9e81b79 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_web_browser.h @@ -0,0 +1,88 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applet_web_browser_types.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace FileSys { +enum class ContentRecordType : u8; +} + +namespace Service::AM::Applets { + +class WebBrowser final : public Applet { +public: + WebBrowser(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::WebBrowserApplet& frontend_); + + ~WebBrowser() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void ExtractOfflineRomFS(); + + void WebBrowserExit(WebExitReason exit_reason, std::string last_url = ""); + +private: + bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const; + + std::optional> GetInputTLVData(WebArgInputTLVType input_tlv_type); + + // Initializers for the various types of browser applets + void InitializeShop(); + void InitializeLogin(); + void InitializeOffline(); + void InitializeShare(); + void InitializeWeb(); + void InitializeWifi(); + void InitializeLobby(); + + // Executors for the various types of browser applets + void ExecuteShop(); + void ExecuteLogin(); + void ExecuteOffline(); + void ExecuteShare(); + void ExecuteWeb(); + void ExecuteWifi(); + void ExecuteLobby(); + + const Core::Frontend::WebBrowserApplet& frontend; + + bool complete{false}; + ResultCode status{ResultSuccess}; + + WebAppletVersion web_applet_version{}; + WebArgHeader web_arg_header{}; + WebArgInputTLVMap web_arg_input_tlv_map; + + u64 title_id{}; + FileSys::ContentRecordType nca_type{}; + std::filesystem::path offline_cache_dir; + std::filesystem::path offline_document; + FileSys::VirtualFile offline_romfs; + + std::string external_url; + + Core::System& system; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_web_browser_types.h b/src/core/hle/service/am/applets/applet_web_browser_types.h new file mode 100644 index 000000000..419c2bf79 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_web_browser_types.h @@ -0,0 +1,178 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::AM::Applets { + +enum class WebAppletVersion : u32_le { + Version0 = 0x0, // Only used by WifiWebAuthApplet + Version131072 = 0x20000, // 1.0.0 - 2.3.0 + Version196608 = 0x30000, // 3.0.0 - 4.1.0 + Version327680 = 0x50000, // 5.0.0 - 5.1.0 + Version393216 = 0x60000, // 6.0.0 - 7.0.1 + Version524288 = 0x80000, // 8.0.0+ +}; + +enum class ShimKind : u32 { + Shop = 1, + Login = 2, + Offline = 3, + Share = 4, + Web = 5, + Wifi = 6, + Lobby = 7, +}; + +enum class WebExitReason : u32 { + EndButtonPressed = 0, + BackButtonPressed = 1, + ExitRequested = 2, + CallbackURL = 3, + WindowClosed = 4, + ErrorDialog = 7, +}; + +enum class WebArgInputTLVType : u16 { + InitialURL = 0x1, + 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, + PlayReportEnabled = 0x13, + BootDisplayKind = 0x17, + BackgroundKind = 0x18, + FooterEnabled = 0x19, + PointerEnabled = 0x1A, + LeftStickMode = 0x1B, + KeyRepeatFrame1 = 0x1C, + KeyRepeatFrame2 = 0x1D, + BootAsMediaPlayerInverted = 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, + YouTubeVideoWhitelist = 0x31, + FooterFixedKind = 0x32, + PageFadeEnabled = 0x33, + MediaCreatorApplicationRatingAge = 0x34, + BootLoadingIconEnabled = 0x35, + PageScrollIndicatorEnabled = 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, + MediaPlayerOfflineEnabled = 0x43, +}; + +enum class WebArgOutputTLVType : u16 { + ShareExitReason = 0x1, + LastURL = 0x2, + LastURLSize = 0x3, + SharePostResult = 0x4, + PostServiceName = 0x5, + PostServiceNameSize = 0x6, + PostID = 0x7, + PostIDSize = 0x8, + MediaPlayerAutoClosedByCompletion = 0x9, +}; + +enum class DocumentKind : u32 { + OfflineHtmlPage = 1, + ApplicationLegalInformation = 2, + SystemDataPage = 3, +}; + +enum class ShareStartPage : u32 { + Default, + Settings, +}; + +enum class BootDisplayKind : u32 { + Default, + White, + Black, +}; + +enum class BackgroundKind : u32 { + Default, +}; + +enum class LeftStickMode : u32 { + Pointer, + Cursor, +}; + +enum class WebSessionBootMode : u32 { + AllForeground, + AllForegroundInitiallyHidden, +}; + +struct WebArgHeader { + u16 total_tlv_entries{}; + INSERT_PADDING_BYTES(2); + ShimKind shim_kind{}; +}; +static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size."); + +struct WebArgInputTLV { + WebArgInputTLVType input_tlv_type{}; + u16 arg_data_size{}; + INSERT_PADDING_WORDS(1); +}; +static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size."); + +struct WebArgOutputTLV { + WebArgOutputTLVType output_tlv_type{}; + u16 arg_data_size{}; + INSERT_PADDING_WORDS(1); +}; +static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size."); + +struct WebCommonReturnValue { + WebExitReason exit_reason{}; + INSERT_PADDING_WORDS(1); + std::array last_url{}; + u64 last_url_size{}; +}; +static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); + +using WebArgInputTLVMap = std::unordered_map>; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index ae995df6b..2b7685d42 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -17,13 +17,13 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/am/applets/applet_controller.h" +#include "core/hle/service/am/applets/applet_error.h" +#include "core/hle/service/am/applets/applet_general_backend.h" +#include "core/hle/service/am/applets/applet_profile_select.h" +#include "core/hle/service/am/applets/applet_software_keyboard.h" +#include "core/hle/service/am/applets/applet_web_browser.h" #include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/am/applets/controller.h" -#include "core/hle/service/am/applets/error.h" -#include "core/hle/service/am/applets/general_backend.h" -#include "core/hle/service/am/applets/profile_select.h" -#include "core/hle/service/am/applets/software_keyboard.h" -#include "core/hle/service/am/applets/web_browser.h" #include "core/hle/service/sm/sm.h" namespace Service::AM::Applets { diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp deleted file mode 100644 index 218c8d1e4..000000000 --- a/src/core/hle/service/am/applets/controller.cpp +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include - -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/frontend/applets/controller.h" -#include "core/hle/result.h" -#include "core/hle/service/am/am.h" -#include "core/hle/service/am/applets/controller.h" -#include "core/hle/service/hid/controllers/npad.h" - -namespace Service::AM::Applets { - -// This error code (0x183ACA) is thrown when the applet fails to initialize. -[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101}; -// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2. -[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102}; - -static Core::Frontend::ControllerParameters ConvertToFrontendParameters( - ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text, - std::vector identification_colors, std::vector text) { - HID::Controller_NPad::NpadStyleSet npad_style_set; - npad_style_set.raw = private_arg.style_set; - - return { - .min_players = std::max(s8{1}, header.player_count_min), - .max_players = header.player_count_max, - .keep_controllers_connected = header.enable_take_over_connection, - .enable_single_mode = header.enable_single_mode, - .enable_border_color = header.enable_identification_color, - .border_colors = std::move(identification_colors), - .enable_explain_text = enable_text, - .explain_text = std::move(text), - .allow_pro_controller = npad_style_set.fullkey == 1, - .allow_handheld = npad_style_set.handheld == 1, - .allow_dual_joycons = npad_style_set.joycon_dual == 1, - .allow_left_joycon = npad_style_set.joycon_left == 1, - .allow_right_joycon = npad_style_set.joycon_right == 1, - }; -} - -Controller::Controller(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::ControllerApplet& frontend_) - : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} - -Controller::~Controller() = default; - -void Controller::Initialize() { - Applet::Initialize(); - - LOG_INFO(Service_HID, "Initializing Controller Applet."); - - LOG_DEBUG(Service_HID, - "Initializing Applet with common_args: arg_version={}, lib_version={}, " - "play_startup_sound={}, size={}, system_tick={}, theme_color={}", - common_args.arguments_version, common_args.library_version, - common_args.play_startup_sound, common_args.size, common_args.system_tick, - common_args.theme_color); - - controller_applet_version = ControllerAppletVersion{common_args.library_version}; - - const auto private_arg_storage = broker.PopNormalDataToApplet(); - ASSERT(private_arg_storage != nullptr); - - const auto& private_arg = private_arg_storage->GetData(); - ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate)); - - std::memcpy(&controller_private_arg, private_arg.data(), private_arg.size()); - ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate), - "Unknown ControllerSupportArgPrivate revision={} with size={}", - controller_applet_version, controller_private_arg.arg_private_size); - - // Some games such as Cave Story+ set invalid values for the ControllerSupportMode. - // Defer to arg_size to set the ControllerSupportMode. - if (controller_private_arg.mode >= ControllerSupportMode::MaxControllerSupportMode) { - switch (controller_private_arg.arg_size) { - case sizeof(ControllerSupportArgOld): - case sizeof(ControllerSupportArgNew): - controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; - break; - case sizeof(ControllerUpdateFirmwareArg): - controller_private_arg.mode = ControllerSupportMode::ShowControllerFirmwareUpdate; - break; - default: - UNIMPLEMENTED_MSG("Unknown ControllerPrivateArg mode={} with arg_size={}", - controller_private_arg.mode, controller_private_arg.arg_size); - controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; - break; - } - } - - // Some games such as Cave Story+ set invalid values for the ControllerSupportCaller. - // This is always 0 (Application) except with ShowControllerFirmwareUpdateForSystem. - if (controller_private_arg.caller >= ControllerSupportCaller::MaxControllerSupportCaller) { - if (controller_private_arg.flag_1 && - controller_private_arg.mode == ControllerSupportMode::ShowControllerFirmwareUpdate) { - controller_private_arg.caller = ControllerSupportCaller::System; - } else { - controller_private_arg.caller = ControllerSupportCaller::Application; - } - } - - switch (controller_private_arg.mode) { - case ControllerSupportMode::ShowControllerSupport: - case ControllerSupportMode::ShowControllerStrapGuide: { - const auto user_arg_storage = broker.PopNormalDataToApplet(); - ASSERT(user_arg_storage != nullptr); - - const auto& user_arg = user_arg_storage->GetData(); - switch (controller_applet_version) { - case ControllerAppletVersion::Version3: - case ControllerAppletVersion::Version4: - case ControllerAppletVersion::Version5: - ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld)); - std::memcpy(&controller_user_arg_old, user_arg.data(), user_arg.size()); - break; - case ControllerAppletVersion::Version7: - ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew)); - std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size()); - break; - default: - UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}", - controller_applet_version, controller_private_arg.arg_size); - ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew)); - std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); - break; - } - break; - } - case ControllerSupportMode::ShowControllerFirmwareUpdate: { - const auto update_arg_storage = broker.PopNormalDataToApplet(); - ASSERT(update_arg_storage != nullptr); - - const auto& update_arg = update_arg_storage->GetData(); - ASSERT(update_arg.size() == sizeof(ControllerUpdateFirmwareArg)); - - std::memcpy(&controller_update_arg, update_arg.data(), update_arg.size()); - break; - } - default: { - UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode); - break; - } - } -} - -bool Controller::TransactionComplete() const { - return complete; -} - -ResultCode Controller::GetStatus() const { - return status; -} - -void Controller::ExecuteInteractive() { - UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); -} - -void Controller::Execute() { - switch (controller_private_arg.mode) { - case ControllerSupportMode::ShowControllerSupport: { - const auto parameters = [this] { - switch (controller_applet_version) { - case ControllerAppletVersion::Version3: - case ControllerAppletVersion::Version4: - case ControllerAppletVersion::Version5: - return ConvertToFrontendParameters( - controller_private_arg, controller_user_arg_old.header, - controller_user_arg_old.enable_explain_text, - std::vector( - controller_user_arg_old.identification_colors.begin(), - controller_user_arg_old.identification_colors.end()), - std::vector(controller_user_arg_old.explain_text.begin(), - controller_user_arg_old.explain_text.end())); - case ControllerAppletVersion::Version7: - default: - return ConvertToFrontendParameters( - controller_private_arg, controller_user_arg_new.header, - controller_user_arg_new.enable_explain_text, - std::vector( - controller_user_arg_new.identification_colors.begin(), - controller_user_arg_new.identification_colors.end()), - std::vector(controller_user_arg_new.explain_text.begin(), - controller_user_arg_new.explain_text.end())); - } - }(); - - is_single_mode = parameters.enable_single_mode; - - LOG_DEBUG(Service_HID, - "Controller Parameters: min_players={}, max_players={}, " - "keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, " - "enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, " - "allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}", - parameters.min_players, parameters.max_players, - parameters.keep_controllers_connected, parameters.enable_single_mode, - parameters.enable_border_color, parameters.enable_explain_text, - parameters.allow_pro_controller, parameters.allow_handheld, - parameters.allow_dual_joycons, parameters.allow_left_joycon, - parameters.allow_right_joycon); - - frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters); - break; - } - case ControllerSupportMode::ShowControllerStrapGuide: - case ControllerSupportMode::ShowControllerFirmwareUpdate: - UNIMPLEMENTED_MSG("ControllerSupportMode={} is not implemented", - controller_private_arg.mode); - ConfigurationComplete(); - break; - default: { - ConfigurationComplete(); - break; - } - } -} - -void Controller::ConfigurationComplete() { - ControllerSupportResultInfo result_info{}; - - const auto& players = Settings::values.players.GetValue(); - - // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters. - // Otherwise, only count connected players from P1-P8. - result_info.player_count = - is_single_mode - ? 1 - : static_cast(std::count_if(players.begin(), players.end() - 2, - [](const auto& player) { return player.connected; })); - - result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance( - players.begin(), std::find_if(players.begin(), players.end(), - [](const auto& player) { return player.connected; }))); - - result_info.result = 0; - - LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}", - result_info.player_count, result_info.selected_id, result_info.result); - - complete = true; - out_data = std::vector(sizeof(ControllerSupportResultInfo)); - std::memcpy(out_data.data(), &result_info, out_data.size()); - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); - broker.SignalStateChanged(); -} - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h deleted file mode 100644 index 20617e91f..000000000 --- a/src/core/hle/service/am/applets/controller.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include - -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "core/hle/result.h" -#include "core/hle/service/am/applets/applets.h" - -namespace Core { -class System; -} - -namespace Service::AM::Applets { - -using IdentificationColor = std::array; -using ExplainText = std::array; - -enum class ControllerAppletVersion : u32_le { - Version3 = 0x3, // 1.0.0 - 2.3.0 - Version4 = 0x4, // 3.0.0 - 5.1.0 - Version5 = 0x5, // 6.0.0 - 7.0.1 - Version7 = 0x7, // 8.0.0+ -}; - -enum class ControllerSupportMode : u8 { - ShowControllerSupport, - ShowControllerStrapGuide, - ShowControllerFirmwareUpdate, - - MaxControllerSupportMode, -}; - -enum class ControllerSupportCaller : u8 { - Application, - System, - - MaxControllerSupportCaller, -}; - -struct ControllerSupportArgPrivate { - u32 arg_private_size{}; - u32 arg_size{}; - bool flag_0{}; - bool flag_1{}; - ControllerSupportMode mode{}; - ControllerSupportCaller caller{}; - u32 style_set{}; - u32 joy_hold_type{}; -}; -static_assert(sizeof(ControllerSupportArgPrivate) == 0x14, - "ControllerSupportArgPrivate has incorrect size."); - -struct ControllerSupportArgHeader { - s8 player_count_min{}; - s8 player_count_max{}; - bool enable_take_over_connection{}; - bool enable_left_justify{}; - bool enable_permit_joy_dual{}; - bool enable_single_mode{}; - bool enable_identification_color{}; -}; -static_assert(sizeof(ControllerSupportArgHeader) == 0x7, - "ControllerSupportArgHeader has incorrect size."); - -// LibraryAppletVersion 0x3, 0x4, 0x5 -struct ControllerSupportArgOld { - ControllerSupportArgHeader header{}; - std::array identification_colors{}; - bool enable_explain_text{}; - std::array explain_text{}; -}; -static_assert(sizeof(ControllerSupportArgOld) == 0x21C, - "ControllerSupportArgOld has incorrect size."); - -// LibraryAppletVersion 0x7 -struct ControllerSupportArgNew { - ControllerSupportArgHeader header{}; - std::array identification_colors{}; - bool enable_explain_text{}; - std::array explain_text{}; -}; -static_assert(sizeof(ControllerSupportArgNew) == 0x430, - "ControllerSupportArgNew has incorrect size."); - -struct ControllerUpdateFirmwareArg { - bool enable_force_update{}; - INSERT_PADDING_BYTES(3); -}; -static_assert(sizeof(ControllerUpdateFirmwareArg) == 0x4, - "ControllerUpdateFirmwareArg has incorrect size."); - -struct ControllerSupportResultInfo { - s8 player_count{}; - INSERT_PADDING_BYTES(3); - u32 selected_id{}; - u32 result{}; -}; -static_assert(sizeof(ControllerSupportResultInfo) == 0xC, - "ControllerSupportResultInfo has incorrect size."); - -class Controller final : public Applet { -public: - explicit Controller(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::ControllerApplet& frontend_); - ~Controller() override; - - void Initialize() override; - - bool TransactionComplete() const override; - ResultCode GetStatus() const override; - void ExecuteInteractive() override; - void Execute() override; - - void ConfigurationComplete(); - -private: - const Core::Frontend::ControllerApplet& frontend; - Core::System& system; - - ControllerAppletVersion controller_applet_version; - ControllerSupportArgPrivate controller_private_arg; - ControllerSupportArgOld controller_user_arg_old; - ControllerSupportArgNew controller_user_arg_new; - ControllerUpdateFirmwareArg controller_update_arg; - bool complete{false}; - ResultCode status{ResultSuccess}; - bool is_single_mode{false}; - std::vector out_data; -}; - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp deleted file mode 100644 index c724e5d5b..000000000 --- a/src/core/hle/service/am/applets/error.cpp +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/frontend/applets/error.h" -#include "core/hle/kernel/k_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 { - -#pragma pack(push, 4) -struct ShowError { - u8 mode; - bool jump; - INSERT_PADDING_BYTES_NOINIT(4); - bool use_64bit_error_code; - INSERT_PADDING_BYTES_NOINIT(1); - u64 error_code_64; - u32 error_code_32; -}; -static_assert(sizeof(ShowError) == 0x14, "ShowError has incorrect size."); -#pragma pack(pop) - -struct ShowErrorRecord { - u8 mode; - bool jump; - INSERT_PADDING_BYTES_NOINIT(6); - u64 error_code_64; - u64 posix_time; -}; -static_assert(sizeof(ShowErrorRecord) == 0x18, "ShowErrorRecord has incorrect size."); - -struct SystemErrorArg { - u8 mode; - bool jump; - INSERT_PADDING_BYTES_NOINIT(6); - u64 error_code_64; - std::array language_code; - std::array main_text; - std::array detail_text; -}; -static_assert(sizeof(SystemErrorArg) == 0x1018, "SystemErrorArg has incorrect size."); - -struct ApplicationErrorArg { - u8 mode; - bool jump; - INSERT_PADDING_BYTES_NOINIT(6); - u32 error_code; - std::array language_code; - std::array main_text; - std::array detail_text; -}; -static_assert(sizeof(ApplicationErrorArg) == 0x1014, "ApplicationErrorArg has incorrect size."); - -union Error::ErrorArguments { - ShowError error; - ShowErrorRecord error_record; - SystemErrorArg system_error; - ApplicationErrorArg application_error; - std::array raw{}; -}; - -namespace { -template -void CopyArgumentData(const std::vector& data, T& variable) { - ASSERT(data.size() >= sizeof(T)); - std::memcpy(&variable, data.data(), sizeof(T)); -} - -ResultCode Decode64BitError(u64 error) { - const auto description = (error >> 32) & 0x1FFF; - auto module = error & 0x3FF; - if (module >= 2000) - module -= 2000; - module &= 0x1FF; - return {static_cast(module), static_cast(description)}; -} - -} // Anonymous namespace - -Error::Error(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::ErrorApplet& frontend_) - : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} - -Error::~Error() = default; - -void Error::Initialize() { - Applet::Initialize(); - args = std::make_unique(); - complete = false; - - const auto storage = broker.PopNormalDataToApplet(); - ASSERT(storage != nullptr); - const auto data = storage->GetData(); - - ASSERT(!data.empty()); - std::memcpy(&mode, data.data(), sizeof(ErrorAppletMode)); - - switch (mode) { - case ErrorAppletMode::ShowError: - CopyArgumentData(data, args->error); - if (args->error.use_64bit_error_code) { - error_code = Decode64BitError(args->error.error_code_64); - } else { - error_code = ResultCode(args->error.error_code_32); - } - break; - case ErrorAppletMode::ShowSystemError: - CopyArgumentData(data, args->system_error); - error_code = ResultCode(Decode64BitError(args->system_error.error_code_64)); - break; - case ErrorAppletMode::ShowApplicationError: - CopyArgumentData(data, args->application_error); - error_code = ResultCode(args->application_error.error_code); - break; - case ErrorAppletMode::ShowErrorRecord: - CopyArgumentData(data, args->error_record); - error_code = Decode64BitError(args->error_record.error_code_64); - break; - default: - UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); - } -} - -bool Error::TransactionComplete() const { - return complete; -} - -ResultCode Error::GetStatus() const { - return ResultSuccess; -} - -void Error::ExecuteInteractive() { - UNREACHABLE_MSG("Unexpected interactive applet data!"); -} - -void Error::Execute() { - if (complete) { - return; - } - - const auto callback = [this] { DisplayCompleted(); }; - const auto title_id = system.CurrentProcess()->GetTitleID(); - const auto& reporter{system.GetReporter()}; - - switch (mode) { - case ErrorAppletMode::ShowError: - reporter.SaveErrorReport(title_id, error_code); - frontend.ShowError(error_code, callback); - break; - case ErrorAppletMode::ShowSystemError: - case ErrorAppletMode::ShowApplicationError: { - const auto is_system = mode == ErrorAppletMode::ShowSystemError; - const auto& main_text = - is_system ? args->system_error.main_text : args->application_error.main_text; - const auto& detail_text = - is_system ? args->system_error.detail_text : args->application_error.detail_text; - - 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; - default: - UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); - DisplayCompleted(); - } -} - -void Error::DisplayCompleted() { - complete = true; - broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{})); - broker.SignalStateChanged(); -} - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h deleted file mode 100644 index 8aa9046a5..000000000 --- a/src/core/hle/service/am/applets/error.h +++ /dev/null @@ -1,53 +0,0 @@ -// 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" -#include "core/hle/service/am/applets/applets.h" - -namespace Core { -class System; -} - -namespace Service::AM::Applets { - -enum class ErrorAppletMode : u8 { - ShowError = 0, - ShowSystemError = 1, - ShowApplicationError = 2, - ShowEula = 3, - ShowErrorPctl = 4, - ShowErrorRecord = 5, - ShowUpdateEula = 8, -}; - -class Error final : public Applet { -public: - explicit Error(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::ErrorApplet& frontend_); - ~Error() override; - - void Initialize() override; - - bool TransactionComplete() const override; - ResultCode GetStatus() const override; - void ExecuteInteractive() override; - void Execute() override; - - void DisplayCompleted(); - -private: - union ErrorArguments; - - const Core::Frontend::ErrorApplet& frontend; - ResultCode error_code = ResultSuccess; - ErrorAppletMode mode = ErrorAppletMode::ShowError; - std::unique_ptr args; - - bool complete = false; - Core::System& system; -}; - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp deleted file mode 100644 index 9fcb9f95b..000000000 --- a/src/core/hle/service/am/applets/general_backend.cpp +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include - -#include "common/assert.h" -#include "common/hex_util.h" -#include "common/logging/log.h" -#include "core/core.h" -#include "core/frontend/applets/general_frontend.h" -#include "core/hle/kernel/k_process.h" -#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::shared_ptr storage = broker.PopNormalDataToApplet(); - for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) { - const auto data = storage->GetData(); - LOG_INFO(Service_AM, - "called (STUBBED), during {} received normal data with size={:08X}, data={}", - prefix, data.size(), Common::HexToString(data)); - } - - storage = broker.PopInteractiveDataToApplet(); - for (; storage != nullptr; storage = broker.PopInteractiveDataToApplet()) { - const auto data = storage->GetData(); - LOG_INFO(Service_AM, - "called (STUBBED), during {} received interactive data with size={:08X}, data={}", - prefix, data.size(), Common::HexToString(data)); - } -} - -Auth::Auth(Core::System& system_, LibraryAppletMode applet_mode_, - Core::Frontend::ParentalControlsApplet& frontend_) - : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} - -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 ? ResultSuccess : 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}", - type, arg0, arg1, arg2); - }; - - switch (type) { - case AuthAppletType::ShowParentalAuthentication: { - const auto callback = [this](bool is_successful) { AuthFinished(is_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(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 is_successful) { - successful = is_successful; - - struct Return { - ResultCode result_code; - }; - static_assert(sizeof(Return) == 0x4, "Return (AuthApplet) has incorrect size."); - - Return return_{GetStatus()}; - - std::vector out(sizeof(Return)); - std::memcpy(out.data(), &return_, sizeof(Return)); - - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out))); - broker.SignalStateChanged(); -} - -PhotoViewer::PhotoViewer(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::PhotoViewerApplet& frontend_) - : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} - -PhotoViewer::~PhotoViewer() = default; - -void PhotoViewer::Initialize() { - Applet::Initialize(); - complete = false; - - const auto storage = broker.PopNormalDataToApplet(); - ASSERT(storage != nullptr); - const auto data = storage->GetData(); - ASSERT(!data.empty()); - mode = static_cast(data[0]); -} - -bool PhotoViewer::TransactionComplete() const { - return complete; -} - -ResultCode PhotoViewer::GetStatus() const { - return ResultSuccess; -} - -void PhotoViewer::ExecuteInteractive() { - UNREACHABLE_MSG("Unexpected interactive applet data."); -} - -void PhotoViewer::Execute() { - if (complete) - return; - - const auto callback = [this] { ViewFinished(); }; - switch (mode) { - case PhotoViewerAppletMode::CurrentApp: - frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback); - break; - case PhotoViewerAppletMode::AllApps: - frontend.ShowAllPhotos(callback); - break; - default: - UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode); - } -} - -void PhotoViewer::ViewFinished() { - broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{})); - broker.SignalStateChanged(); -} - -StubApplet::StubApplet(Core::System& system_, AppletId id_, LibraryAppletMode applet_mode_) - : Applet{system_, applet_mode_}, id{id_}, system{system_} {} - -StubApplet::~StubApplet() = default; - -void StubApplet::Initialize() { - LOG_WARNING(Service_AM, "called (STUBBED)"); - Applet::Initialize(); - - const auto data = broker.PeekDataToAppletForDebug(); - system.GetReporter().SaveUnimplementedAppletReport( - static_cast(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"); -} - -bool StubApplet::TransactionComplete() const { - LOG_WARNING(Service_AM, "called (STUBBED)"); - return true; -} - -ResultCode StubApplet::GetStatus() const { - LOG_WARNING(Service_AM, "called (STUBBED)"); - return ResultSuccess; -} - -void StubApplet::ExecuteInteractive() { - LOG_WARNING(Service_AM, "called (STUBBED)"); - LogCurrentStorage(broker, "ExecuteInteractive"); - - broker.PushNormalDataFromApplet(std::make_shared(system, std::vector(0x1000))); - broker.PushInteractiveDataFromApplet( - std::make_shared(system, std::vector(0x1000))); - broker.SignalStateChanged(); -} - -void StubApplet::Execute() { - LOG_WARNING(Service_AM, "called (STUBBED)"); - LogCurrentStorage(broker, "Execute"); - - broker.PushNormalDataFromApplet(std::make_shared(system, std::vector(0x1000))); - broker.PushInteractiveDataFromApplet( - std::make_shared(system, std::vector(0x1000))); - broker.SignalStateChanged(); -} - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h deleted file mode 100644 index 7496ded88..000000000 --- a/src/core/hle/service/am/applets/general_backend.h +++ /dev/null @@ -1,90 +0,0 @@ -// 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/am/applets/applets.h" - -namespace Core { -class System; -} - -namespace Service::AM::Applets { - -enum class AuthAppletType : u32 { - ShowParentalAuthentication, - RegisterParentalPasscode, - ChangeParentalPasscode, -}; - -class Auth final : public Applet { -public: - explicit Auth(Core::System& system_, LibraryAppletMode applet_mode_, - 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 is_successful = true); - -private: - Core::Frontend::ParentalControlsApplet& frontend; - Core::System& system; - 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, -}; - -class PhotoViewer final : public Applet { -public: - explicit PhotoViewer(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::PhotoViewerApplet& frontend_); - ~PhotoViewer() override; - - void Initialize() override; - bool TransactionComplete() const override; - ResultCode GetStatus() const override; - void ExecuteInteractive() override; - void Execute() override; - - void ViewFinished(); - -private: - const Core::Frontend::PhotoViewerApplet& frontend; - bool complete = false; - PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; - Core::System& system; -}; - -class StubApplet final : public Applet { -public: - explicit StubApplet(Core::System& system_, AppletId id_, LibraryAppletMode applet_mode_); - ~StubApplet() override; - - void Initialize() override; - - bool TransactionComplete() const override; - ResultCode GetStatus() const override; - void ExecuteInteractive() override; - void Execute() override; - -private: - AppletId id; - Core::System& system; -}; - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp deleted file mode 100644 index 37048be26..000000000 --- a/src/core/hle/service/am/applets/profile_select.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include - -#include "common/assert.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/frontend/applets/profile_select.h" -#include "core/hle/service/am/am.h" -#include "core/hle/service/am/applets/profile_select.h" - -namespace Service::AM::Applets { - -constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; - -ProfileSelect::ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::ProfileSelectApplet& frontend_) - : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} - -ProfileSelect::~ProfileSelect() = default; - -void ProfileSelect::Initialize() { - complete = false; - status = ResultSuccess; - final_data.clear(); - - Applet::Initialize(); - - const auto user_config_storage = broker.PopNormalDataToApplet(); - ASSERT(user_config_storage != nullptr); - const auto& user_config = user_config_storage->GetData(); - - ASSERT(user_config.size() >= sizeof(UserSelectionConfig)); - std::memcpy(&config, user_config.data(), sizeof(UserSelectionConfig)); -} - -bool ProfileSelect::TransactionComplete() const { - return complete; -} - -ResultCode ProfileSelect::GetStatus() const { - return status; -} - -void ProfileSelect::ExecuteInteractive() { - UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); -} - -void ProfileSelect::Execute() { - if (complete) { - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); - return; - } - - frontend.SelectProfile([this](std::optional uuid) { SelectionComplete(uuid); }); -} - -void ProfileSelect::SelectionComplete(std::optional uuid) { - UserSelectionOutput output{}; - - if (uuid.has_value() && uuid->uuid != Common::INVALID_UUID) { - output.result = 0; - output.uuid_selected = uuid->uuid; - } else { - status = ERR_USER_CANCELLED_SELECTION; - output.result = ERR_USER_CANCELLED_SELECTION.raw; - output.uuid_selected = Common::INVALID_UUID; - } - - final_data = std::vector(sizeof(UserSelectionOutput)); - std::memcpy(final_data.data(), &output, final_data.size()); - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); - broker.SignalStateChanged(); -} - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h deleted file mode 100644 index 8fb76e6c4..000000000 --- a/src/core/hle/service/am/applets/profile_select.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include - -#include "common/common_funcs.h" -#include "common/uuid.h" -#include "core/hle/result.h" -#include "core/hle/service/am/applets/applets.h" - -namespace Core { -class System; -} - -namespace Service::AM::Applets { - -struct UserSelectionConfig { - // TODO(DarkLordZach): RE this structure - // It seems to be flags and the like that determine the UI of the applet on the switch... from - // my research this is safe to ignore for now. - INSERT_PADDING_BYTES(0xA0); -}; -static_assert(sizeof(UserSelectionConfig) == 0xA0, "UserSelectionConfig has incorrect size."); - -struct UserSelectionOutput { - u64 result; - u128 uuid_selected; -}; -static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has incorrect size."); - -class ProfileSelect final : public Applet { -public: - explicit ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::ProfileSelectApplet& frontend_); - ~ProfileSelect() override; - - void Initialize() override; - - bool TransactionComplete() const override; - ResultCode GetStatus() const override; - void ExecuteInteractive() override; - void Execute() override; - - void SelectionComplete(std::optional uuid); - -private: - const Core::Frontend::ProfileSelectApplet& frontend; - - UserSelectionConfig config; - bool complete = false; - ResultCode status = ResultSuccess; - std::vector final_data; - Core::System& system; -}; - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp deleted file mode 100644 index 00dfe1675..000000000 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ /dev/null @@ -1,1082 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/string_util.h" -#include "core/core.h" -#include "core/frontend/applets/software_keyboard.h" -#include "core/hle/service/am/am.h" -#include "core/hle/service/am/applets/software_keyboard.h" - -namespace Service::AM::Applets { - -namespace { - -// The maximum number of UTF-16 characters that can be input into the swkbd text field. -constexpr u32 DEFAULT_MAX_TEXT_LENGTH = 500; - -constexpr std::size_t REPLY_BASE_SIZE = sizeof(SwkbdState) + sizeof(SwkbdReplyType); -constexpr std::size_t REPLY_UTF8_SIZE = 0x7D4; -constexpr std::size_t REPLY_UTF16_SIZE = 0x3EC; - -constexpr const char* GetTextCheckResultName(SwkbdTextCheckResult text_check_result) { - switch (text_check_result) { - case SwkbdTextCheckResult::Success: - return "Success"; - case SwkbdTextCheckResult::Failure: - return "Failure"; - case SwkbdTextCheckResult::Confirm: - return "Confirm"; - case SwkbdTextCheckResult::Silent: - return "Silent"; - default: - UNIMPLEMENTED_MSG("Unknown TextCheckResult={}", text_check_result); - return "Unknown"; - } -} - -void SetReplyBase(std::vector& reply, SwkbdState state, SwkbdReplyType reply_type) { - std::memcpy(reply.data(), &state, sizeof(SwkbdState)); - std::memcpy(reply.data() + sizeof(SwkbdState), &reply_type, sizeof(SwkbdReplyType)); -} - -} // Anonymous namespace - -SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, LibraryAppletMode applet_mode_, - Core::Frontend::SoftwareKeyboardApplet& frontend_) - : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {} - -SoftwareKeyboard::~SoftwareKeyboard() = default; - -void SoftwareKeyboard::Initialize() { - Applet::Initialize(); - - LOG_INFO(Service_AM, "Initializing Software Keyboard Applet with LibraryAppletMode={}", - applet_mode); - - LOG_DEBUG(Service_AM, - "Initializing Applet with common_args: arg_version={}, lib_version={}, " - "play_startup_sound={}, size={}, system_tick={}, theme_color={}", - common_args.arguments_version, common_args.library_version, - common_args.play_startup_sound, common_args.size, common_args.system_tick, - common_args.theme_color); - - swkbd_applet_version = SwkbdAppletVersion{common_args.library_version}; - - switch (applet_mode) { - case LibraryAppletMode::AllForeground: - InitializeForeground(); - break; - case LibraryAppletMode::Background: - case LibraryAppletMode::BackgroundIndirectDisplay: - InitializeBackground(applet_mode); - break; - default: - UNREACHABLE_MSG("Invalid LibraryAppletMode={}", applet_mode); - break; - } -} - -bool SoftwareKeyboard::TransactionComplete() const { - return complete; -} - -ResultCode SoftwareKeyboard::GetStatus() const { - return status; -} - -void SoftwareKeyboard::ExecuteInteractive() { - if (complete) { - return; - } - - if (is_background) { - ProcessInlineKeyboardRequest(); - } else { - ProcessTextCheck(); - } -} - -void SoftwareKeyboard::Execute() { - if (complete) { - return; - } - - if (is_background) { - return; - } - - ShowNormalKeyboard(); -} - -void SoftwareKeyboard::SubmitTextNormal(SwkbdResult result, std::u16string submitted_text) { - if (complete) { - return; - } - - if (swkbd_config_common.use_text_check && result == SwkbdResult::Ok) { - SubmitForTextCheck(submitted_text); - } else { - SubmitNormalOutputAndExit(result, submitted_text); - } -} - -void SoftwareKeyboard::SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text, - s32 cursor_position) { - if (complete) { - return; - } - - current_text = std::move(submitted_text); - current_cursor_position = cursor_position; - - if (inline_use_utf8) { - switch (reply_type) { - case SwkbdReplyType::ChangedString: - reply_type = SwkbdReplyType::ChangedStringUtf8; - break; - case SwkbdReplyType::MovedCursor: - reply_type = SwkbdReplyType::MovedCursorUtf8; - break; - case SwkbdReplyType::DecidedEnter: - reply_type = SwkbdReplyType::DecidedEnterUtf8; - break; - default: - break; - } - } - - if (use_changed_string_v2) { - switch (reply_type) { - case SwkbdReplyType::ChangedString: - reply_type = SwkbdReplyType::ChangedStringV2; - break; - case SwkbdReplyType::ChangedStringUtf8: - reply_type = SwkbdReplyType::ChangedStringUtf8V2; - break; - default: - break; - } - } - - if (use_moved_cursor_v2) { - switch (reply_type) { - case SwkbdReplyType::MovedCursor: - reply_type = SwkbdReplyType::MovedCursorV2; - break; - case SwkbdReplyType::MovedCursorUtf8: - reply_type = SwkbdReplyType::MovedCursorUtf8V2; - break; - default: - break; - } - } - - SendReply(reply_type); -} - -void SoftwareKeyboard::InitializeForeground() { - LOG_INFO(Service_AM, "Initializing Normal Software Keyboard Applet."); - - is_background = false; - - const auto swkbd_config_storage = broker.PopNormalDataToApplet(); - ASSERT(swkbd_config_storage != nullptr); - - const auto& swkbd_config_data = swkbd_config_storage->GetData(); - ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon)); - - std::memcpy(&swkbd_config_common, swkbd_config_data.data(), sizeof(SwkbdConfigCommon)); - - switch (swkbd_applet_version) { - case SwkbdAppletVersion::Version5: - case SwkbdAppletVersion::Version65542: - ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld)); - std::memcpy(&swkbd_config_old, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), - sizeof(SwkbdConfigOld)); - break; - case SwkbdAppletVersion::Version196615: - case SwkbdAppletVersion::Version262152: - case SwkbdAppletVersion::Version327689: - ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld2)); - std::memcpy(&swkbd_config_old2, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), - sizeof(SwkbdConfigOld2)); - break; - case SwkbdAppletVersion::Version393227: - case SwkbdAppletVersion::Version524301: - ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew)); - std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), - sizeof(SwkbdConfigNew)); - break; - default: - UNIMPLEMENTED_MSG("Unknown SwkbdConfig revision={} with size={}", swkbd_applet_version, - swkbd_config_data.size()); - ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew)); - std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), - sizeof(SwkbdConfigNew)); - break; - } - - const auto work_buffer_storage = broker.PopNormalDataToApplet(); - ASSERT(work_buffer_storage != nullptr); - - if (swkbd_config_common.initial_string_length == 0) { - InitializeFrontendKeyboard(); - return; - } - - const auto& work_buffer = work_buffer_storage->GetData(); - - std::vector initial_string(swkbd_config_common.initial_string_length); - - std::memcpy(initial_string.data(), - work_buffer.data() + swkbd_config_common.initial_string_offset, - swkbd_config_common.initial_string_length * sizeof(char16_t)); - - initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(initial_string.data(), - initial_string.size()); - - LOG_DEBUG(Service_AM, "\nInitial Text: {}", Common::UTF16ToUTF8(initial_text)); - - InitializeFrontendKeyboard(); -} - -void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mode) { - LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet."); - - is_background = true; - - const auto swkbd_inline_initialize_arg_storage = broker.PopNormalDataToApplet(); - ASSERT(swkbd_inline_initialize_arg_storage != nullptr); - - const auto& swkbd_inline_initialize_arg = swkbd_inline_initialize_arg_storage->GetData(); - ASSERT(swkbd_inline_initialize_arg.size() == sizeof(SwkbdInitializeArg)); - - std::memcpy(&swkbd_initialize_arg, swkbd_inline_initialize_arg.data(), - swkbd_inline_initialize_arg.size()); - - if (swkbd_initialize_arg.library_applet_mode_flag) { - ASSERT(library_applet_mode == LibraryAppletMode::Background); - } else { - ASSERT(library_applet_mode == LibraryAppletMode::BackgroundIndirectDisplay); - } -} - -void SoftwareKeyboard::ProcessTextCheck() { - const auto text_check_storage = broker.PopInteractiveDataToApplet(); - ASSERT(text_check_storage != nullptr); - - const auto& text_check_data = text_check_storage->GetData(); - ASSERT(text_check_data.size() == sizeof(SwkbdTextCheck)); - - SwkbdTextCheck swkbd_text_check; - - std::memcpy(&swkbd_text_check, text_check_data.data(), sizeof(SwkbdTextCheck)); - - std::u16string text_check_message = - swkbd_text_check.text_check_result == SwkbdTextCheckResult::Failure || - swkbd_text_check.text_check_result == SwkbdTextCheckResult::Confirm - ? Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_text_check.text_check_message.data(), - swkbd_text_check.text_check_message.size()) - : u""; - - LOG_INFO(Service_AM, "\nTextCheckResult: {}\nTextCheckMessage: {}", - GetTextCheckResultName(swkbd_text_check.text_check_result), - Common::UTF16ToUTF8(text_check_message)); - - switch (swkbd_text_check.text_check_result) { - case SwkbdTextCheckResult::Success: - SubmitNormalOutputAndExit(SwkbdResult::Ok, current_text); - break; - case SwkbdTextCheckResult::Failure: - ShowTextCheckDialog(SwkbdTextCheckResult::Failure, std::move(text_check_message)); - break; - case SwkbdTextCheckResult::Confirm: - ShowTextCheckDialog(SwkbdTextCheckResult::Confirm, std::move(text_check_message)); - break; - case SwkbdTextCheckResult::Silent: - default: - break; - } -} - -void SoftwareKeyboard::ProcessInlineKeyboardRequest() { - const auto request_data_storage = broker.PopInteractiveDataToApplet(); - ASSERT(request_data_storage != nullptr); - - const auto& request_data = request_data_storage->GetData(); - ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand)); - - SwkbdRequestCommand request_command; - - std::memcpy(&request_command, request_data.data(), sizeof(SwkbdRequestCommand)); - - switch (request_command) { - case SwkbdRequestCommand::Finalize: - RequestFinalize(request_data); - break; - case SwkbdRequestCommand::SetUserWordInfo: - RequestSetUserWordInfo(request_data); - break; - case SwkbdRequestCommand::SetCustomizeDic: - RequestSetCustomizeDic(request_data); - break; - case SwkbdRequestCommand::Calc: - RequestCalc(request_data); - break; - case SwkbdRequestCommand::SetCustomizedDictionaries: - RequestSetCustomizedDictionaries(request_data); - break; - case SwkbdRequestCommand::UnsetCustomizedDictionaries: - RequestUnsetCustomizedDictionaries(request_data); - break; - case SwkbdRequestCommand::SetChangedStringV2Flag: - RequestSetChangedStringV2Flag(request_data); - break; - case SwkbdRequestCommand::SetMovedCursorV2Flag: - RequestSetMovedCursorV2Flag(request_data); - break; - default: - UNIMPLEMENTED_MSG("Unknown SwkbdRequestCommand={}", request_command); - break; - } -} - -void SoftwareKeyboard::SubmitNormalOutputAndExit(SwkbdResult result, - std::u16string submitted_text) { - std::vector out_data(sizeof(SwkbdResult) + STRING_BUFFER_SIZE); - - if (swkbd_config_common.use_utf8) { - std::string utf8_submitted_text = Common::UTF16ToUTF8(submitted_text); - - LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-8 Submitted Text: {}", result, - utf8_submitted_text); - - std::memcpy(out_data.data(), &result, sizeof(SwkbdResult)); - std::memcpy(out_data.data() + sizeof(SwkbdResult), utf8_submitted_text.data(), - utf8_submitted_text.size()); - } else { - LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-16 Submitted Text: {}", result, - Common::UTF16ToUTF8(submitted_text)); - - std::memcpy(out_data.data(), &result, sizeof(SwkbdResult)); - std::memcpy(out_data.data() + sizeof(SwkbdResult), submitted_text.data(), - submitted_text.size() * sizeof(char16_t)); - } - - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); - - ExitKeyboard(); -} - -void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) { - current_text = std::move(submitted_text); - - std::vector out_data(sizeof(u64) + STRING_BUFFER_SIZE); - - if (swkbd_config_common.use_utf8) { - std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text); - const u64 buffer_size = sizeof(u64) + utf8_submitted_text.size(); - - LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size, - utf8_submitted_text); - - std::memcpy(out_data.data(), &buffer_size, sizeof(u64)); - std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(), - utf8_submitted_text.size()); - } else { - const u64 buffer_size = sizeof(u64) + current_text.size() * sizeof(char16_t); - - LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size, - Common::UTF16ToUTF8(current_text)); - - std::memcpy(out_data.data(), &buffer_size, sizeof(u64)); - std::memcpy(out_data.data() + sizeof(u64), current_text.data(), - current_text.size() * sizeof(char16_t)); - } - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(out_data))); -} - -void SoftwareKeyboard::SendReply(SwkbdReplyType reply_type) { - switch (reply_type) { - case SwkbdReplyType::FinishedInitialize: - ReplyFinishedInitialize(); - break; - case SwkbdReplyType::Default: - ReplyDefault(); - break; - case SwkbdReplyType::ChangedString: - ReplyChangedString(); - break; - case SwkbdReplyType::MovedCursor: - ReplyMovedCursor(); - break; - case SwkbdReplyType::MovedTab: - ReplyMovedTab(); - break; - case SwkbdReplyType::DecidedEnter: - ReplyDecidedEnter(); - break; - case SwkbdReplyType::DecidedCancel: - ReplyDecidedCancel(); - break; - case SwkbdReplyType::ChangedStringUtf8: - ReplyChangedStringUtf8(); - break; - case SwkbdReplyType::MovedCursorUtf8: - ReplyMovedCursorUtf8(); - break; - case SwkbdReplyType::DecidedEnterUtf8: - ReplyDecidedEnterUtf8(); - break; - case SwkbdReplyType::UnsetCustomizeDic: - ReplyUnsetCustomizeDic(); - break; - case SwkbdReplyType::ReleasedUserWordInfo: - ReplyReleasedUserWordInfo(); - break; - case SwkbdReplyType::UnsetCustomizedDictionaries: - ReplyUnsetCustomizedDictionaries(); - break; - case SwkbdReplyType::ChangedStringV2: - ReplyChangedStringV2(); - break; - case SwkbdReplyType::MovedCursorV2: - ReplyMovedCursorV2(); - break; - case SwkbdReplyType::ChangedStringUtf8V2: - ReplyChangedStringUtf8V2(); - break; - case SwkbdReplyType::MovedCursorUtf8V2: - ReplyMovedCursorUtf8V2(); - break; - default: - UNIMPLEMENTED_MSG("Unknown SwkbdReplyType={}", reply_type); - ReplyDefault(); - break; - } -} - -void SoftwareKeyboard::ChangeState(SwkbdState state) { - swkbd_state = state; - - ReplyDefault(); -} - -void SoftwareKeyboard::InitializeFrontendKeyboard() { - if (is_background) { - const auto& appear_arg = swkbd_calc_arg.appear_arg; - - std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - appear_arg.ok_text.data(), appear_arg.ok_text.size()); - - const u32 max_text_length = - appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH - ? appear_arg.max_text_length - : DEFAULT_MAX_TEXT_LENGTH; - - const u32 min_text_length = - appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; - - const s32 initial_cursor_position = - current_cursor_position > 0 ? current_cursor_position : 0; - - const auto text_draw_type = - max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; - - Core::Frontend::KeyboardInitializeParameters initialize_parameters{ - .ok_text{std::move(ok_text)}, - .header_text{}, - .sub_text{}, - .guide_text{}, - .initial_text{current_text}, - .max_text_length{max_text_length}, - .min_text_length{min_text_length}, - .initial_cursor_position{initial_cursor_position}, - .type{appear_arg.type}, - .password_mode{SwkbdPasswordMode::Disabled}, - .text_draw_type{text_draw_type}, - .key_disable_flags{appear_arg.key_disable_flags}, - .use_blur_background{false}, - .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, - .enable_return_button{appear_arg.enable_return_button}, - .disable_cancel_button{appear_arg.disable_cancel_button}, - }; - - frontend.InitializeKeyboard( - true, std::move(initialize_parameters), {}, - [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) { - SubmitTextInline(reply_type, submitted_text, cursor_position); - }); - } else { - std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size()); - - std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size()); - - std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size()); - - std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size()); - - const u32 max_text_length = - swkbd_config_common.max_text_length > 0 && - swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH - ? swkbd_config_common.max_text_length - : DEFAULT_MAX_TEXT_LENGTH; - - const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length - ? swkbd_config_common.min_text_length - : 0; - - const s32 initial_cursor_position = [this] { - switch (swkbd_config_common.initial_cursor_position) { - case SwkbdInitialCursorPosition::Start: - default: - return 0; - case SwkbdInitialCursorPosition::End: - return static_cast(initial_text.size()); - } - }(); - - const auto text_draw_type = [this, max_text_length] { - switch (swkbd_config_common.text_draw_type) { - case SwkbdTextDrawType::Line: - default: - return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; - case SwkbdTextDrawType::Box: - case SwkbdTextDrawType::DownloadCode: - return swkbd_config_common.text_draw_type; - } - }(); - - const auto enable_return_button = text_draw_type == SwkbdTextDrawType::Box - ? swkbd_config_common.enable_return_button - : false; - - const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227 - ? swkbd_config_new.disable_cancel_button - : false; - - Core::Frontend::KeyboardInitializeParameters initialize_parameters{ - .ok_text{std::move(ok_text)}, - .header_text{std::move(header_text)}, - .sub_text{std::move(sub_text)}, - .guide_text{std::move(guide_text)}, - .initial_text{initial_text}, - .max_text_length{max_text_length}, - .min_text_length{min_text_length}, - .initial_cursor_position{initial_cursor_position}, - .type{swkbd_config_common.type}, - .password_mode{swkbd_config_common.password_mode}, - .text_draw_type{text_draw_type}, - .key_disable_flags{swkbd_config_common.key_disable_flags}, - .use_blur_background{swkbd_config_common.use_blur_background}, - .enable_backspace_button{true}, - .enable_return_button{enable_return_button}, - .disable_cancel_button{disable_cancel_button}, - }; - - frontend.InitializeKeyboard(false, std::move(initialize_parameters), - [this](SwkbdResult result, std::u16string submitted_text) { - SubmitTextNormal(result, submitted_text); - }, - {}); - } -} - -void SoftwareKeyboard::ShowNormalKeyboard() { - frontend.ShowNormalKeyboard(); -} - -void SoftwareKeyboard::ShowTextCheckDialog(SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) { - frontend.ShowTextCheckDialog(text_check_result, std::move(text_check_message)); -} - -void SoftwareKeyboard::ShowInlineKeyboard() { - if (swkbd_state != SwkbdState::InitializedIsHidden) { - return; - } - - ChangeState(SwkbdState::InitializedIsAppearing); - - const auto& appear_arg = swkbd_calc_arg.appear_arg; - - const u32 max_text_length = - appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH - ? appear_arg.max_text_length - : DEFAULT_MAX_TEXT_LENGTH; - - const u32 min_text_length = - appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; - - Core::Frontend::InlineAppearParameters appear_parameters{ - .max_text_length{max_text_length}, - .min_text_length{min_text_length}, - .key_top_scale_x{swkbd_calc_arg.key_top_scale_x}, - .key_top_scale_y{swkbd_calc_arg.key_top_scale_y}, - .key_top_translate_x{swkbd_calc_arg.key_top_translate_x}, - .key_top_translate_y{swkbd_calc_arg.key_top_translate_y}, - .type{appear_arg.type}, - .key_disable_flags{appear_arg.key_disable_flags}, - .key_top_as_floating{swkbd_calc_arg.key_top_as_floating}, - .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, - .enable_return_button{appear_arg.enable_return_button}, - .disable_cancel_button{appear_arg.disable_cancel_button}, - }; - - frontend.ShowInlineKeyboard(std::move(appear_parameters)); - - ChangeState(SwkbdState::InitializedIsShown); -} - -void SoftwareKeyboard::HideInlineKeyboard() { - if (swkbd_state != SwkbdState::InitializedIsShown) { - return; - } - - ChangeState(SwkbdState::InitializedIsDisappearing); - - frontend.HideInlineKeyboard(); - - ChangeState(SwkbdState::InitializedIsHidden); -} - -void SoftwareKeyboard::InlineTextChanged() { - Core::Frontend::InlineTextParameters text_parameters{ - .input_text{current_text}, - .cursor_position{current_cursor_position}, - }; - - frontend.InlineTextChanged(std::move(text_parameters)); -} - -void SoftwareKeyboard::ExitKeyboard() { - complete = true; - status = ResultSuccess; - - frontend.ExitKeyboard(); - - broker.SignalStateChanged(); -} - -// Inline Software Keyboard Requests - -void SoftwareKeyboard::RequestFinalize(const std::vector& request_data) { - LOG_DEBUG(Service_AM, "Processing Request: Finalize"); - - ChangeState(SwkbdState::NotInitialized); - - ExitKeyboard(); -} - -void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector& request_data) { - LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented."); -} - -void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector& request_data) { - LOG_WARNING(Service_AM, "SetCustomizeDic is not implemented."); -} - -void SoftwareKeyboard::RequestCalc(const std::vector& request_data) { - LOG_DEBUG(Service_AM, "Processing Request: Calc"); - - ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArg)); - - std::memcpy(&swkbd_calc_arg, request_data.data() + sizeof(SwkbdRequestCommand), - sizeof(SwkbdCalcArg)); - - if (swkbd_calc_arg.flags.set_input_text) { - current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - swkbd_calc_arg.input_text.data(), swkbd_calc_arg.input_text.size()); - } - - if (swkbd_calc_arg.flags.set_cursor_position) { - current_cursor_position = swkbd_calc_arg.cursor_position; - } - - if (swkbd_calc_arg.flags.set_utf8_mode) { - inline_use_utf8 = swkbd_calc_arg.utf8_mode; - } - - if (swkbd_state <= SwkbdState::InitializedIsHidden && - swkbd_calc_arg.flags.unset_customize_dic) { - ReplyUnsetCustomizeDic(); - } - - if (swkbd_state <= SwkbdState::InitializedIsHidden && - swkbd_calc_arg.flags.unset_user_word_info) { - ReplyReleasedUserWordInfo(); - } - - if (swkbd_state == SwkbdState::NotInitialized && swkbd_calc_arg.flags.set_initialize_arg) { - InitializeFrontendKeyboard(); - - ChangeState(SwkbdState::InitializedIsHidden); - - ReplyFinishedInitialize(); - } - - if (!swkbd_calc_arg.flags.set_initialize_arg && - (swkbd_calc_arg.flags.set_input_text || swkbd_calc_arg.flags.set_cursor_position)) { - InlineTextChanged(); - } - - if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg.flags.appear) { - ShowInlineKeyboard(); - return; - } - - if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg.flags.disappear) { - HideInlineKeyboard(); - return; - } -} - -void SoftwareKeyboard::RequestSetCustomizedDictionaries(const std::vector& request_data) { - LOG_WARNING(Service_AM, "SetCustomizedDictionaries is not implemented."); -} - -void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(const std::vector& request_data) { - LOG_WARNING(Service_AM, "(STUBBED) Processing Request: UnsetCustomizedDictionaries"); - - ReplyUnsetCustomizedDictionaries(); -} - -void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector& request_data) { - LOG_DEBUG(Service_AM, "Processing Request: SetChangedStringV2Flag"); - - ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1); - - std::memcpy(&use_changed_string_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1); -} - -void SoftwareKeyboard::RequestSetMovedCursorV2Flag(const std::vector& request_data) { - LOG_DEBUG(Service_AM, "Processing Request: SetMovedCursorV2Flag"); - - ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1); - - std::memcpy(&use_moved_cursor_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1); -} - -// Inline Software Keyboard Replies - -void SoftwareKeyboard::ReplyFinishedInitialize() { - LOG_DEBUG(Service_AM, "Sending Reply: FinishedInitialize"); - - std::vector reply(REPLY_BASE_SIZE + 1); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::FinishedInitialize); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyDefault() { - LOG_DEBUG(Service_AM, "Sending Reply: Default"); - - std::vector reply(REPLY_BASE_SIZE); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::Default); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyChangedString() { - LOG_DEBUG(Service_AM, "Sending Reply: ChangedString"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg)); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedString); - - const SwkbdChangedStringArg changed_string_arg{ - .text_length{static_cast(current_text.size())}, - .dictionary_start_cursor_position{-1}, - .dictionary_end_cursor_position{-1}, - .cursor_position{current_cursor_position}, - }; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), - current_text.size() * sizeof(char16_t)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg, - sizeof(SwkbdChangedStringArg)); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyMovedCursor() { - LOG_DEBUG(Service_AM, "Sending Reply: MovedCursor"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg)); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursor); - - const SwkbdMovedCursorArg moved_cursor_arg{ - .text_length{static_cast(current_text.size())}, - .cursor_position{current_cursor_position}, - }; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), - current_text.size() * sizeof(char16_t)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg, - sizeof(SwkbdMovedCursorArg)); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyMovedTab() { - LOG_DEBUG(Service_AM, "Sending Reply: MovedTab"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedTabArg)); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedTab); - - const SwkbdMovedTabArg moved_tab_arg{ - .text_length{static_cast(current_text.size())}, - .cursor_position{current_cursor_position}, - }; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), - current_text.size() * sizeof(char16_t)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_tab_arg, - sizeof(SwkbdMovedTabArg)); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyDecidedEnter() { - LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnter"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdDecidedEnterArg)); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnter); - - const SwkbdDecidedEnterArg decided_enter_arg{ - .text_length{static_cast(current_text.size())}, - }; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), - current_text.size() * sizeof(char16_t)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &decided_enter_arg, - sizeof(SwkbdDecidedEnterArg)); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); - - HideInlineKeyboard(); -} - -void SoftwareKeyboard::ReplyDecidedCancel() { - LOG_DEBUG(Service_AM, "Sending Reply: DecidedCancel"); - - std::vector reply(REPLY_BASE_SIZE); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedCancel); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); - - HideInlineKeyboard(); -} - -void SoftwareKeyboard::ReplyChangedStringUtf8() { - LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg)); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8); - - std::string utf8_current_text = Common::UTF16ToUTF8(current_text); - - const SwkbdChangedStringArg changed_string_arg{ - .text_length{static_cast(current_text.size())}, - .dictionary_start_cursor_position{-1}, - .dictionary_end_cursor_position{-1}, - .cursor_position{current_cursor_position}, - }; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg, - sizeof(SwkbdChangedStringArg)); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyMovedCursorUtf8() { - LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg)); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8); - - std::string utf8_current_text = Common::UTF16ToUTF8(current_text); - - const SwkbdMovedCursorArg moved_cursor_arg{ - .text_length{static_cast(current_text.size())}, - .cursor_position{current_cursor_position}, - }; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg, - sizeof(SwkbdMovedCursorArg)); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyDecidedEnterUtf8() { - LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnterUtf8"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdDecidedEnterArg)); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnterUtf8); - - std::string utf8_current_text = Common::UTF16ToUTF8(current_text); - - const SwkbdDecidedEnterArg decided_enter_arg{ - .text_length{static_cast(current_text.size())}, - }; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &decided_enter_arg, - sizeof(SwkbdDecidedEnterArg)); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); - - HideInlineKeyboard(); -} - -void SoftwareKeyboard::ReplyUnsetCustomizeDic() { - LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizeDic"); - - std::vector reply(REPLY_BASE_SIZE); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizeDic); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyReleasedUserWordInfo() { - LOG_DEBUG(Service_AM, "Sending Reply: ReleasedUserWordInfo"); - - std::vector reply(REPLY_BASE_SIZE); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::ReleasedUserWordInfo); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyUnsetCustomizedDictionaries() { - LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizedDictionaries"); - - std::vector reply(REPLY_BASE_SIZE); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizedDictionaries); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyChangedStringV2() { - LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringV2"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg) + 1); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringV2); - - const SwkbdChangedStringArg changed_string_arg{ - .text_length{static_cast(current_text.size())}, - .dictionary_start_cursor_position{-1}, - .dictionary_end_cursor_position{-1}, - .cursor_position{current_cursor_position}, - }; - - constexpr u8 flag = 0; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), - current_text.size() * sizeof(char16_t)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg, - sizeof(SwkbdChangedStringArg)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg), - &flag, 1); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyMovedCursorV2() { - LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorV2"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg) + 1); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorV2); - - const SwkbdMovedCursorArg moved_cursor_arg{ - .text_length{static_cast(current_text.size())}, - .cursor_position{current_cursor_position}, - }; - - constexpr u8 flag = 0; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), - current_text.size() * sizeof(char16_t)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg, - sizeof(SwkbdMovedCursorArg)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg), - &flag, 1); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyChangedStringUtf8V2() { - LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8V2"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg) + 1); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8V2); - - std::string utf8_current_text = Common::UTF16ToUTF8(current_text); - - const SwkbdChangedStringArg changed_string_arg{ - .text_length{static_cast(current_text.size())}, - .dictionary_start_cursor_position{-1}, - .dictionary_end_cursor_position{-1}, - .cursor_position{current_cursor_position}, - }; - - constexpr u8 flag = 0; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg, - sizeof(SwkbdChangedStringArg)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg), - &flag, 1); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -void SoftwareKeyboard::ReplyMovedCursorUtf8V2() { - LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8V2"); - - std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg) + 1); - - SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8V2); - - std::string utf8_current_text = Common::UTF16ToUTF8(current_text); - - const SwkbdMovedCursorArg moved_cursor_arg{ - .text_length{static_cast(current_text.size())}, - .cursor_position{current_cursor_position}, - }; - - constexpr u8 flag = 0; - - std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg, - sizeof(SwkbdMovedCursorArg)); - std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg), - &flag, 1); - - broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); -} - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h deleted file mode 100644 index e3fc733d0..000000000 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "core/hle/result.h" -#include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/am/applets/software_keyboard_types.h" - -namespace Core { -class System; -} - -namespace Service::AM::Applets { - -class SoftwareKeyboard final : public Applet { -public: - explicit SoftwareKeyboard(Core::System& system_, LibraryAppletMode applet_mode_, - Core::Frontend::SoftwareKeyboardApplet& frontend_); - ~SoftwareKeyboard() override; - - void Initialize() override; - - bool TransactionComplete() const override; - ResultCode GetStatus() const override; - void ExecuteInteractive() override; - void Execute() override; - - /** - * Submits the input text to the application. - * If text checking is enabled, the application will verify the input text. - * If use_utf8 is enabled, the input text will be converted to UTF-8 prior to being submitted. - * This should only be used by the normal software keyboard. - * - * @param result SwkbdResult enum - * @param submitted_text UTF-16 encoded string - */ - void SubmitTextNormal(SwkbdResult result, std::u16string submitted_text); - - /** - * Submits the input text to the application. - * If utf8_mode is enabled, the input text will be converted to UTF-8 prior to being submitted. - * This should only be used by the inline software keyboard. - * - * @param reply_type SwkbdReplyType enum - * @param submitted_text UTF-16 encoded string - * @param cursor_position The current position of the text cursor - */ - void SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text, - s32 cursor_position); - -private: - /// Initializes the normal software keyboard. - void InitializeForeground(); - - /// Initializes the inline software keyboard. - void InitializeBackground(LibraryAppletMode library_applet_mode); - - /// Processes the text check sent by the application. - void ProcessTextCheck(); - - /// Processes the inline software keyboard request command sent by the application. - void ProcessInlineKeyboardRequest(); - - /// Submits the input text and exits the applet. - void SubmitNormalOutputAndExit(SwkbdResult result, std::u16string submitted_text); - - /// Submits the input text for text checking. - void SubmitForTextCheck(std::u16string submitted_text); - - /// Sends a reply to the application after processing a request command. - void SendReply(SwkbdReplyType reply_type); - - /// Changes the inline keyboard state. - void ChangeState(SwkbdState state); - - /** - * Signals the frontend to initialize the software keyboard with common parameters. - * This initializes either the normal software keyboard or the inline software keyboard - * depending on the state of is_background. - * Note that this does not cause the keyboard to appear. - * Use the respective Show*Keyboard() functions to cause the respective keyboards to appear. - */ - void InitializeFrontendKeyboard(); - - /// Signals the frontend to show the normal software keyboard. - void ShowNormalKeyboard(); - - /// Signals the frontend to show the text check dialog. - void ShowTextCheckDialog(SwkbdTextCheckResult text_check_result, - std::u16string text_check_message); - - /// Signals the frontend to show the inline software keyboard. - void ShowInlineKeyboard(); - - /// Signals the frontend to hide the inline software keyboard. - void HideInlineKeyboard(); - - /// Signals the frontend that the current inline keyboard text has changed. - void InlineTextChanged(); - - /// Signals both the frontend and application that the software keyboard is exiting. - void ExitKeyboard(); - - // Inline Software Keyboard Requests - - void RequestFinalize(const std::vector& request_data); - void RequestSetUserWordInfo(const std::vector& request_data); - void RequestSetCustomizeDic(const std::vector& request_data); - void RequestCalc(const std::vector& request_data); - void RequestSetCustomizedDictionaries(const std::vector& request_data); - void RequestUnsetCustomizedDictionaries(const std::vector& request_data); - void RequestSetChangedStringV2Flag(const std::vector& request_data); - void RequestSetMovedCursorV2Flag(const std::vector& request_data); - - // Inline Software Keyboard Replies - - void ReplyFinishedInitialize(); - void ReplyDefault(); - void ReplyChangedString(); - void ReplyMovedCursor(); - void ReplyMovedTab(); - void ReplyDecidedEnter(); - void ReplyDecidedCancel(); - void ReplyChangedStringUtf8(); - void ReplyMovedCursorUtf8(); - void ReplyDecidedEnterUtf8(); - void ReplyUnsetCustomizeDic(); - void ReplyReleasedUserWordInfo(); - void ReplyUnsetCustomizedDictionaries(); - void ReplyChangedStringV2(); - void ReplyMovedCursorV2(); - void ReplyChangedStringUtf8V2(); - void ReplyMovedCursorUtf8V2(); - - Core::Frontend::SoftwareKeyboardApplet& frontend; - Core::System& system; - - SwkbdAppletVersion swkbd_applet_version; - - SwkbdConfigCommon swkbd_config_common; - SwkbdConfigOld swkbd_config_old; - SwkbdConfigOld2 swkbd_config_old2; - SwkbdConfigNew swkbd_config_new; - std::u16string initial_text; - - SwkbdState swkbd_state{SwkbdState::NotInitialized}; - SwkbdInitializeArg swkbd_initialize_arg; - SwkbdCalcArg swkbd_calc_arg; - bool use_changed_string_v2{false}; - bool use_moved_cursor_v2{false}; - bool inline_use_utf8{false}; - s32 current_cursor_position{}; - - std::u16string current_text; - - bool is_background{false}; - - bool complete{false}; - ResultCode status{ResultSuccess}; -}; - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard_types.h b/src/core/hle/service/am/applets/software_keyboard_types.h deleted file mode 100644 index 21aa8e800..000000000 --- a/src/core/hle/service/am/applets/software_keyboard_types.h +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include - -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" - -namespace Service::AM::Applets { - -constexpr std::size_t MAX_OK_TEXT_LENGTH = 8; -constexpr std::size_t MAX_HEADER_TEXT_LENGTH = 64; -constexpr std::size_t MAX_SUB_TEXT_LENGTH = 128; -constexpr std::size_t MAX_GUIDE_TEXT_LENGTH = 256; -constexpr std::size_t STRING_BUFFER_SIZE = 0x7D4; - -enum class SwkbdAppletVersion : u32_le { - Version5 = 0x5, // 1.0.0 - Version65542 = 0x10006, // 2.0.0 - 2.3.0 - Version196615 = 0x30007, // 3.0.0 - 3.0.2 - Version262152 = 0x40008, // 4.0.0 - 4.1.0 - Version327689 = 0x50009, // 5.0.0 - 5.1.0 - Version393227 = 0x6000B, // 6.0.0 - 7.0.1 - Version524301 = 0x8000D, // 8.0.0+ -}; - -enum class SwkbdType : u32 { - Normal, - NumberPad, - Qwerty, - Unknown3, - Latin, - SimplifiedChinese, - TraditionalChinese, - Korean, -}; - -enum class SwkbdInitialCursorPosition : u32 { - Start, - End, -}; - -enum class SwkbdPasswordMode : u32 { - Disabled, - Enabled, -}; - -enum class SwkbdTextDrawType : u32 { - Line, - Box, - DownloadCode, -}; - -enum class SwkbdResult : u32 { - Ok, - Cancel, -}; - -enum class SwkbdTextCheckResult : u32 { - Success, - Failure, - Confirm, - Silent, -}; - -enum class SwkbdState : u32 { - NotInitialized = 0x0, - InitializedIsHidden = 0x1, - InitializedIsAppearing = 0x2, - InitializedIsShown = 0x3, - InitializedIsDisappearing = 0x4, -}; - -enum class SwkbdRequestCommand : u32 { - Finalize = 0x4, - SetUserWordInfo = 0x6, - SetCustomizeDic = 0x7, - Calc = 0xA, - SetCustomizedDictionaries = 0xB, - UnsetCustomizedDictionaries = 0xC, - SetChangedStringV2Flag = 0xD, - SetMovedCursorV2Flag = 0xE, -}; - -enum class SwkbdReplyType : u32 { - FinishedInitialize = 0x0, - Default = 0x1, - ChangedString = 0x2, - MovedCursor = 0x3, - MovedTab = 0x4, - DecidedEnter = 0x5, - DecidedCancel = 0x6, - ChangedStringUtf8 = 0x7, - MovedCursorUtf8 = 0x8, - DecidedEnterUtf8 = 0x9, - UnsetCustomizeDic = 0xA, - ReleasedUserWordInfo = 0xB, - UnsetCustomizedDictionaries = 0xC, - ChangedStringV2 = 0xD, - MovedCursorV2 = 0xE, - ChangedStringUtf8V2 = 0xF, - MovedCursorUtf8V2 = 0x10, -}; - -struct SwkbdKeyDisableFlags { - union { - u32 raw{}; - - BitField<1, 1, u32> space; - BitField<2, 1, u32> at; - BitField<3, 1, u32> percent; - BitField<4, 1, u32> slash; - BitField<5, 1, u32> backslash; - BitField<6, 1, u32> numbers; - BitField<7, 1, u32> download_code; - BitField<8, 1, u32> username; - }; -}; -static_assert(sizeof(SwkbdKeyDisableFlags) == 0x4, "SwkbdKeyDisableFlags has incorrect size."); - -struct SwkbdConfigCommon { - SwkbdType type{}; - std::array ok_text{}; - char16_t left_optional_symbol_key{}; - char16_t right_optional_symbol_key{}; - bool use_prediction{}; - INSERT_PADDING_BYTES(1); - SwkbdKeyDisableFlags key_disable_flags{}; - SwkbdInitialCursorPosition initial_cursor_position{}; - std::array header_text{}; - std::array sub_text{}; - std::array guide_text{}; - u32 max_text_length{}; - u32 min_text_length{}; - SwkbdPasswordMode password_mode{}; - SwkbdTextDrawType text_draw_type{}; - bool enable_return_button{}; - bool use_utf8{}; - bool use_blur_background{}; - INSERT_PADDING_BYTES(1); - u32 initial_string_offset{}; - u32 initial_string_length{}; - u32 user_dictionary_offset{}; - u32 user_dictionary_entries{}; - bool use_text_check{}; - INSERT_PADDING_BYTES(3); -}; -static_assert(sizeof(SwkbdConfigCommon) == 0x3D4, "SwkbdConfigCommon has incorrect size."); - -#pragma pack(push, 4) -// SwkbdAppletVersion 0x5, 0x10006 -struct SwkbdConfigOld { - INSERT_PADDING_WORDS(1); - VAddr text_check_callback{}; -}; -static_assert(sizeof(SwkbdConfigOld) == 0x3E0 - sizeof(SwkbdConfigCommon), - "SwkbdConfigOld has incorrect size."); - -// SwkbdAppletVersion 0x30007, 0x40008, 0x50009 -struct SwkbdConfigOld2 { - INSERT_PADDING_WORDS(1); - VAddr text_check_callback{}; - std::array text_grouping{}; -}; -static_assert(sizeof(SwkbdConfigOld2) == 0x400 - sizeof(SwkbdConfigCommon), - "SwkbdConfigOld2 has incorrect size."); - -// SwkbdAppletVersion 0x6000B, 0x8000D -struct SwkbdConfigNew { - std::array text_grouping{}; - std::array customized_dictionary_set_entries{}; - u8 total_customized_dictionary_set_entries{}; - bool disable_cancel_button{}; - INSERT_PADDING_BYTES(18); -}; -static_assert(sizeof(SwkbdConfigNew) == 0x4C8 - sizeof(SwkbdConfigCommon), - "SwkbdConfigNew has incorrect size."); -#pragma pack(pop) - -struct SwkbdTextCheck { - SwkbdTextCheckResult text_check_result{}; - std::array text_check_message{}; -}; -static_assert(sizeof(SwkbdTextCheck) == 0x7D8, "SwkbdTextCheck has incorrect size."); - -struct SwkbdCalcArgFlags { - union { - u64 raw{}; - - BitField<0, 1, u64> set_initialize_arg; - BitField<1, 1, u64> set_volume; - BitField<2, 1, u64> appear; - BitField<3, 1, u64> set_input_text; - BitField<4, 1, u64> set_cursor_position; - BitField<5, 1, u64> set_utf8_mode; - BitField<6, 1, u64> unset_customize_dic; - BitField<7, 1, u64> disappear; - BitField<8, 1, u64> unknown; - BitField<9, 1, u64> set_key_top_translate_scale; - BitField<10, 1, u64> unset_user_word_info; - BitField<11, 1, u64> set_disable_hardware_keyboard; - }; -}; -static_assert(sizeof(SwkbdCalcArgFlags) == 0x8, "SwkbdCalcArgFlags has incorrect size."); - -struct SwkbdInitializeArg { - u32 unknown{}; - bool library_applet_mode_flag{}; - bool is_above_hos_500{}; - INSERT_PADDING_BYTES(2); -}; -static_assert(sizeof(SwkbdInitializeArg) == 0x8, "SwkbdInitializeArg has incorrect size."); - -struct SwkbdAppearArg { - SwkbdType type{}; - std::array ok_text{}; - char16_t left_optional_symbol_key{}; - char16_t right_optional_symbol_key{}; - bool use_prediction{}; - bool disable_cancel_button{}; - SwkbdKeyDisableFlags key_disable_flags{}; - u32 max_text_length{}; - u32 min_text_length{}; - bool enable_return_button{}; - INSERT_PADDING_BYTES(3); - u32 flags{}; - INSERT_PADDING_WORDS(6); -}; -static_assert(sizeof(SwkbdAppearArg) == 0x48, "SwkbdAppearArg has incorrect size."); - -struct SwkbdCalcArg { - u32 unknown{}; - u16 calc_arg_size{}; - INSERT_PADDING_BYTES(2); - SwkbdCalcArgFlags flags{}; - SwkbdInitializeArg initialize_arg{}; - f32 volume{}; - s32 cursor_position{}; - SwkbdAppearArg appear_arg{}; - std::array input_text{}; - bool utf8_mode{}; - INSERT_PADDING_BYTES(1); - bool enable_backspace_button{}; - INSERT_PADDING_BYTES(3); - bool key_top_as_floating{}; - bool footer_scalable{}; - bool alpha_enabled_in_input_mode{}; - u8 input_mode_fade_type{}; - bool disable_touch{}; - bool disable_hardware_keyboard{}; - INSERT_PADDING_BYTES(8); - f32 key_top_scale_x{}; - f32 key_top_scale_y{}; - f32 key_top_translate_x{}; - f32 key_top_translate_y{}; - f32 key_top_bg_alpha{}; - f32 footer_bg_alpha{}; - f32 balloon_scale{}; - INSERT_PADDING_WORDS(4); - u8 se_group{}; - INSERT_PADDING_BYTES(3); -}; -static_assert(sizeof(SwkbdCalcArg) == 0x4A0, "SwkbdCalcArg has incorrect size."); - -struct SwkbdChangedStringArg { - u32 text_length{}; - s32 dictionary_start_cursor_position{}; - s32 dictionary_end_cursor_position{}; - s32 cursor_position{}; -}; -static_assert(sizeof(SwkbdChangedStringArg) == 0x10, "SwkbdChangedStringArg has incorrect size."); - -struct SwkbdMovedCursorArg { - u32 text_length{}; - s32 cursor_position{}; -}; -static_assert(sizeof(SwkbdMovedCursorArg) == 0x8, "SwkbdMovedCursorArg has incorrect size."); - -struct SwkbdMovedTabArg { - u32 text_length{}; - s32 cursor_position{}; -}; -static_assert(sizeof(SwkbdMovedTabArg) == 0x8, "SwkbdMovedTabArg has incorrect size."); - -struct SwkbdDecidedEnterArg { - u32 text_length{}; -}; -static_assert(sizeof(SwkbdDecidedEnterArg) == 0x4, "SwkbdDecidedEnterArg has incorrect size."); - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp deleted file mode 100644 index 3b28e829b..000000000 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ /dev/null @@ -1,474 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/assert.h" -#include "common/fs/file.h" -#include "common/fs/fs.h" -#include "common/fs/path_util.h" -#include "common/logging/log.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/file_sys/content_archive.h" -#include "core/file_sys/mode.h" -#include "core/file_sys/nca_metadata.h" -#include "core/file_sys/patch_manager.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_vector.h" -#include "core/frontend/applets/web_browser.h" -#include "core/hle/kernel/k_process.h" -#include "core/hle/result.h" -#include "core/hle/service/am/am.h" -#include "core/hle/service/am/applets/web_browser.h" -#include "core/hle/service/filesystem/filesystem.h" -#include "core/hle/service/ns/pl_u.h" - -namespace Service::AM::Applets { - -namespace { - -template -void ParseRawValue(T& value, const std::vector& data) { - static_assert(std::is_trivially_copyable_v, - "It's undefined behavior to use memcpy with non-trivially copyable objects"); - std::memcpy(&value, data.data(), data.size()); -} - -template -T ParseRawValue(const std::vector& data) { - T value; - ParseRawValue(value, data); - return value; -} - -std::string ParseStringValue(const std::vector& data) { - return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast(data.data()), - data.size()); -} - -std::string GetMainURL(const std::string& url) { - const auto index = url.find('?'); - - if (index == std::string::npos) { - return url; - } - - return url.substr(0, index); -} - -WebArgInputTLVMap ReadWebArgs(const std::vector& web_arg, WebArgHeader& web_arg_header) { - std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader)); - - if (web_arg.size() == sizeof(WebArgHeader)) { - return {}; - } - - WebArgInputTLVMap input_tlv_map; - - u64 current_offset = sizeof(WebArgHeader); - - for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) { - if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) { - return input_tlv_map; - } - - WebArgInputTLV input_tlv; - std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV)); - - current_offset += sizeof(WebArgInputTLV); - - if (web_arg.size() < current_offset + input_tlv.arg_data_size) { - return input_tlv_map; - } - - std::vector data(input_tlv.arg_data_size); - std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size); - - current_offset += input_tlv.arg_data_size; - - input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data)); - } - - return input_tlv_map; -} - -FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, - FileSys::ContentRecordType nca_type) { - if (nca_type == FileSys::ContentRecordType::Data) { - const auto nca = - system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type); - - if (nca == nullptr) { - LOG_ERROR(Service_AM, - "NCA of type={} with title_id={:016X} is not found in the System NAND!", - nca_type, title_id); - return FileSys::SystemArchive::SynthesizeSystemArchive(title_id); - } - - return nca->GetRomFS(); - } else { - const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type); - - if (nca == nullptr) { - LOG_ERROR(Service_AM, - "NCA of type={} with title_id={:016X} is not found in the ContentProvider!", - nca_type, title_id); - return nullptr; - } - - const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), - system.GetContentProvider()}; - - return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); - } -} - -void ExtractSharedFonts(Core::System& system) { - static constexpr std::array DECRYPTED_SHARED_FONTS{ - "FontStandard.ttf", - "FontChineseSimplified.ttf", - "FontExtendedChineseSimplified.ttf", - "FontChineseTraditional.ttf", - "FontKorean.ttf", - "FontNintendoExtended.ttf", - "FontNintendoExtended2.ttf", - }; - - const auto fonts_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts"; - - for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { - const auto font_file_path = fonts_dir / DECRYPTED_SHARED_FONTS[i]; - - if (Common::FS::Exists(font_file_path)) { - continue; - } - - const auto font = NS::SHARED_FONTS[i]; - const auto font_title_id = static_cast(font.first); - - const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry( - font_title_id, FileSys::ContentRecordType::Data); - - FileSys::VirtualFile romfs; - - if (!nca) { - romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id); - } else { - romfs = nca->GetRomFS(); - } - - if (!romfs) { - LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!", - font_title_id); - continue; - } - - const auto extracted_romfs = FileSys::ExtractRomFS(romfs); - - if (!extracted_romfs) { - LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!", - font_title_id); - continue; - } - - const auto font_file = extracted_romfs->GetFile(font.second); - - if (!font_file) { - LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!", - font_title_id, font.second); - continue; - } - - std::vector font_data_u32(font_file->GetSize() / sizeof(u32)); - font_file->ReadBytes(font_data_u32.data(), font_file->GetSize()); - - std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), - Common::swap32); - - std::vector decrypted_data(font_file->GetSize() - 8); - - NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data); - - FileSys::VirtualFile decrypted_font = std::make_shared( - std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); - - const auto temp_dir = system.GetFilesystem()->CreateDirectory( - Common::FS::PathToUTF8String(fonts_dir), FileSys::Mode::ReadWrite); - - const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); - - FileSys::VfsRawCopy(decrypted_font, out_file); - } -} - -} // namespace - -WebBrowser::WebBrowser(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::WebBrowserApplet& frontend_) - : Applet{system_, applet_mode_}, frontend(frontend_), system{system_} {} - -WebBrowser::~WebBrowser() = default; - -void WebBrowser::Initialize() { - Applet::Initialize(); - - LOG_INFO(Service_AM, "Initializing Web Browser Applet."); - - LOG_DEBUG(Service_AM, - "Initializing Applet with common_args: arg_version={}, lib_version={}, " - "play_startup_sound={}, size={}, system_tick={}, theme_color={}", - common_args.arguments_version, common_args.library_version, - common_args.play_startup_sound, common_args.size, common_args.system_tick, - common_args.theme_color); - - web_applet_version = WebAppletVersion{common_args.library_version}; - - const auto web_arg_storage = broker.PopNormalDataToApplet(); - ASSERT(web_arg_storage != nullptr); - - const auto& web_arg = web_arg_storage->GetData(); - ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; }); - - web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header); - - LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}", - web_arg_header.total_tlv_entries, web_arg_header.shim_kind); - - ExtractSharedFonts(system); - - switch (web_arg_header.shim_kind) { - case ShimKind::Shop: - InitializeShop(); - break; - case ShimKind::Login: - InitializeLogin(); - break; - case ShimKind::Offline: - InitializeOffline(); - break; - case ShimKind::Share: - InitializeShare(); - break; - case ShimKind::Web: - InitializeWeb(); - break; - case ShimKind::Wifi: - InitializeWifi(); - break; - case ShimKind::Lobby: - InitializeLobby(); - break; - default: - UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); - break; - } -} - -bool WebBrowser::TransactionComplete() const { - return complete; -} - -ResultCode WebBrowser::GetStatus() const { - return status; -} - -void WebBrowser::ExecuteInteractive() { - UNIMPLEMENTED_MSG("WebSession is not implemented"); -} - -void WebBrowser::Execute() { - switch (web_arg_header.shim_kind) { - case ShimKind::Shop: - ExecuteShop(); - break; - case ShimKind::Login: - ExecuteLogin(); - break; - case ShimKind::Offline: - ExecuteOffline(); - break; - case ShimKind::Share: - ExecuteShare(); - break; - case ShimKind::Web: - ExecuteWeb(); - break; - case ShimKind::Wifi: - ExecuteWifi(); - break; - case ShimKind::Lobby: - ExecuteLobby(); - break; - default: - UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); - WebBrowserExit(WebExitReason::EndButtonPressed); - break; - } -} - -void WebBrowser::ExtractOfflineRomFS() { - LOG_DEBUG(Service_AM, "Extracting RomFS to {}", - Common::FS::PathToUTF8String(offline_cache_dir)); - - const auto extracted_romfs_dir = - FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); - - const auto temp_dir = system.GetFilesystem()->CreateDirectory( - Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite); - - FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); -} - -void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { - if ((web_arg_header.shim_kind == ShimKind::Share && - web_applet_version >= WebAppletVersion::Version196608) || - (web_arg_header.shim_kind == ShimKind::Web && - web_applet_version >= WebAppletVersion::Version524288)) { - // TODO: Push Output TLVs instead of a WebCommonReturnValue - } - - WebCommonReturnValue web_common_return_value; - - web_common_return_value.exit_reason = exit_reason; - std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size()); - web_common_return_value.last_url_size = last_url.size(); - - LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}", - exit_reason, last_url, last_url.size()); - - complete = true; - std::vector out_data(sizeof(WebCommonReturnValue)); - std::memcpy(out_data.data(), &web_common_return_value, out_data.size()); - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); - broker.SignalStateChanged(); -} - -bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const { - return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end(); -} - -std::optional> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) { - const auto map_it = web_arg_input_tlv_map.find(input_tlv_type); - - if (map_it == web_arg_input_tlv_map.end()) { - return std::nullopt; - } - - return map_it->second; -} - -void WebBrowser::InitializeShop() {} - -void WebBrowser::InitializeLogin() {} - -void WebBrowser::InitializeOffline() { - const auto document_path = - ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value()); - - const auto document_kind = - ParseRawValue(GetInputTLVData(WebArgInputTLVType::DocumentKind).value()); - - std::string additional_paths; - - switch (document_kind) { - case DocumentKind::OfflineHtmlPage: - default: - title_id = system.CurrentProcess()->GetTitleID(); - nca_type = FileSys::ContentRecordType::HtmlDocument; - additional_paths = "html-document"; - break; - case DocumentKind::ApplicationLegalInformation: - title_id = ParseRawValue(GetInputTLVData(WebArgInputTLVType::ApplicationID).value()); - nca_type = FileSys::ContentRecordType::LegalInformation; - break; - case DocumentKind::SystemDataPage: - title_id = ParseRawValue(GetInputTLVData(WebArgInputTLVType::SystemDataID).value()); - nca_type = FileSys::ContentRecordType::Data; - break; - } - - static constexpr std::array RESOURCE_TYPES{ - "manual", - "legal_information", - "system_data", - }; - - offline_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / - fmt::format("offline_web_applet_{}/{:016X}", - RESOURCE_TYPES[static_cast(document_kind) - 1], title_id); - - offline_document = Common::FS::ConcatPathSafe( - offline_cache_dir, fmt::format("{}/{}", additional_paths, document_path)); -} - -void WebBrowser::InitializeShare() {} - -void WebBrowser::InitializeWeb() { - external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value()); -} - -void WebBrowser::InitializeWifi() {} - -void WebBrowser::InitializeLobby() {} - -void WebBrowser::ExecuteShop() { - LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented"); - WebBrowserExit(WebExitReason::EndButtonPressed); -} - -void WebBrowser::ExecuteLogin() { - LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented"); - WebBrowserExit(WebExitReason::EndButtonPressed); -} - -void WebBrowser::ExecuteOffline() { - const auto main_url = GetMainURL(Common::FS::PathToUTF8String(offline_document)); - - if (!Common::FS::Exists(main_url)) { - offline_romfs = GetOfflineRomFS(system, title_id, nca_type); - - if (offline_romfs == nullptr) { - LOG_ERROR(Service_AM, - "RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id, - nca_type); - WebBrowserExit(WebExitReason::WindowClosed); - return; - } - } - - LOG_INFO(Service_AM, "Opening offline document at {}", - Common::FS::PathToUTF8String(offline_document)); - - frontend.OpenLocalWebPage( - Common::FS::PathToUTF8String(offline_document), [this] { ExtractOfflineRomFS(); }, - [this](WebExitReason exit_reason, std::string last_url) { - WebBrowserExit(exit_reason, last_url); - }); -} - -void WebBrowser::ExecuteShare() { - LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented"); - WebBrowserExit(WebExitReason::EndButtonPressed); -} - -void WebBrowser::ExecuteWeb() { - LOG_INFO(Service_AM, "Opening external URL at {}", external_url); - - frontend.OpenExternalWebPage(external_url, - [this](WebExitReason exit_reason, std::string last_url) { - WebBrowserExit(exit_reason, last_url); - }); -} - -void WebBrowser::ExecuteWifi() { - LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented"); - WebBrowserExit(WebExitReason::EndButtonPressed); -} - -void WebBrowser::ExecuteLobby() { - LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented"); - WebBrowserExit(WebExitReason::EndButtonPressed); -} -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h deleted file mode 100644 index 9f81214b6..000000000 --- a/src/core/hle/service/am/applets/web_browser.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include - -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "core/file_sys/vfs_types.h" -#include "core/hle/result.h" -#include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/am/applets/web_types.h" - -namespace Core { -class System; -} - -namespace FileSys { -enum class ContentRecordType : u8; -} - -namespace Service::AM::Applets { - -class WebBrowser final : public Applet { -public: - WebBrowser(Core::System& system_, LibraryAppletMode applet_mode_, - const Core::Frontend::WebBrowserApplet& frontend_); - - ~WebBrowser() override; - - void Initialize() override; - - bool TransactionComplete() const override; - ResultCode GetStatus() const override; - void ExecuteInteractive() override; - void Execute() override; - - void ExtractOfflineRomFS(); - - void WebBrowserExit(WebExitReason exit_reason, std::string last_url = ""); - -private: - bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const; - - std::optional> GetInputTLVData(WebArgInputTLVType input_tlv_type); - - // Initializers for the various types of browser applets - void InitializeShop(); - void InitializeLogin(); - void InitializeOffline(); - void InitializeShare(); - void InitializeWeb(); - void InitializeWifi(); - void InitializeLobby(); - - // Executors for the various types of browser applets - void ExecuteShop(); - void ExecuteLogin(); - void ExecuteOffline(); - void ExecuteShare(); - void ExecuteWeb(); - void ExecuteWifi(); - void ExecuteLobby(); - - const Core::Frontend::WebBrowserApplet& frontend; - - bool complete{false}; - ResultCode status{ResultSuccess}; - - WebAppletVersion web_applet_version{}; - WebArgHeader web_arg_header{}; - WebArgInputTLVMap web_arg_input_tlv_map; - - u64 title_id{}; - FileSys::ContentRecordType nca_type{}; - std::filesystem::path offline_cache_dir; - std::filesystem::path offline_document; - FileSys::VirtualFile offline_romfs; - - std::string external_url; - - Core::System& system; -}; - -} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_types.h b/src/core/hle/service/am/applets/web_types.h deleted file mode 100644 index 419c2bf79..000000000 --- a/src/core/hle/service/am/applets/web_types.h +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include - -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" - -namespace Service::AM::Applets { - -enum class WebAppletVersion : u32_le { - Version0 = 0x0, // Only used by WifiWebAuthApplet - Version131072 = 0x20000, // 1.0.0 - 2.3.0 - Version196608 = 0x30000, // 3.0.0 - 4.1.0 - Version327680 = 0x50000, // 5.0.0 - 5.1.0 - Version393216 = 0x60000, // 6.0.0 - 7.0.1 - Version524288 = 0x80000, // 8.0.0+ -}; - -enum class ShimKind : u32 { - Shop = 1, - Login = 2, - Offline = 3, - Share = 4, - Web = 5, - Wifi = 6, - Lobby = 7, -}; - -enum class WebExitReason : u32 { - EndButtonPressed = 0, - BackButtonPressed = 1, - ExitRequested = 2, - CallbackURL = 3, - WindowClosed = 4, - ErrorDialog = 7, -}; - -enum class WebArgInputTLVType : u16 { - InitialURL = 0x1, - 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, - PlayReportEnabled = 0x13, - BootDisplayKind = 0x17, - BackgroundKind = 0x18, - FooterEnabled = 0x19, - PointerEnabled = 0x1A, - LeftStickMode = 0x1B, - KeyRepeatFrame1 = 0x1C, - KeyRepeatFrame2 = 0x1D, - BootAsMediaPlayerInverted = 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, - YouTubeVideoWhitelist = 0x31, - FooterFixedKind = 0x32, - PageFadeEnabled = 0x33, - MediaCreatorApplicationRatingAge = 0x34, - BootLoadingIconEnabled = 0x35, - PageScrollIndicatorEnabled = 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, - MediaPlayerOfflineEnabled = 0x43, -}; - -enum class WebArgOutputTLVType : u16 { - ShareExitReason = 0x1, - LastURL = 0x2, - LastURLSize = 0x3, - SharePostResult = 0x4, - PostServiceName = 0x5, - PostServiceNameSize = 0x6, - PostID = 0x7, - PostIDSize = 0x8, - MediaPlayerAutoClosedByCompletion = 0x9, -}; - -enum class DocumentKind : u32 { - OfflineHtmlPage = 1, - ApplicationLegalInformation = 2, - SystemDataPage = 3, -}; - -enum class ShareStartPage : u32 { - Default, - Settings, -}; - -enum class BootDisplayKind : u32 { - Default, - White, - Black, -}; - -enum class BackgroundKind : u32 { - Default, -}; - -enum class LeftStickMode : u32 { - Pointer, - Cursor, -}; - -enum class WebSessionBootMode : u32 { - AllForeground, - AllForegroundInitiallyHidden, -}; - -struct WebArgHeader { - u16 total_tlv_entries{}; - INSERT_PADDING_BYTES(2); - ShimKind shim_kind{}; -}; -static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size."); - -struct WebArgInputTLV { - WebArgInputTLVType input_tlv_type{}; - u16 arg_data_size{}; - INSERT_PADDING_WORDS(1); -}; -static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size."); - -struct WebArgOutputTLV { - WebArgOutputTLVType output_tlv_type{}; - u16 arg_data_size{}; - INSERT_PADDING_WORDS(1); -}; -static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size."); - -struct WebCommonReturnValue { - WebExitReason exit_reason{}; - INSERT_PADDING_WORDS(1); - std::array last_url{}; - u64 last_url_size{}; -}; -static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); - -using WebArgInputTLVMap = std::unordered_map>; - -} // namespace Service::AM::Applets -- cgit v1.2.3 From c6d7da88c7ab125279ea4ccad0e3e839632b2f7a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 14 Jul 2021 00:52:17 -0400 Subject: service: Append service name prefix to common filenames --- src/core/CMakeLists.txt | 44 +- src/core/core.cpp | 4 +- src/core/hardware_interrupt_manager.cpp | 2 +- src/core/hle/service/acc/acc.cpp | 2 +- src/core/hle/service/acc/acc.h | 2 +- src/core/hle/service/am/am.cpp | 4 +- src/core/hle/service/apm/apm.cpp | 2 +- src/core/hle/service/apm/apm_controller.cpp | 89 ++++ src/core/hle/service/apm/apm_controller.h | 70 +++ src/core/hle/service/apm/apm_interface.cpp | 138 +++++ src/core/hle/service/apm/apm_interface.h | 43 ++ src/core/hle/service/apm/controller.cpp | 89 ---- src/core/hle/service/apm/controller.h | 70 --- src/core/hle/service/apm/interface.cpp | 138 ----- src/core/hle/service/apm/interface.h | 43 -- src/core/hle/service/bcat/bcat.h | 2 +- src/core/hle/service/bcat/bcat_module.cpp | 610 +++++++++++++++++++++++ src/core/hle/service/bcat/bcat_module.h | 48 ++ src/core/hle/service/bcat/module.cpp | 610 ----------------------- src/core/hle/service/bcat/module.h | 48 -- src/core/hle/service/friend/friend.cpp | 2 +- src/core/hle/service/friend/friend_interface.cpp | 21 + src/core/hle/service/friend/friend_interface.h | 17 + src/core/hle/service/friend/interface.cpp | 21 - src/core/hle/service/friend/interface.h | 17 - src/core/hle/service/glue/arp.cpp | 2 +- src/core/hle/service/glue/glue_manager.cpp | 78 +++ src/core/hle/service/glue/glue_manager.h | 63 +++ src/core/hle/service/glue/manager.cpp | 78 --- src/core/hle/service/glue/manager.h | 63 --- src/core/hle/service/mii/manager.cpp | 465 ----------------- src/core/hle/service/mii/manager.h | 333 ------------- src/core/hle/service/mii/mii.cpp | 2 +- src/core/hle/service/mii/mii_manager.cpp | 465 +++++++++++++++++ src/core/hle/service/mii/mii_manager.h | 333 +++++++++++++ src/core/hle/service/mii/raw_data.h | 2 +- src/core/hle/service/nvdrv/interface.cpp | 259 ---------- src/core/hle/service/nvdrv/interface.h | 45 -- src/core/hle/service/nvdrv/nvdrv.cpp | 2 +- src/core/hle/service/nvdrv/nvdrv_interface.cpp | 259 ++++++++++ src/core/hle/service/nvdrv/nvdrv_interface.h | 45 ++ src/core/hle/service/pctl/module.cpp | 406 --------------- src/core/hle/service/pctl/module.h | 49 -- src/core/hle/service/pctl/pctl.h | 2 +- src/core/hle/service/pctl/pctl_module.cpp | 406 +++++++++++++++ src/core/hle/service/pctl/pctl_module.h | 49 ++ src/core/hle/service/service.cpp | 6 +- src/core/hle/service/sm/controller.cpp | 80 --- src/core/hle/service/sm/controller.h | 27 - src/core/hle/service/sm/sm.cpp | 2 +- src/core/hle/service/sm/sm_controller.cpp | 80 +++ src/core/hle/service/sm/sm_controller.h | 27 + src/core/hle/service/spl/csrng.h | 2 +- src/core/hle/service/spl/module.cpp | 175 ------- src/core/hle/service/spl/module.h | 48 -- src/core/hle/service/spl/spl.h | 2 +- src/core/hle/service/spl/spl_module.cpp | 175 +++++++ src/core/hle/service/spl/spl_module.h | 48 ++ src/core/hle/service/time/interface.cpp | 42 -- src/core/hle/service/time/interface.h | 21 - src/core/hle/service/time/time.cpp | 2 +- src/core/hle/service/time/time_interface.cpp | 42 ++ src/core/hle/service/time/time_interface.h | 21 + 63 files changed, 3171 insertions(+), 3171 deletions(-) create mode 100644 src/core/hle/service/apm/apm_controller.cpp create mode 100644 src/core/hle/service/apm/apm_controller.h create mode 100644 src/core/hle/service/apm/apm_interface.cpp create mode 100644 src/core/hle/service/apm/apm_interface.h delete mode 100644 src/core/hle/service/apm/controller.cpp delete mode 100644 src/core/hle/service/apm/controller.h delete mode 100644 src/core/hle/service/apm/interface.cpp delete mode 100644 src/core/hle/service/apm/interface.h create mode 100644 src/core/hle/service/bcat/bcat_module.cpp create mode 100644 src/core/hle/service/bcat/bcat_module.h delete mode 100644 src/core/hle/service/bcat/module.cpp delete mode 100644 src/core/hle/service/bcat/module.h create mode 100644 src/core/hle/service/friend/friend_interface.cpp create mode 100644 src/core/hle/service/friend/friend_interface.h delete mode 100644 src/core/hle/service/friend/interface.cpp delete mode 100644 src/core/hle/service/friend/interface.h create mode 100644 src/core/hle/service/glue/glue_manager.cpp create mode 100644 src/core/hle/service/glue/glue_manager.h delete mode 100644 src/core/hle/service/glue/manager.cpp delete mode 100644 src/core/hle/service/glue/manager.h delete mode 100644 src/core/hle/service/mii/manager.cpp delete mode 100644 src/core/hle/service/mii/manager.h create mode 100644 src/core/hle/service/mii/mii_manager.cpp create mode 100644 src/core/hle/service/mii/mii_manager.h delete mode 100644 src/core/hle/service/nvdrv/interface.cpp delete mode 100644 src/core/hle/service/nvdrv/interface.h create mode 100644 src/core/hle/service/nvdrv/nvdrv_interface.cpp create mode 100644 src/core/hle/service/nvdrv/nvdrv_interface.h delete mode 100644 src/core/hle/service/pctl/module.cpp delete mode 100644 src/core/hle/service/pctl/module.h create mode 100644 src/core/hle/service/pctl/pctl_module.cpp create mode 100644 src/core/hle/service/pctl/pctl_module.h delete mode 100644 src/core/hle/service/sm/controller.cpp delete mode 100644 src/core/hle/service/sm/controller.h create mode 100644 src/core/hle/service/sm/sm_controller.cpp create mode 100644 src/core/hle/service/sm/sm_controller.h delete mode 100644 src/core/hle/service/spl/module.cpp delete mode 100644 src/core/hle/service/spl/module.h create mode 100644 src/core/hle/service/spl/spl_module.cpp create mode 100644 src/core/hle/service/spl/spl_module.h delete mode 100644 src/core/hle/service/time/interface.cpp delete mode 100644 src/core/hle/service/time/interface.h create mode 100644 src/core/hle/service/time/time_interface.cpp create mode 100644 src/core/hle/service/time/time_interface.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 48b16f8f7..c7b899131 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -300,10 +300,10 @@ add_library(core STATIC hle/service/aoc/aoc_u.h hle/service/apm/apm.cpp hle/service/apm/apm.h - hle/service/apm/controller.cpp - hle/service/apm/controller.h - hle/service/apm/interface.cpp - hle/service/apm/interface.h + hle/service/apm/apm_controller.cpp + hle/service/apm/apm_controller.h + hle/service/apm/apm_interface.cpp + hle/service/apm/apm_interface.h hle/service/audio/audctl.cpp hle/service/audio/audctl.h hle/service/audio/auddbg.cpp @@ -335,8 +335,8 @@ add_library(core STATIC hle/service/bcat/backend/backend.h hle/service/bcat/bcat.cpp hle/service/bcat/bcat.h - hle/service/bcat/module.cpp - hle/service/bcat/module.h + hle/service/bcat/bcat_module.cpp + hle/service/bcat/bcat_module.h hle/service/bpc/bpc.cpp hle/service/bpc/bpc.h hle/service/btdrv/btdrv.cpp @@ -382,8 +382,8 @@ add_library(core STATIC hle/service/friend/errors.h hle/service/friend/friend.cpp hle/service/friend/friend.h - hle/service/friend/interface.cpp - hle/service/friend/interface.h + hle/service/friend/friend_interface.cpp + hle/service/friend/friend_interface.h hle/service/glue/arp.cpp hle/service/glue/arp.h hle/service/glue/bgtc.cpp @@ -393,8 +393,8 @@ add_library(core STATIC 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/glue/glue_manager.cpp + hle/service/glue/glue_manager.h hle/service/grc/grc.cpp hle/service/grc/grc.h hle/service/hid/hid.cpp @@ -435,10 +435,10 @@ add_library(core STATIC hle/service/lm/lm.h hle/service/mig/mig.cpp hle/service/mig/mig.h - hle/service/mii/manager.cpp - hle/service/mii/manager.h hle/service/mii/mii.cpp hle/service/mii/mii.h + hle/service/mii/mii_manager.cpp + hle/service/mii/mii_manager.h hle/service/mii/raw_data.cpp hle/service/mii/raw_data.h hle/service/mii/types.h @@ -486,11 +486,11 @@ add_library(core STATIC hle/service/nvdrv/devices/nvhost_vic.h hle/service/nvdrv/devices/nvmap.cpp hle/service/nvdrv/devices/nvmap.h - hle/service/nvdrv/interface.cpp - hle/service/nvdrv/interface.h hle/service/nvdrv/nvdata.h hle/service/nvdrv/nvdrv.cpp hle/service/nvdrv/nvdrv.h + hle/service/nvdrv/nvdrv_interface.cpp + hle/service/nvdrv/nvdrv_interface.h hle/service/nvdrv/nvmemp.cpp hle/service/nvdrv/nvmemp.h hle/service/nvdrv/syncpoint_manager.cpp @@ -503,10 +503,10 @@ add_library(core STATIC hle/service/olsc/olsc.h hle/service/pcie/pcie.cpp hle/service/pcie/pcie.h - hle/service/pctl/module.cpp - hle/service/pctl/module.h hle/service/pctl/pctl.cpp hle/service/pctl/pctl.h + hle/service/pctl/pctl_module.cpp + hle/service/pctl/pctl_module.h hle/service/pcv/pcv.cpp hle/service/pcv/pcv.h hle/service/pm/pm.cpp @@ -529,10 +529,10 @@ add_library(core STATIC hle/service/set/set_sys.h hle/service/set/settings.cpp hle/service/set/settings.h - hle/service/sm/controller.cpp - hle/service/sm/controller.h hle/service/sm/sm.cpp hle/service/sm/sm.h + hle/service/sm/sm_controller.cpp + hle/service/sm/sm_controller.h hle/service/sockets/bsd.cpp hle/service/sockets/bsd.h hle/service/sockets/ethc.cpp @@ -547,10 +547,10 @@ add_library(core STATIC hle/service/sockets/sockets_translate.h hle/service/spl/csrng.cpp hle/service/spl/csrng.h - hle/service/spl/module.cpp - hle/service/spl/module.h hle/service/spl/spl.cpp hle/service/spl/spl.h + hle/service/spl/spl_module.cpp + hle/service/spl/spl_module.h hle/service/spl/spl_results.h hle/service/spl/spl_types.h hle/service/ssl/ssl.cpp @@ -559,8 +559,6 @@ add_library(core STATIC hle/service/time/ephemeral_network_system_clock_context_writer.h hle/service/time/ephemeral_network_system_clock_core.h hle/service/time/errors.h - hle/service/time/interface.cpp - hle/service/time/interface.h hle/service/time/local_system_clock_context_writer.h hle/service/time/network_system_clock_context_writer.h hle/service/time/standard_local_system_clock_core.h @@ -578,6 +576,8 @@ add_library(core STATIC hle/service/time/tick_based_steady_clock_core.h hle/service/time/time.cpp hle/service/time/time.h + hle/service/time/time_interface.cpp + hle/service/time/time_interface.h hle/service/time/time_manager.cpp hle/service/time/time_manager.h hle/service/time/time_sharedmemory.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 891f1cb49..406320ed6 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -35,9 +35,9 @@ #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/physical_core.h" #include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/apm/controller.h" +#include "core/hle/service/apm/apm_controller.h" #include "core/hle/service/filesystem/filesystem.h" -#include "core/hle/service/glue/manager.h" +#include "core/hle/service/glue/glue_manager.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" diff --git a/src/core/hardware_interrupt_manager.cpp b/src/core/hardware_interrupt_manager.cpp index 645f26e91..290db505e 100644 --- a/src/core/hardware_interrupt_manager.cpp +++ b/src/core/hardware_interrupt_manager.cpp @@ -5,7 +5,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/hardware_interrupt_manager.h" -#include "core/hle/service/nvdrv/interface.h" +#include "core/hle/service/nvdrv/nvdrv_interface.h" #include "core/hle/service/sm/sm.h" namespace Core::Hardware { diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index d1c1fb71d..2e969f2a8 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -26,7 +26,7 @@ #include "core/hle/service/acc/errors.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/glue/arp.h" -#include "core/hle/service/glue/manager.h" +#include "core/hle/service/glue/glue_manager.h" #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 0e3ad8ec6..a83a480cd 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -5,7 +5,7 @@ #pragma once #include "common/uuid.h" -#include "core/hle/service/glue/manager.h" +#include "core/hle/service/glue/glue_manager.h" #include "core/hle/service/service.h" namespace Service::Account { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 41f1e6b68..a538f82e3 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -32,8 +32,8 @@ #include "core/hle/service/am/omm.h" #include "core/hle/service/am/spsm.h" #include "core/hle/service/am/tcap.h" -#include "core/hle/service/apm/controller.h" -#include "core/hle/service/apm/interface.h" +#include "core/hle/service/apm/apm_controller.h" +#include "core/hle/service/apm/apm_interface.h" #include "core/hle/service/bcat/backend/backend.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ns/ns.h" diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp index 97d6619dd..f5ebfe8d6 100644 --- a/src/core/hle/service/apm/apm.cpp +++ b/src/core/hle/service/apm/apm.cpp @@ -5,7 +5,7 @@ #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" -#include "core/hle/service/apm/interface.h" +#include "core/hle/service/apm/apm_interface.h" namespace Service::APM { diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp new file mode 100644 index 000000000..98839fe97 --- /dev/null +++ b/src/core/hle/service/apm/apm_controller.cpp @@ -0,0 +1,89 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/core_timing.h" +#include "core/hle/service/apm/apm_controller.h" + +namespace Service::APM { + +constexpr auto DEFAULT_PERFORMANCE_CONFIGURATION = PerformanceConfiguration::Config7; + +Controller::Controller(Core::Timing::CoreTiming& core_timing_) + : core_timing{core_timing_}, configs{ + {PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION}, + {PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION}, + } {} + +Controller::~Controller() = default; + +void Controller::SetPerformanceConfiguration(PerformanceMode mode, + PerformanceConfiguration config) { + static constexpr std::array, 16> config_to_speed{{ + {PerformanceConfiguration::Config1, 1020}, + {PerformanceConfiguration::Config2, 1020}, + {PerformanceConfiguration::Config3, 1224}, + {PerformanceConfiguration::Config4, 1020}, + {PerformanceConfiguration::Config5, 1020}, + {PerformanceConfiguration::Config6, 1224}, + {PerformanceConfiguration::Config7, 1020}, + {PerformanceConfiguration::Config8, 1020}, + {PerformanceConfiguration::Config9, 1020}, + {PerformanceConfiguration::Config10, 1020}, + {PerformanceConfiguration::Config11, 1020}, + {PerformanceConfiguration::Config12, 1020}, + {PerformanceConfiguration::Config13, 1785}, + {PerformanceConfiguration::Config14, 1785}, + {PerformanceConfiguration::Config15, 1020}, + {PerformanceConfiguration::Config16, 1020}, + }}; + + const auto iter = std::find_if(config_to_speed.cbegin(), config_to_speed.cend(), + [config](const auto& entry) { return entry.first == config; }); + + if (iter == config_to_speed.cend()) { + LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", config); + return; + } + + SetClockSpeed(iter->second); + configs.insert_or_assign(mode, config); +} + +void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { + constexpr std::array BOOST_MODE_TO_CONFIG_MAP{{ + PerformanceConfiguration::Config7, + PerformanceConfiguration::Config13, + PerformanceConfiguration::Config15, + }}; + + SetPerformanceConfiguration(PerformanceMode::Docked, + BOOST_MODE_TO_CONFIG_MAP.at(static_cast(mode))); +} + +PerformanceMode Controller::GetCurrentPerformanceMode() const { + return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked + : PerformanceMode::Handheld; +} + +PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { + if (configs.find(mode) == configs.end()) { + configs.insert_or_assign(mode, DEFAULT_PERFORMANCE_CONFIGURATION); + } + + return configs[mode]; +} + +void Controller::SetClockSpeed(u32 mhz) { + LOG_INFO(Service_APM, "called, mhz={:08X}", mhz); + // TODO(DarkLordZach): Actually signal core_timing to change clock speed. + // TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used. +} + +} // namespace Service::APM diff --git a/src/core/hle/service/apm/apm_controller.h b/src/core/hle/service/apm/apm_controller.h new file mode 100644 index 000000000..8d48e0104 --- /dev/null +++ b/src/core/hle/service/apm/apm_controller.h @@ -0,0 +1,70 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" + +namespace Core::Timing { +class CoreTiming; +} + +namespace Service::APM { + +enum class PerformanceConfiguration : u32 { + Config1 = 0x00010000, + Config2 = 0x00010001, + Config3 = 0x00010002, + Config4 = 0x00020000, + Config5 = 0x00020001, + Config6 = 0x00020002, + Config7 = 0x00020003, + Config8 = 0x00020004, + Config9 = 0x00020005, + Config10 = 0x00020006, + Config11 = 0x92220007, + Config12 = 0x92220008, + Config13 = 0x92220009, + Config14 = 0x9222000A, + Config15 = 0x9222000B, + Config16 = 0x9222000C, +}; + +enum class CpuBoostMode : u32 { + Disabled = 0, + Full = 1, // CPU + GPU -> Config 13, 14, 15, or 16 + Partial = 2, // GPU Only -> Config 15 or 16 +}; + +enum class PerformanceMode : u8 { + Handheld = 0, + Docked = 1, +}; + +// Class to manage the state and change of the emulated system performance. +// Specifically, this deals with PerformanceMode, which corresponds to the system being docked or +// undocked, and PerformanceConfig which specifies the exact CPU, GPU, and Memory clocks to operate +// at. Additionally, this manages 'Boost Mode', which allows games to temporarily overclock the +// system during times of high load -- this simply maps to different PerformanceConfigs to use. +class Controller { +public: + explicit Controller(Core::Timing::CoreTiming& core_timing_); + ~Controller(); + + void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config); + void SetFromCpuBoostMode(CpuBoostMode mode); + + PerformanceMode GetCurrentPerformanceMode() const; + PerformanceConfiguration GetCurrentPerformanceConfiguration(PerformanceMode mode); + +private: + void SetClockSpeed(u32 mhz); + + [[maybe_unused]] Core::Timing::CoreTiming& core_timing; + + std::map configs; +}; + +} // namespace Service::APM diff --git a/src/core/hle/service/apm/apm_interface.cpp b/src/core/hle/service/apm/apm_interface.cpp new file mode 100644 index 000000000..e58bad083 --- /dev/null +++ b/src/core/hle/service/apm/apm_interface.cpp @@ -0,0 +1,138 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/apm/apm.h" +#include "core/hle/service/apm/apm_controller.h" +#include "core/hle/service/apm/apm_interface.h" + +namespace Service::APM { + +class ISession final : public ServiceFramework { +public: + explicit ISession(Core::System& system_, Controller& controller_) + : ServiceFramework{system_, "ISession"}, controller{controller_} { + static const FunctionInfo functions[] = { + {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"}, + {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"}, + {2, nullptr, "SetCpuOverclockEnabled"}, + }; + RegisterHandlers(functions); + } + +private: + void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto mode = rp.PopEnum(); + const auto config = rp.PopEnum(); + LOG_DEBUG(Service_APM, "called mode={} config={}", mode, config); + + controller.SetPerformanceConfiguration(mode, config); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto mode = rp.PopEnum(); + LOG_DEBUG(Service_APM, "called mode={}", mode); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(controller.GetCurrentPerformanceConfiguration(mode)); + } + + Controller& controller; +}; + +APM::APM(Core::System& system_, std::shared_ptr apm_, Controller& controller_, + const char* name) + : ServiceFramework{system_, name}, apm(std::move(apm_)), controller{controller_} { + static const FunctionInfo functions[] = { + {0, &APM::OpenSession, "OpenSession"}, + {1, &APM::GetPerformanceMode, "GetPerformanceMode"}, + {6, &APM::IsCpuOverclockEnabled, "IsCpuOverclockEnabled"}, + }; + RegisterHandlers(functions); +} + +APM::~APM() = default; + +void APM::OpenSession(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_APM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, controller); +} + +void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_APM, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.PushEnum(controller.GetCurrentPerformanceMode()); +} + +void APM::IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_APM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(false); +} + +APM_Sys::APM_Sys(Core::System& system_, Controller& controller_) + : ServiceFramework{system_, "apm:sys"}, controller{controller_} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestPerformanceMode"}, + {1, &APM_Sys::GetPerformanceEvent, "GetPerformanceEvent"}, + {2, nullptr, "GetThrottlingState"}, + {3, nullptr, "GetLastThrottlingState"}, + {4, nullptr, "ClearLastThrottlingState"}, + {5, nullptr, "LoadAndApplySettings"}, + {6, &APM_Sys::SetCpuBoostMode, "SetCpuBoostMode"}, + {7, &APM_Sys::GetCurrentPerformanceConfiguration, "GetCurrentPerformanceConfiguration"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +APM_Sys::~APM_Sys() = default; + +void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_APM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, controller); +} + +void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto mode = rp.PopEnum(); + + LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); + + controller.SetFromCpuBoostMode(mode); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void APM_Sys::GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_APM, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum( + controller.GetCurrentPerformanceConfiguration(controller.GetCurrentPerformanceMode())); +} + +} // namespace Service::APM diff --git a/src/core/hle/service/apm/apm_interface.h b/src/core/hle/service/apm/apm_interface.h new file mode 100644 index 000000000..063ad5308 --- /dev/null +++ b/src/core/hle/service/apm/apm_interface.h @@ -0,0 +1,43 @@ +// Copyright 2018 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::APM { + +class Controller; +class Module; + +class APM final : public ServiceFramework { +public: + explicit APM(Core::System& system_, std::shared_ptr apm_, Controller& controller_, + const char* name); + ~APM() override; + +private: + void OpenSession(Kernel::HLERequestContext& ctx); + void GetPerformanceMode(Kernel::HLERequestContext& ctx); + void IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx); + + std::shared_ptr apm; + Controller& controller; +}; + +class APM_Sys final : public ServiceFramework { +public: + explicit APM_Sys(Core::System& system_, Controller& controller); + ~APM_Sys() override; + + void SetCpuBoostMode(Kernel::HLERequestContext& ctx); + +private: + void GetPerformanceEvent(Kernel::HLERequestContext& ctx); + void GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx); + + Controller& controller; +}; + +} // namespace Service::APM diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp deleted file mode 100644 index 8bfa7c0e4..000000000 --- a/src/core/hle/service/apm/controller.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include - -#include "common/logging/log.h" -#include "common/settings.h" -#include "core/core_timing.h" -#include "core/hle/service/apm/controller.h" - -namespace Service::APM { - -constexpr auto DEFAULT_PERFORMANCE_CONFIGURATION = PerformanceConfiguration::Config7; - -Controller::Controller(Core::Timing::CoreTiming& core_timing_) - : core_timing{core_timing_}, configs{ - {PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION}, - {PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION}, - } {} - -Controller::~Controller() = default; - -void Controller::SetPerformanceConfiguration(PerformanceMode mode, - PerformanceConfiguration config) { - static constexpr std::array, 16> config_to_speed{{ - {PerformanceConfiguration::Config1, 1020}, - {PerformanceConfiguration::Config2, 1020}, - {PerformanceConfiguration::Config3, 1224}, - {PerformanceConfiguration::Config4, 1020}, - {PerformanceConfiguration::Config5, 1020}, - {PerformanceConfiguration::Config6, 1224}, - {PerformanceConfiguration::Config7, 1020}, - {PerformanceConfiguration::Config8, 1020}, - {PerformanceConfiguration::Config9, 1020}, - {PerformanceConfiguration::Config10, 1020}, - {PerformanceConfiguration::Config11, 1020}, - {PerformanceConfiguration::Config12, 1020}, - {PerformanceConfiguration::Config13, 1785}, - {PerformanceConfiguration::Config14, 1785}, - {PerformanceConfiguration::Config15, 1020}, - {PerformanceConfiguration::Config16, 1020}, - }}; - - const auto iter = std::find_if(config_to_speed.cbegin(), config_to_speed.cend(), - [config](const auto& entry) { return entry.first == config; }); - - if (iter == config_to_speed.cend()) { - LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", config); - return; - } - - SetClockSpeed(iter->second); - configs.insert_or_assign(mode, config); -} - -void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { - constexpr std::array BOOST_MODE_TO_CONFIG_MAP{{ - PerformanceConfiguration::Config7, - PerformanceConfiguration::Config13, - PerformanceConfiguration::Config15, - }}; - - SetPerformanceConfiguration(PerformanceMode::Docked, - BOOST_MODE_TO_CONFIG_MAP.at(static_cast(mode))); -} - -PerformanceMode Controller::GetCurrentPerformanceMode() const { - return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked - : PerformanceMode::Handheld; -} - -PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { - if (configs.find(mode) == configs.end()) { - configs.insert_or_assign(mode, DEFAULT_PERFORMANCE_CONFIGURATION); - } - - return configs[mode]; -} - -void Controller::SetClockSpeed(u32 mhz) { - LOG_INFO(Service_APM, "called, mhz={:08X}", mhz); - // TODO(DarkLordZach): Actually signal core_timing to change clock speed. - // TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used. -} - -} // namespace Service::APM diff --git a/src/core/hle/service/apm/controller.h b/src/core/hle/service/apm/controller.h deleted file mode 100644 index 8d48e0104..000000000 --- a/src/core/hle/service/apm/controller.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "common/common_types.h" - -namespace Core::Timing { -class CoreTiming; -} - -namespace Service::APM { - -enum class PerformanceConfiguration : u32 { - Config1 = 0x00010000, - Config2 = 0x00010001, - Config3 = 0x00010002, - Config4 = 0x00020000, - Config5 = 0x00020001, - Config6 = 0x00020002, - Config7 = 0x00020003, - Config8 = 0x00020004, - Config9 = 0x00020005, - Config10 = 0x00020006, - Config11 = 0x92220007, - Config12 = 0x92220008, - Config13 = 0x92220009, - Config14 = 0x9222000A, - Config15 = 0x9222000B, - Config16 = 0x9222000C, -}; - -enum class CpuBoostMode : u32 { - Disabled = 0, - Full = 1, // CPU + GPU -> Config 13, 14, 15, or 16 - Partial = 2, // GPU Only -> Config 15 or 16 -}; - -enum class PerformanceMode : u8 { - Handheld = 0, - Docked = 1, -}; - -// Class to manage the state and change of the emulated system performance. -// Specifically, this deals with PerformanceMode, which corresponds to the system being docked or -// undocked, and PerformanceConfig which specifies the exact CPU, GPU, and Memory clocks to operate -// at. Additionally, this manages 'Boost Mode', which allows games to temporarily overclock the -// system during times of high load -- this simply maps to different PerformanceConfigs to use. -class Controller { -public: - explicit Controller(Core::Timing::CoreTiming& core_timing_); - ~Controller(); - - void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config); - void SetFromCpuBoostMode(CpuBoostMode mode); - - PerformanceMode GetCurrentPerformanceMode() const; - PerformanceConfiguration GetCurrentPerformanceConfiguration(PerformanceMode mode); - -private: - void SetClockSpeed(u32 mhz); - - [[maybe_unused]] Core::Timing::CoreTiming& core_timing; - - std::map configs; -}; - -} // namespace Service::APM diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp deleted file mode 100644 index d69ddd135..000000000 --- a/src/core/hle/service/apm/interface.cpp +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/apm/apm.h" -#include "core/hle/service/apm/controller.h" -#include "core/hle/service/apm/interface.h" - -namespace Service::APM { - -class ISession final : public ServiceFramework { -public: - explicit ISession(Core::System& system_, Controller& controller_) - : ServiceFramework{system_, "ISession"}, controller{controller_} { - static const FunctionInfo functions[] = { - {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"}, - {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"}, - {2, nullptr, "SetCpuOverclockEnabled"}, - }; - RegisterHandlers(functions); - } - -private: - void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - const auto mode = rp.PopEnum(); - const auto config = rp.PopEnum(); - LOG_DEBUG(Service_APM, "called mode={} config={}", mode, config); - - controller.SetPerformanceConfiguration(mode, config); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - const auto mode = rp.PopEnum(); - LOG_DEBUG(Service_APM, "called mode={}", mode); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(controller.GetCurrentPerformanceConfiguration(mode)); - } - - Controller& controller; -}; - -APM::APM(Core::System& system_, std::shared_ptr apm_, Controller& controller_, - const char* name) - : ServiceFramework{system_, name}, apm(std::move(apm_)), controller{controller_} { - static const FunctionInfo functions[] = { - {0, &APM::OpenSession, "OpenSession"}, - {1, &APM::GetPerformanceMode, "GetPerformanceMode"}, - {6, &APM::IsCpuOverclockEnabled, "IsCpuOverclockEnabled"}, - }; - RegisterHandlers(functions); -} - -APM::~APM() = default; - -void APM::OpenSession(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_APM, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, controller); -} - -void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_APM, "called"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.PushEnum(controller.GetCurrentPerformanceMode()); -} - -void APM::IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_APM, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(false); -} - -APM_Sys::APM_Sys(Core::System& system_, Controller& controller_) - : ServiceFramework{system_, "apm:sys"}, controller{controller_} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "RequestPerformanceMode"}, - {1, &APM_Sys::GetPerformanceEvent, "GetPerformanceEvent"}, - {2, nullptr, "GetThrottlingState"}, - {3, nullptr, "GetLastThrottlingState"}, - {4, nullptr, "ClearLastThrottlingState"}, - {5, nullptr, "LoadAndApplySettings"}, - {6, &APM_Sys::SetCpuBoostMode, "SetCpuBoostMode"}, - {7, &APM_Sys::GetCurrentPerformanceConfiguration, "GetCurrentPerformanceConfiguration"}, - }; - // clang-format on - - RegisterHandlers(functions); -} - -APM_Sys::~APM_Sys() = default; - -void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_APM, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, controller); -} - -void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto mode = rp.PopEnum(); - - LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); - - controller.SetFromCpuBoostMode(mode); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} - -void APM_Sys::GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_APM, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum( - controller.GetCurrentPerformanceConfiguration(controller.GetCurrentPerformanceMode())); -} - -} // namespace Service::APM diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h deleted file mode 100644 index 063ad5308..000000000 --- a/src/core/hle/service/apm/interface.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2018 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::APM { - -class Controller; -class Module; - -class APM final : public ServiceFramework { -public: - explicit APM(Core::System& system_, std::shared_ptr apm_, Controller& controller_, - const char* name); - ~APM() override; - -private: - void OpenSession(Kernel::HLERequestContext& ctx); - void GetPerformanceMode(Kernel::HLERequestContext& ctx); - void IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx); - - std::shared_ptr apm; - Controller& controller; -}; - -class APM_Sys final : public ServiceFramework { -public: - explicit APM_Sys(Core::System& system_, Controller& controller); - ~APM_Sys() override; - - void SetCpuBoostMode(Kernel::HLERequestContext& ctx); - -private: - void GetPerformanceEvent(Kernel::HLERequestContext& ctx); - void GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx); - - Controller& controller; -}; - -} // namespace Service::APM diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h index d72798980..1eba477da 100644 --- a/src/core/hle/service/bcat/bcat.h +++ b/src/core/hle/service/bcat/bcat.h @@ -4,7 +4,7 @@ #pragma once -#include "core/hle/service/bcat/module.h" +#include "core/hle/service/bcat/bcat_module.h" namespace Core { class System; diff --git a/src/core/hle/service/bcat/bcat_module.cpp b/src/core/hle/service/bcat/bcat_module.cpp new file mode 100644 index 000000000..72294eb2e --- /dev/null +++ b/src/core/hle/service/bcat/bcat_module.cpp @@ -0,0 +1,610 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "backend/boxcat.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/vfs.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" +#include "core/hle/service/bcat/backend/backend.h" +#include "core/hle/service/bcat/bcat.h" +#include "core/hle/service/bcat/bcat_module.h" +#include "core/hle/service/filesystem/filesystem.h" + +namespace Service::BCAT { + +constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1}; +constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2}; +constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6}; +constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7}; + +// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files +// and if any of them have a non-zero result it just forwards that result. This is the FS error code +// for permission denied, which is the closest approximation of this scenario. +constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400}; + +using BCATDigest = std::array; + +namespace { + +u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) { + u64 out{}; + std::memcpy(&out, id.data(), sizeof(u64)); + return out; +} + +// The digest is only used to determine if a file is unique compared to others of the same name. +// Since the algorithm isn't ever checked in game, MD5 is safe. +BCATDigest DigestFile(const FileSys::VirtualFile& file) { + BCATDigest out{}; + const auto bytes = file->ReadAllBytes(); + mbedtls_md5_ret(bytes.data(), bytes.size(), out.data()); + return out; +} + +// For a name to be valid it must be non-empty, must have a null terminating character as the final +// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if +// file. +bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array name, + char match_char) { + const auto null_chars = std::count(name.begin(), name.end(), 0); + const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) { + return !std::isalnum(static_cast(c)) && c != '_' && c != match_char && c != '\0'; + }); + if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') { + LOG_ERROR(Service_BCAT, "Name passed was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return false; + } + + return true; +} + +bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) { + return VerifyNameValidInternal(ctx, name, '-'); +} + +bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) { + return VerifyNameValidInternal(ctx, name, '.'); +} + +} // Anonymous namespace + +struct DeliveryCacheDirectoryEntry { + FileName name; + u64 size; + BCATDigest digest; +}; + +class IDeliveryCacheProgressService final : public ServiceFramework { +public: + explicit IDeliveryCacheProgressService(Core::System& system_, Kernel::KReadableEvent& event_, + const DeliveryCacheProgressImpl& impl_) + : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{event_}, impl{impl_} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"}, + {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void GetEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(event); + } + + void GetImpl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + ctx.WriteBuffer(impl); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + Kernel::KReadableEvent& event; + const DeliveryCacheProgressImpl& impl; +}; + +class IBcatService final : public ServiceFramework { +public: + explicit IBcatService(Core::System& system_, Backend& backend_) + : ServiceFramework{system_, "IBcatService"}, backend{backend_}, + progress{{ + ProgressServiceBackend{system_.Kernel(), "Normal"}, + ProgressServiceBackend{system_.Kernel(), "Directory"}, + }} { + // clang-format off + static const FunctionInfo functions[] = { + {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"}, + {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"}, + {10200, nullptr, "CancelSyncDeliveryCacheRequest"}, + {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"}, + {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"}, + {20300, nullptr, "GetDeliveryCacheStorageUpdateNotifier"}, + {20301, nullptr, "RequestSuspendDeliveryTask"}, + {20400, nullptr, "RegisterSystemApplicationDeliveryTask"}, + {20401, nullptr, "UnregisterSystemApplicationDeliveryTask"}, + {20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"}, + {30100, &IBcatService::SetPassphrase, "SetPassphrase"}, + {30101, nullptr, "Unknown"}, + {30102, nullptr, "Unknown2"}, + {30200, nullptr, "RegisterBackgroundDeliveryTask"}, + {30201, nullptr, "UnregisterBackgroundDeliveryTask"}, + {30202, nullptr, "BlockDeliveryTask"}, + {30203, nullptr, "UnblockDeliveryTask"}, + {30210, nullptr, "SetDeliveryTaskTimer"}, + {30300, nullptr, "RegisterSystemApplicationDeliveryTasks"}, + {90100, nullptr, "EnumerateBackgroundDeliveryTask"}, + {90101, nullptr, "Unknown90101"}, + {90200, nullptr, "GetDeliveryList"}, + {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"}, + {90202, nullptr, "ClearDeliveryTaskSubscriptionStatus"}, + {90300, nullptr, "GetPushNotificationLog"}, + {90301, nullptr, "Unknown90301"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + enum class SyncType { + Normal, + Directory, + Count, + }; + + std::shared_ptr CreateProgressService(SyncType type) { + auto& progress_backend{GetProgressBackend(type)}; + return std::make_shared(system, progress_backend.GetEvent(), + progress_backend.GetImpl()); + } + + void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + backend.Synchronize({system.CurrentProcess()->GetTitleID(), + GetCurrentBuildID(system.GetCurrentProcessBuildID())}, + GetProgressBackend(SyncType::Normal)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(CreateProgressService(SyncType::Normal)); + } + + void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto name_raw = rp.PopRaw(); + const auto name = + Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, name={}", name); + + backend.SynchronizeDirectory({system.CurrentProcess()->GetTitleID(), + GetCurrentBuildID(system.GetCurrentProcessBuildID())}, + name, GetProgressBackend(SyncType::Directory)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(CreateProgressService(SyncType::Directory)); + } + + void SetPassphrase(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw(); + + const auto passphrase_raw = ctx.ReadBuffer(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, + Common::HexToString(passphrase_raw)); + + if (title_id == 0) { + LOG_ERROR(Service_BCAT, "Invalid title ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + } + + if (passphrase_raw.size() > 0x40) { + LOG_ERROR(Service_BCAT, "Passphrase too large!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + Passphrase passphrase{}; + std::memcpy(passphrase.data(), passphrase_raw.data(), + std::min(passphrase.size(), passphrase_raw.size())); + + backend.SetPassphrase(title_id, passphrase); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); + + if (title_id == 0) { + LOG_ERROR(Service_BCAT, "Invalid title ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + if (!backend.Clear(title_id)) { + LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_CLEAR_CACHE); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + ProgressServiceBackend& GetProgressBackend(SyncType type) { + return progress.at(static_cast(type)); + } + + const ProgressServiceBackend& GetProgressBackend(SyncType type) const { + return progress.at(static_cast(type)); + } + + Backend& backend; + std::array(SyncType::Count)> progress; +}; + +void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, *backend); +} + +class IDeliveryCacheFileService final : public ServiceFramework { +public: + explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheFileService::Open, "Open"}, + {1, &IDeliveryCacheFileService::Read, "Read"}, + {2, &IDeliveryCacheFileService::GetSize, "GetSize"}, + {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Open(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto dir_name_raw = rp.PopRaw(); + const auto file_name_raw = rp.PopRaw(); + + const auto dir_name = + Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size()); + const auto file_name = + Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name); + + if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw)) + return; + + if (current_file != nullptr) { + LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_ENTITY_ALREADY_OPEN); + return; + } + + const auto dir = root->GetSubdirectory(dir_name); + + if (dir == nullptr) { + LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + current_file = dir->GetFile(file_name); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto offset{rp.PopRaw()}; + + auto size = ctx.GetWriteBufferSize(); + + LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + size = std::min(current_file->GetSize() - offset, size); + const auto buffer = current_file->ReadBytes(size, offset); + ctx.WriteBuffer(buffer); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(buffer.size()); + } + + void GetSize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(current_file->GetSize()); + } + + void GetDigest(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(DigestFile(current_file)); + } + + FileSys::VirtualDir root; + FileSys::VirtualFile current_file; +}; + +class IDeliveryCacheDirectoryService final + : public ServiceFramework { +public: + explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheDirectoryService::Open, "Open"}, + {1, &IDeliveryCacheDirectoryService::Read, "Read"}, + {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Open(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto name_raw = rp.PopRaw(); + const auto name = + Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, name={}", name); + + if (!VerifyNameValidDir(ctx, name_raw)) + return; + + if (current_dir != nullptr) { + LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_ENTITY_ALREADY_OPEN); + return; + } + + current_dir = root->GetSubdirectory(name); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Read(Kernel::HLERequestContext& ctx) { + auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry); + + LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "There is no open directory!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + return; + } + + const auto files = current_dir->GetFiles(); + write_size = std::min(write_size, files.size()); + std::vector entries(write_size); + std::transform( + files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) { + FileName name{}; + std::memcpy(name.data(), file->GetName().data(), + std::min(file->GetName().size(), name.size())); + return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)}; + }); + + ctx.WriteBuffer(entries); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(write_size * sizeof(DeliveryCacheDirectoryEntry))); + } + + void GetCount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "There is no open directory!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + return; + } + + const auto files = current_dir->GetFiles(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(files.size())); + } + + FileSys::VirtualDir root; + FileSys::VirtualDir current_dir; +}; + +class IDeliveryCacheStorageService final : public ServiceFramework { +public: + explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"}, + {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"}, + {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"}, + }; + // clang-format on + + RegisterHandlers(functions); + + for (const auto& subdir : root->GetSubdirectories()) { + DirectoryName name{}; + std::memcpy(name.data(), subdir->GetName().data(), + std::min(sizeof(DirectoryName) - 1, subdir->GetName().size())); + entries.push_back(name); + } + } + +private: + void CreateFileService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, root); + } + + void CreateDirectoryService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, root); + } + + void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) { + auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName); + + LOG_DEBUG(Service_BCAT, "called, size={:016X}", size); + + size = std::min(size, entries.size() - next_read_index); + ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName)); + next_read_index += size; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(size)); + } + + FileSys::VirtualDir root; + std::vector entries; + u64 next_read_index = 0; +}; + +void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + const auto title_id = system.CurrentProcess()->GetTitleID(); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, fsc.GetBCATDirectory(title_id)); +} + +void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( + Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, fsc.GetBCATDirectory(title_id)); +} + +std::unique_ptr CreateBackendFromSettings([[maybe_unused]] Core::System& system, + DirectoryGetter getter) { +#ifdef YUZU_ENABLE_BOXCAT + if (Settings::values.bcat_backend.GetValue() == "boxcat") { + return std::make_unique(system.GetAppletManager(), std::move(getter)); + } +#endif + + return std::make_unique(std::move(getter)); +} + +Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, + FileSystem::FileSystemController& fsc_, const char* name) + : ServiceFramework{system_, name}, fsc{fsc_}, module{std::move(module_)}, + backend{CreateBackendFromSettings(system_, + [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })} {} + +Module::Interface::~Interface() = default; + +void InstallInterfaces(Core::System& system) { + auto module = std::make_shared(); + std::make_shared(system, module, system.GetFileSystemController(), "bcat:a") + ->InstallAsService(system.ServiceManager()); + std::make_shared(system, module, system.GetFileSystemController(), "bcat:m") + ->InstallAsService(system.ServiceManager()); + std::make_shared(system, module, system.GetFileSystemController(), "bcat:u") + ->InstallAsService(system.ServiceManager()); + std::make_shared(system, module, system.GetFileSystemController(), "bcat:s") + ->InstallAsService(system.ServiceManager()); +} + +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/bcat_module.h b/src/core/hle/service/bcat/bcat_module.h new file mode 100644 index 000000000..738731c06 --- /dev/null +++ b/src/core/hle/service/bcat/bcat_module.h @@ -0,0 +1,48 @@ +// Copyright 2018 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 Core { +class System; +} + +namespace Service { + +namespace FileSystem { +class FileSystemController; +} // namespace FileSystem + +namespace BCAT { + +class Backend; + +class Module final { +public: + class Interface : public ServiceFramework { + public: + explicit Interface(Core::System& system_, std::shared_ptr module_, + FileSystem::FileSystemController& fsc_, const char* name); + ~Interface() override; + + void CreateBcatService(Kernel::HLERequestContext& ctx); + void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx); + void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx); + + protected: + FileSystem::FileSystemController& fsc; + + std::shared_ptr module; + std::unique_ptr backend; + }; +}; + +/// Registers all BCAT services with the specified service manager. +void InstallInterfaces(Core::System& system); + +} // namespace BCAT + +} // namespace Service diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp deleted file mode 100644 index f85444da8..000000000 --- a/src/core/hle/service/bcat/module.cpp +++ /dev/null @@ -1,610 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include "backend/boxcat.h" -#include "common/hex_util.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/file_sys/vfs.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/k_process.h" -#include "core/hle/kernel/k_readable_event.h" -#include "core/hle/kernel/k_writable_event.h" -#include "core/hle/service/bcat/backend/backend.h" -#include "core/hle/service/bcat/bcat.h" -#include "core/hle/service/bcat/module.h" -#include "core/hle/service/filesystem/filesystem.h" - -namespace Service::BCAT { - -constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1}; -constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2}; -constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6}; -constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7}; - -// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files -// and if any of them have a non-zero result it just forwards that result. This is the FS error code -// for permission denied, which is the closest approximation of this scenario. -constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400}; - -using BCATDigest = std::array; - -namespace { - -u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) { - u64 out{}; - std::memcpy(&out, id.data(), sizeof(u64)); - return out; -} - -// The digest is only used to determine if a file is unique compared to others of the same name. -// Since the algorithm isn't ever checked in game, MD5 is safe. -BCATDigest DigestFile(const FileSys::VirtualFile& file) { - BCATDigest out{}; - const auto bytes = file->ReadAllBytes(); - mbedtls_md5_ret(bytes.data(), bytes.size(), out.data()); - return out; -} - -// For a name to be valid it must be non-empty, must have a null terminating character as the final -// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if -// file. -bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array name, - char match_char) { - const auto null_chars = std::count(name.begin(), name.end(), 0); - const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) { - return !std::isalnum(static_cast(c)) && c != '_' && c != match_char && c != '\0'; - }); - if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') { - LOG_ERROR(Service_BCAT, "Name passed was invalid!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - return false; - } - - return true; -} - -bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) { - return VerifyNameValidInternal(ctx, name, '-'); -} - -bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) { - return VerifyNameValidInternal(ctx, name, '.'); -} - -} // Anonymous namespace - -struct DeliveryCacheDirectoryEntry { - FileName name; - u64 size; - BCATDigest digest; -}; - -class IDeliveryCacheProgressService final : public ServiceFramework { -public: - explicit IDeliveryCacheProgressService(Core::System& system_, Kernel::KReadableEvent& event_, - const DeliveryCacheProgressImpl& impl_) - : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{event_}, impl{impl_} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"}, - {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void GetEvent(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(event); - } - - void GetImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - ctx.WriteBuffer(impl); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - Kernel::KReadableEvent& event; - const DeliveryCacheProgressImpl& impl; -}; - -class IBcatService final : public ServiceFramework { -public: - explicit IBcatService(Core::System& system_, Backend& backend_) - : ServiceFramework{system_, "IBcatService"}, backend{backend_}, - progress{{ - ProgressServiceBackend{system_.Kernel(), "Normal"}, - ProgressServiceBackend{system_.Kernel(), "Directory"}, - }} { - // clang-format off - static const FunctionInfo functions[] = { - {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"}, - {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"}, - {10200, nullptr, "CancelSyncDeliveryCacheRequest"}, - {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"}, - {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"}, - {20300, nullptr, "GetDeliveryCacheStorageUpdateNotifier"}, - {20301, nullptr, "RequestSuspendDeliveryTask"}, - {20400, nullptr, "RegisterSystemApplicationDeliveryTask"}, - {20401, nullptr, "UnregisterSystemApplicationDeliveryTask"}, - {20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"}, - {30100, &IBcatService::SetPassphrase, "SetPassphrase"}, - {30101, nullptr, "Unknown"}, - {30102, nullptr, "Unknown2"}, - {30200, nullptr, "RegisterBackgroundDeliveryTask"}, - {30201, nullptr, "UnregisterBackgroundDeliveryTask"}, - {30202, nullptr, "BlockDeliveryTask"}, - {30203, nullptr, "UnblockDeliveryTask"}, - {30210, nullptr, "SetDeliveryTaskTimer"}, - {30300, nullptr, "RegisterSystemApplicationDeliveryTasks"}, - {90100, nullptr, "EnumerateBackgroundDeliveryTask"}, - {90101, nullptr, "Unknown90101"}, - {90200, nullptr, "GetDeliveryList"}, - {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"}, - {90202, nullptr, "ClearDeliveryTaskSubscriptionStatus"}, - {90300, nullptr, "GetPushNotificationLog"}, - {90301, nullptr, "Unknown90301"}, - }; - // clang-format on - RegisterHandlers(functions); - } - -private: - enum class SyncType { - Normal, - Directory, - Count, - }; - - std::shared_ptr CreateProgressService(SyncType type) { - auto& progress_backend{GetProgressBackend(type)}; - return std::make_shared(system, progress_backend.GetEvent(), - progress_backend.GetImpl()); - } - - void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - backend.Synchronize({system.CurrentProcess()->GetTitleID(), - GetCurrentBuildID(system.GetCurrentProcessBuildID())}, - GetProgressBackend(SyncType::Normal)); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(CreateProgressService(SyncType::Normal)); - } - - void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto name_raw = rp.PopRaw(); - const auto name = - Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size()); - - LOG_DEBUG(Service_BCAT, "called, name={}", name); - - backend.SynchronizeDirectory({system.CurrentProcess()->GetTitleID(), - GetCurrentBuildID(system.GetCurrentProcessBuildID())}, - name, GetProgressBackend(SyncType::Directory)); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(CreateProgressService(SyncType::Directory)); - } - - void SetPassphrase(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto title_id = rp.PopRaw(); - - const auto passphrase_raw = ctx.ReadBuffer(); - - LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, - Common::HexToString(passphrase_raw)); - - if (title_id == 0) { - LOG_ERROR(Service_BCAT, "Invalid title ID!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - } - - if (passphrase_raw.size() > 0x40) { - LOG_ERROR(Service_BCAT, "Passphrase too large!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - return; - } - - Passphrase passphrase{}; - std::memcpy(passphrase.data(), passphrase_raw.data(), - std::min(passphrase.size(), passphrase_raw.size())); - - backend.SetPassphrase(title_id, passphrase); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto title_id = rp.PopRaw(); - - LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); - - if (title_id == 0) { - LOG_ERROR(Service_BCAT, "Invalid title ID!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - return; - } - - if (!backend.Clear(title_id)) { - LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_FAILED_CLEAR_CACHE); - return; - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - ProgressServiceBackend& GetProgressBackend(SyncType type) { - return progress.at(static_cast(type)); - } - - const ProgressServiceBackend& GetProgressBackend(SyncType type) const { - return progress.at(static_cast(type)); - } - - Backend& backend; - std::array(SyncType::Count)> progress; -}; - -void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, *backend); -} - -class IDeliveryCacheFileService final : public ServiceFramework { -public: - explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_) - : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IDeliveryCacheFileService::Open, "Open"}, - {1, &IDeliveryCacheFileService::Read, "Read"}, - {2, &IDeliveryCacheFileService::GetSize, "GetSize"}, - {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void Open(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto dir_name_raw = rp.PopRaw(); - const auto file_name_raw = rp.PopRaw(); - - const auto dir_name = - Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size()); - const auto file_name = - Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size()); - - LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name); - - if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw)) - return; - - if (current_file != nullptr) { - LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_ENTITY_ALREADY_OPEN); - return; - } - - const auto dir = root->GetSubdirectory(dir_name); - - if (dir == nullptr) { - LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_FAILED_OPEN_ENTITY); - return; - } - - current_file = dir->GetFile(file_name); - - if (current_file == nullptr) { - LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_FAILED_OPEN_ENTITY); - return; - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - void Read(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto offset{rp.PopRaw()}; - - auto size = ctx.GetWriteBufferSize(); - - LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size); - - if (current_file == nullptr) { - LOG_ERROR(Service_BCAT, "There is no file currently open!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_NO_OPEN_ENTITY); - } - - size = std::min(current_file->GetSize() - offset, size); - const auto buffer = current_file->ReadBytes(size, offset); - ctx.WriteBuffer(buffer); - - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); - rb.Push(buffer.size()); - } - - void GetSize(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - if (current_file == nullptr) { - LOG_ERROR(Service_BCAT, "There is no file currently open!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_NO_OPEN_ENTITY); - } - - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); - rb.Push(current_file->GetSize()); - } - - void GetDigest(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - if (current_file == nullptr) { - LOG_ERROR(Service_BCAT, "There is no file currently open!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_NO_OPEN_ENTITY); - } - - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(ResultSuccess); - rb.PushRaw(DigestFile(current_file)); - } - - FileSys::VirtualDir root; - FileSys::VirtualFile current_file; -}; - -class IDeliveryCacheDirectoryService final - : public ServiceFramework { -public: - explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_) - : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IDeliveryCacheDirectoryService::Open, "Open"}, - {1, &IDeliveryCacheDirectoryService::Read, "Read"}, - {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void Open(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto name_raw = rp.PopRaw(); - const auto name = - Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size()); - - LOG_DEBUG(Service_BCAT, "called, name={}", name); - - if (!VerifyNameValidDir(ctx, name_raw)) - return; - - if (current_dir != nullptr) { - LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_ENTITY_ALREADY_OPEN); - return; - } - - current_dir = root->GetSubdirectory(name); - - if (current_dir == nullptr) { - LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_FAILED_OPEN_ENTITY); - return; - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - void Read(Kernel::HLERequestContext& ctx) { - auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry); - - LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size); - - if (current_dir == nullptr) { - LOG_ERROR(Service_BCAT, "There is no open directory!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_NO_OPEN_ENTITY); - return; - } - - const auto files = current_dir->GetFiles(); - write_size = std::min(write_size, files.size()); - std::vector entries(write_size); - std::transform( - files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) { - FileName name{}; - std::memcpy(name.data(), file->GetName().data(), - std::min(file->GetName().size(), name.size())); - return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)}; - }); - - ctx.WriteBuffer(entries); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast(write_size * sizeof(DeliveryCacheDirectoryEntry))); - } - - void GetCount(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - if (current_dir == nullptr) { - LOG_ERROR(Service_BCAT, "There is no open directory!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_NO_OPEN_ENTITY); - return; - } - - const auto files = current_dir->GetFiles(); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast(files.size())); - } - - FileSys::VirtualDir root; - FileSys::VirtualDir current_dir; -}; - -class IDeliveryCacheStorageService final : public ServiceFramework { -public: - explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_) - : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"}, - {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"}, - {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"}, - }; - // clang-format on - - RegisterHandlers(functions); - - for (const auto& subdir : root->GetSubdirectories()) { - DirectoryName name{}; - std::memcpy(name.data(), subdir->GetName().data(), - std::min(sizeof(DirectoryName) - 1, subdir->GetName().size())); - entries.push_back(name); - } - } - -private: - void CreateFileService(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, root); - } - - void CreateDirectoryService(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, root); - } - - void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) { - auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName); - - LOG_DEBUG(Service_BCAT, "called, size={:016X}", size); - - size = std::min(size, entries.size() - next_read_index); - ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName)); - next_read_index += size; - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast(size)); - } - - FileSys::VirtualDir root; - std::vector entries; - u64 next_read_index = 0; -}; - -void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_BCAT, "called"); - - const auto title_id = system.CurrentProcess()->GetTitleID(); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, fsc.GetBCATDirectory(title_id)); -} - -void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( - Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto title_id = rp.PopRaw(); - - LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, fsc.GetBCATDirectory(title_id)); -} - -std::unique_ptr CreateBackendFromSettings([[maybe_unused]] Core::System& system, - DirectoryGetter getter) { -#ifdef YUZU_ENABLE_BOXCAT - if (Settings::values.bcat_backend.GetValue() == "boxcat") { - return std::make_unique(system.GetAppletManager(), std::move(getter)); - } -#endif - - return std::make_unique(std::move(getter)); -} - -Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, - FileSystem::FileSystemController& fsc_, const char* name) - : ServiceFramework{system_, name}, fsc{fsc_}, module{std::move(module_)}, - backend{CreateBackendFromSettings(system_, - [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })} {} - -Module::Interface::~Interface() = default; - -void InstallInterfaces(Core::System& system) { - auto module = std::make_shared(); - std::make_shared(system, module, system.GetFileSystemController(), "bcat:a") - ->InstallAsService(system.ServiceManager()); - std::make_shared(system, module, system.GetFileSystemController(), "bcat:m") - ->InstallAsService(system.ServiceManager()); - std::make_shared(system, module, system.GetFileSystemController(), "bcat:u") - ->InstallAsService(system.ServiceManager()); - std::make_shared(system, module, system.GetFileSystemController(), "bcat:s") - ->InstallAsService(system.ServiceManager()); -} - -} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h deleted file mode 100644 index 738731c06..000000000 --- a/src/core/hle/service/bcat/module.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 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 Core { -class System; -} - -namespace Service { - -namespace FileSystem { -class FileSystemController; -} // namespace FileSystem - -namespace BCAT { - -class Backend; - -class Module final { -public: - class Interface : public ServiceFramework { - public: - explicit Interface(Core::System& system_, std::shared_ptr module_, - FileSystem::FileSystemController& fsc_, const char* name); - ~Interface() override; - - void CreateBcatService(Kernel::HLERequestContext& ctx); - void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx); - void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx); - - protected: - FileSystem::FileSystemController& fsc; - - std::shared_ptr module; - std::unique_ptr backend; - }; -}; - -/// Registers all BCAT services with the specified service manager. -void InstallInterfaces(Core::System& system); - -} // namespace BCAT - -} // namespace Service diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index c5f88bce7..a3c939c0c 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -12,7 +12,7 @@ #include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/friend/errors.h" #include "core/hle/service/friend/friend.h" -#include "core/hle/service/friend/interface.h" +#include "core/hle/service/friend/friend_interface.h" namespace Service::Friend { diff --git a/src/core/hle/service/friend/friend_interface.cpp b/src/core/hle/service/friend/friend_interface.cpp new file mode 100644 index 000000000..9b18b2a32 --- /dev/null +++ b/src/core/hle/service/friend/friend_interface.cpp @@ -0,0 +1,21 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/friend/friend_interface.h" + +namespace Service::Friend { + +Friend::Friend(std::shared_ptr module_, Core::System& system_, const char* name) + : Interface(std::move(module_), system_, name) { + static const FunctionInfo functions[] = { + {0, &Friend::CreateFriendService, "CreateFriendService"}, + {1, &Friend::CreateNotificationService, "CreateNotificationService"}, + {2, nullptr, "CreateDaemonSuspendSessionService"}, + }; + RegisterHandlers(functions); +} + +Friend::~Friend() = default; + +} // namespace Service::Friend diff --git a/src/core/hle/service/friend/friend_interface.h b/src/core/hle/service/friend/friend_interface.h new file mode 100644 index 000000000..43d914b32 --- /dev/null +++ b/src/core/hle/service/friend/friend_interface.h @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/friend/friend.h" + +namespace Service::Friend { + +class Friend final : public Module::Interface { +public: + explicit Friend(std::shared_ptr module_, Core::System& system_, const char* name); + ~Friend() override; +}; + +} // namespace Service::Friend diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp deleted file mode 100644 index 7368ccec2..000000000 --- a/src/core/hle/service/friend/interface.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/hle/service/friend/interface.h" - -namespace Service::Friend { - -Friend::Friend(std::shared_ptr module_, Core::System& system_, const char* name) - : Interface(std::move(module_), system_, name) { - static const FunctionInfo functions[] = { - {0, &Friend::CreateFriendService, "CreateFriendService"}, - {1, &Friend::CreateNotificationService, "CreateNotificationService"}, - {2, nullptr, "CreateDaemonSuspendSessionService"}, - }; - RegisterHandlers(functions); -} - -Friend::~Friend() = default; - -} // namespace Service::Friend diff --git a/src/core/hle/service/friend/interface.h b/src/core/hle/service/friend/interface.h deleted file mode 100644 index 43d914b32..000000000 --- a/src/core/hle/service/friend/interface.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/hle/service/friend/friend.h" - -namespace Service::Friend { - -class Friend final : public Module::Interface { -public: - explicit Friend(std::shared_ptr module_, Core::System& system_, const char* name); - ~Friend() override; -}; - -} // namespace Service::Friend diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp index ca25df67e..5a3b54cc1 100644 --- a/src/core/hle/service/glue/arp.cpp +++ b/src/core/hle/service/glue/arp.cpp @@ -13,7 +13,7 @@ #include "core/hle/kernel/kernel.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/glue/glue_manager.h" #include "core/hle/service/service.h" namespace Service::Glue { diff --git a/src/core/hle/service/glue/glue_manager.cpp b/src/core/hle/service/glue/glue_manager.cpp new file mode 100644 index 000000000..aa9d48c0c --- /dev/null +++ b/src/core/hle/service/glue/glue_manager.cpp @@ -0,0 +1,78 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/glue/errors.h" +#include "core/hle/service/glue/glue_manager.h" + +namespace Service::Glue { + +struct ARPManager::MapEntry { + ApplicationLaunchProperty launch; + std::vector control; +}; + +ARPManager::ARPManager() = default; + +ARPManager::~ARPManager() = default; + +ResultVal 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(iter->second.launch); +} + +ResultVal> 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>(iter->second.control); +} + +ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch, + std::vector 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 ResultSuccess; +} + +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 ResultSuccess; +} + +void ARPManager::ResetAll() { + entries.clear(); +} + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/glue_manager.h b/src/core/hle/service/glue/glue_manager.h new file mode 100644 index 000000000..a7f5ce3ee --- /dev/null +++ b/src/core/hle/service/glue/glue_manager.h @@ -0,0 +1,63 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#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 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> 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 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 entries; +}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/manager.cpp b/src/core/hle/service/glue/manager.cpp deleted file mode 100644 index 9b1754cf8..000000000 --- a/src/core/hle/service/glue/manager.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// 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 control; -}; - -ARPManager::ARPManager() = default; - -ARPManager::~ARPManager() = default; - -ResultVal 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(iter->second.launch); -} - -ResultVal> 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>(iter->second.control); -} - -ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch, - std::vector 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 ResultSuccess; -} - -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 ResultSuccess; -} - -void ARPManager::ResetAll() { - entries.clear(); -} - -} // namespace Service::Glue diff --git a/src/core/hle/service/glue/manager.h b/src/core/hle/service/glue/manager.h deleted file mode 100644 index a7f5ce3ee..000000000 --- a/src/core/hle/service/glue/manager.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#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 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> 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 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 entries; -}; - -} // namespace Service::Glue diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp deleted file mode 100644 index 869d2763f..000000000 --- a/src/core/hle/service/mii/manager.cpp +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright 2020 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include - -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/string_util.h" - -#include "core/hle/service/acc/profile_manager.h" -#include "core/hle/service/mii/manager.h" -#include "core/hle/service/mii/raw_data.h" -#include "core/hle/service/mii/types.h" - -namespace Service::Mii { - -namespace { - -constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; - -constexpr std::size_t BaseMiiCount{2}; -constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; - -constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; -constexpr std::array HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; -constexpr std::array EyeColorLookup{8, 9, 10, 11, 12, 13}; -constexpr std::array MouthColorLookup{19, 20, 21, 22, 23}; -constexpr std::array GlassesColorLookup{8, 14, 15, 16, 17, 18, 0}; -constexpr std::array EyeRotateLookup{ - {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, - 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, - 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}}; -constexpr std::array EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, - 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, - 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}}; - -template -std::array ResizeArray(const std::array& in) { - std::array out{}; - std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize)); - return out; -} - -MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { - MiiStoreBitFields bf; - std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); - - return { - .uuid = data.data.uuid, - .name = ResizeArray(data.data.name), - .font_region = static_cast(bf.font_region.Value()), - .favorite_color = static_cast(bf.favorite_color.Value()), - .gender = static_cast(bf.gender.Value()), - .height = static_cast(bf.height.Value()), - .build = static_cast(bf.build.Value()), - .type = static_cast(bf.type.Value()), - .region_move = static_cast(bf.region_move.Value()), - .faceline_type = static_cast(bf.faceline_type.Value()), - .faceline_color = static_cast(bf.faceline_color.Value()), - .faceline_wrinkle = static_cast(bf.faceline_wrinkle.Value()), - .faceline_make = static_cast(bf.faceline_makeup.Value()), - .hair_type = static_cast(bf.hair_type.Value()), - .hair_color = static_cast(bf.hair_color.Value()), - .hair_flip = static_cast(bf.hair_flip.Value()), - .eye_type = static_cast(bf.eye_type.Value()), - .eye_color = static_cast(bf.eye_color.Value()), - .eye_scale = static_cast(bf.eye_scale.Value()), - .eye_aspect = static_cast(bf.eye_aspect.Value()), - .eye_rotate = static_cast(bf.eye_rotate.Value()), - .eye_x = static_cast(bf.eye_x.Value()), - .eye_y = static_cast(bf.eye_y.Value()), - .eyebrow_type = static_cast(bf.eyebrow_type.Value()), - .eyebrow_color = static_cast(bf.eyebrow_color.Value()), - .eyebrow_scale = static_cast(bf.eyebrow_scale.Value()), - .eyebrow_aspect = static_cast(bf.eyebrow_aspect.Value()), - .eyebrow_rotate = static_cast(bf.eyebrow_rotate.Value()), - .eyebrow_x = static_cast(bf.eyebrow_x.Value()), - .eyebrow_y = static_cast(bf.eyebrow_y.Value() + 3), - .nose_type = static_cast(bf.nose_type.Value()), - .nose_scale = static_cast(bf.nose_scale.Value()), - .nose_y = static_cast(bf.nose_y.Value()), - .mouth_type = static_cast(bf.mouth_type.Value()), - .mouth_color = static_cast(bf.mouth_color.Value()), - .mouth_scale = static_cast(bf.mouth_scale.Value()), - .mouth_aspect = static_cast(bf.mouth_aspect.Value()), - .mouth_y = static_cast(bf.mouth_y.Value()), - .beard_color = static_cast(bf.beard_color.Value()), - .beard_type = static_cast(bf.beard_type.Value()), - .mustache_type = static_cast(bf.mustache_type.Value()), - .mustache_scale = static_cast(bf.mustache_scale.Value()), - .mustache_y = static_cast(bf.mustache_y.Value()), - .glasses_type = static_cast(bf.glasses_type.Value()), - .glasses_color = static_cast(bf.glasses_color.Value()), - .glasses_scale = static_cast(bf.glasses_scale.Value()), - .glasses_y = static_cast(bf.glasses_y.Value()), - .mole_type = static_cast(bf.mole_type.Value()), - .mole_scale = static_cast(bf.mole_scale.Value()), - .mole_x = static_cast(bf.mole_x.Value()), - .mole_y = static_cast(bf.mole_y.Value()), - .padding = 0, - }; -} - -u16 GenerateCrc16(const void* data, std::size_t size) { - s32 crc{}; - for (std::size_t i = 0; i < size; i++) { - crc ^= static_cast(data)[i] << 8; - for (std::size_t j = 0; j < 8; j++) { - crc <<= 1; - if ((crc & 0x10000) != 0) { - crc = (crc ^ 0x1021) & 0xFFFF; - } - } - } - return Common::swap16(static_cast(crc)); -} - -Common::UUID GenerateValidUUID() { - auto uuid{Common::UUID::Generate()}; - - // Bit 7 must be set, and bit 6 unset for the UUID to be valid - uuid.uuid[1] &= 0xFFFFFFFFFFFFFF3FULL; - uuid.uuid[1] |= 0x0000000000000080ULL; - - return uuid; -} - -template -T GetRandomValue(T min, T max) { - std::random_device device; - std::mt19937 gen(device()); - std::uniform_int_distribution distribution(static_cast(min), static_cast(max)); - return static_cast(distribution(gen)); -} - -template -T GetRandomValue(T max) { - return GetRandomValue({}, max); -} - -MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) { - MiiStoreBitFields bf{}; - - if (gender == Gender::All) { - gender = GetRandomValue(Gender::Maximum); - } - - bf.gender.Assign(gender); - bf.favorite_color.Assign(GetRandomValue(11)); - bf.region_move.Assign(0); - bf.font_region.Assign(FontRegion::Standard); - bf.type.Assign(0); - bf.height.Assign(64); - bf.build.Assign(64); - - if (age == Age::All) { - const auto temp{GetRandomValue(10)}; - if (temp >= 8) { - age = Age::Old; - } else if (temp >= 4) { - age = Age::Normal; - } else { - age = Age::Young; - } - } - - if (race == Race::All) { - const auto temp{GetRandomValue(10)}; - if (temp >= 8) { - race = Race::Black; - } else if (temp >= 4) { - race = Race::White; - } else { - race = Race::Asian; - } - } - - u32 axis_y{}; - if (gender == Gender::Female && age == Age::Young) { - axis_y = GetRandomValue(3); - } - - const std::size_t index{3 * static_cast(age) + - 9 * static_cast(gender) + static_cast(race)}; - - const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)}; - const auto faceline_color_info{RawData::RandomMiiFacelineColor.at( - 3 * static_cast(gender) + static_cast(race))}; - const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; - const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; - const auto hair_type_info{RawData::RandomMiiHairType.at(index)}; - const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast(race) + - static_cast(age))}; - const auto eye_type_info{RawData::RandomMiiEyeType.at(index)}; - const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast(race))}; - const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; - const auto nose_type_info{RawData::RandomMiiNoseType.at(index)}; - const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)}; - const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast(age))}; - - bf.faceline_type.Assign( - faceline_type_info.values[GetRandomValue(faceline_type_info.values_count)]); - bf.faceline_color.Assign( - faceline_color_info.values[GetRandomValue(faceline_color_info.values_count)]); - bf.faceline_wrinkle.Assign( - faceline_wrinkle_info - .values[GetRandomValue(faceline_wrinkle_info.values_count)]); - bf.faceline_makeup.Assign( - faceline_makeup_info - .values[GetRandomValue(faceline_makeup_info.values_count)]); - - bf.hair_type.Assign( - hair_type_info.values[GetRandomValue(hair_type_info.values_count)]); - bf.hair_color.Assign( - HairColorLookup[hair_color_info - .values[GetRandomValue(hair_color_info.values_count)]]); - bf.hair_flip.Assign(GetRandomValue(HairFlip::Maximum)); - - bf.eye_type.Assign( - eye_type_info.values[GetRandomValue(eye_type_info.values_count)]); - - const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; - const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; - const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; - const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]}; - - bf.eye_color.Assign( - EyeColorLookup[eye_color_info - .values[GetRandomValue(eye_color_info.values_count)]]); - bf.eye_scale.Assign(4); - bf.eye_aspect.Assign(3); - bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate); - bf.eye_x.Assign(2); - bf.eye_y.Assign(axis_y + 12); - - bf.eyebrow_type.Assign( - eyebrow_type_info.values[GetRandomValue(eyebrow_type_info.values_count)]); - - const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; - const auto eyebrow_y{race == Race::Asian ? 9 : 10}; - const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6}; - const auto eyebrow_rotate{ - 32 - EyebrowRotateLookup[static_cast(bf.eyebrow_type.Value())]}; - - bf.eyebrow_color.Assign(bf.hair_color); - bf.eyebrow_scale.Assign(4); - bf.eyebrow_aspect.Assign(3); - bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate); - bf.eyebrow_x.Assign(2); - bf.eyebrow_y.Assign(axis_y + eyebrow_y); - - const auto nose_scale{gender == Gender::Female ? 3 : 4}; - - bf.nose_type.Assign( - nose_type_info.values[GetRandomValue(nose_type_info.values_count)]); - bf.nose_scale.Assign(nose_scale); - bf.nose_y.Assign(axis_y + 9); - - const auto mouth_color{gender == Gender::Female ? GetRandomValue(4) : 0}; - - bf.mouth_type.Assign( - mouth_type_info.values[GetRandomValue(mouth_type_info.values_count)]); - bf.mouth_color.Assign(MouthColorLookup[mouth_color]); - bf.mouth_scale.Assign(4); - bf.mouth_aspect.Assign(3); - bf.mouth_y.Assign(axis_y + 13); - - bf.beard_color.Assign(bf.hair_color); - bf.mustache_scale.Assign(4); - - if (gender == Gender::Male && age != Age::Young && GetRandomValue(10) < 2) { - const auto mustache_and_beard_flag{ - GetRandomValue(BeardAndMustacheFlag::All)}; - - auto beard_type{BeardType::None}; - auto mustache_type{MustacheType::None}; - - if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == - BeardAndMustacheFlag::Beard) { - beard_type = GetRandomValue(BeardType::Beard1, BeardType::Beard5); - } - - if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == - BeardAndMustacheFlag::Mustache) { - mustache_type = - GetRandomValue(MustacheType::Mustache1, MustacheType::Mustache5); - } - - bf.mustache_type.Assign(mustache_type); - bf.beard_type.Assign(beard_type); - bf.mustache_y.Assign(10); - } else { - bf.mustache_type.Assign(MustacheType::None); - bf.beard_type.Assign(BeardType::None); - bf.mustache_y.Assign(axis_y + 10); - } - - const auto glasses_type_start{GetRandomValue(100)}; - u8 glasses_type{}; - while (glasses_type_start < glasses_type_info.values[glasses_type]) { - if (++glasses_type >= glasses_type_info.values_count) { - UNREACHABLE(); - break; - } - } - - bf.glasses_type.Assign(glasses_type); - bf.glasses_color.Assign(GlassesColorLookup[0]); - bf.glasses_scale.Assign(4); - bf.glasses_y.Assign(axis_y + 10); - - bf.mole_type.Assign(0); - bf.mole_scale.Assign(4); - bf.mole_x.Assign(2); - bf.mole_y.Assign(20); - - return {DefaultMiiName, bf, user_id}; -} - -MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) { - MiiStoreBitFields bf{}; - - bf.font_region.Assign(info.font_region); - bf.favorite_color.Assign(info.favorite_color); - bf.gender.Assign(info.gender); - bf.height.Assign(info.height); - bf.build.Assign(info.weight); - bf.type.Assign(info.type); - bf.region_move.Assign(info.region); - bf.faceline_type.Assign(info.face_type); - bf.faceline_color.Assign(info.face_color); - bf.faceline_wrinkle.Assign(info.face_wrinkle); - bf.faceline_makeup.Assign(info.face_makeup); - bf.hair_type.Assign(info.hair_type); - bf.hair_color.Assign(HairColorLookup[info.hair_color]); - bf.hair_flip.Assign(static_cast(info.hair_flip)); - bf.eye_type.Assign(info.eye_type); - bf.eye_color.Assign(EyeColorLookup[info.eye_color]); - bf.eye_scale.Assign(info.eye_scale); - bf.eye_aspect.Assign(info.eye_aspect); - bf.eye_rotate.Assign(info.eye_rotate); - bf.eye_x.Assign(info.eye_x); - bf.eye_y.Assign(info.eye_y); - bf.eyebrow_type.Assign(info.eyebrow_type); - bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]); - bf.eyebrow_scale.Assign(info.eyebrow_scale); - bf.eyebrow_aspect.Assign(info.eyebrow_aspect); - bf.eyebrow_rotate.Assign(info.eyebrow_rotate); - bf.eyebrow_x.Assign(info.eyebrow_x); - bf.eyebrow_y.Assign(info.eyebrow_y - 3); - bf.nose_type.Assign(info.nose_type); - bf.nose_scale.Assign(info.nose_scale); - bf.nose_y.Assign(info.nose_y); - bf.mouth_type.Assign(info.mouth_type); - bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]); - bf.mouth_scale.Assign(info.mouth_scale); - bf.mouth_aspect.Assign(info.mouth_aspect); - bf.mouth_y.Assign(info.mouth_y); - bf.beard_color.Assign(HairColorLookup[info.beard_color]); - bf.beard_type.Assign(static_cast(info.beard_type)); - bf.mustache_type.Assign(static_cast(info.mustache_type)); - bf.mustache_scale.Assign(info.mustache_scale); - bf.mustache_y.Assign(info.mustache_y); - bf.glasses_type.Assign(info.glasses_type); - bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]); - bf.glasses_scale.Assign(info.glasses_scale); - bf.glasses_y.Assign(info.glasses_y); - bf.mole_type.Assign(info.mole_type); - bf.mole_scale.Assign(info.mole_scale); - bf.mole_x.Assign(info.mole_x); - bf.mole_y.Assign(info.mole_y); - - return {DefaultMiiName, bf, user_id}; -} - -} // namespace - -MiiStoreData::MiiStoreData() = default; - -MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields, - const Common::UUID& user_id) { - data.name = name; - data.uuid = GenerateValidUUID(); - - std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields)); - data_crc = GenerateCrc16(data.data.data(), sizeof(data)); - device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID)); -} - -MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} - -bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) { - if ((source_flag & SourceFlag::Database) == SourceFlag::None) { - return false; - } - - const bool result{current_update_counter != update_counter}; - - current_update_counter = update_counter; - - return result; -} - -bool MiiManager::IsFullDatabase() const { - // TODO(bunnei): We don't implement the Mii database, so it cannot be full - return false; -} - -u32 MiiManager::GetCount(SourceFlag source_flag) const { - std::size_t count{}; - if ((source_flag & SourceFlag::Database) != SourceFlag::None) { - // TODO(bunnei): We don't implement the Mii database, but when we do, update this - count += 0; - } - if ((source_flag & SourceFlag::Default) != SourceFlag::None) { - count += (DefaultMiiCount - BaseMiiCount); - } - return static_cast(count); -} - -ResultVal MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, - SourceFlag source_flag) { - if ((source_flag & SourceFlag::Database) == SourceFlag::None) { - return ERROR_CANNOT_FIND_ENTRY; - } - - // TODO(bunnei): We don't implement the Mii database, so we can't have an entry - return ERROR_CANNOT_FIND_ENTRY; -} - -MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { - return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); -} - -MiiInfo MiiManager::BuildDefault(std::size_t index) { - return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); -} - -ResultVal> MiiManager::GetDefault(SourceFlag source_flag) { - std::vector result; - - if ((source_flag & SourceFlag::Default) == SourceFlag::None) { - return MakeResult(std::move(result)); - } - - for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) { - result.emplace_back(BuildDefault(index), Source::Default); - } - - return MakeResult(std::move(result)); -} - -ResultCode MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) { - constexpr u32 INVALID_INDEX{0xFFFFFFFF}; - - index = INVALID_INDEX; - - // TODO(bunnei): We don't implement the Mii database, so we can't have an index - return ERROR_CANNOT_FIND_ENTRY; -} - -} // namespace Service::Mii diff --git a/src/core/hle/service/mii/manager.h b/src/core/hle/service/mii/manager.h deleted file mode 100644 index 8e048fc56..000000000 --- a/src/core/hle/service/mii/manager.h +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2020 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/uuid.h" -#include "core/hle/result.h" -#include "core/hle/service/mii/types.h" - -namespace Service::Mii { - -enum class Source : u32 { - Database = 0, - Default = 1, - Account = 2, - Friend = 3, -}; - -enum class SourceFlag : u32 { - None = 0, - Database = 1 << 0, - Default = 1 << 1, -}; -DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); - -struct MiiInfo { - Common::UUID uuid; - std::array name; - u8 font_region; - u8 favorite_color; - u8 gender; - u8 height; - u8 build; - u8 type; - u8 region_move; - u8 faceline_type; - u8 faceline_color; - u8 faceline_wrinkle; - u8 faceline_make; - u8 hair_type; - u8 hair_color; - u8 hair_flip; - u8 eye_type; - u8 eye_color; - u8 eye_scale; - u8 eye_aspect; - u8 eye_rotate; - u8 eye_x; - u8 eye_y; - u8 eyebrow_type; - u8 eyebrow_color; - u8 eyebrow_scale; - u8 eyebrow_aspect; - u8 eyebrow_rotate; - u8 eyebrow_x; - u8 eyebrow_y; - u8 nose_type; - u8 nose_scale; - u8 nose_y; - u8 mouth_type; - u8 mouth_color; - u8 mouth_scale; - u8 mouth_aspect; - u8 mouth_y; - u8 beard_color; - u8 beard_type; - u8 mustache_type; - u8 mustache_scale; - u8 mustache_y; - u8 glasses_type; - u8 glasses_color; - u8 glasses_scale; - u8 glasses_y; - u8 mole_type; - u8 mole_scale; - u8 mole_x; - u8 mole_y; - u8 padding; - - std::u16string Name() const; -}; -static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); -static_assert(std::has_unique_object_representations_v, - "All bits of MiiInfo must contribute to its value."); - -#pragma pack(push, 4) - -struct MiiInfoElement { - MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {} - - MiiInfo info{}; - Source source{}; -}; -static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); - -struct MiiStoreBitFields { - union { - u32 word_0{}; - - BitField<0, 8, u32> hair_type; - BitField<8, 7, u32> height; - BitField<15, 1, u32> mole_type; - BitField<16, 7, u32> build; - BitField<23, 1, HairFlip> hair_flip; - BitField<24, 7, u32> hair_color; - BitField<31, 1, u32> type; - }; - - union { - u32 word_1{}; - - BitField<0, 7, u32> eye_color; - BitField<7, 1, Gender> gender; - BitField<8, 7, u32> eyebrow_color; - BitField<16, 7, u32> mouth_color; - BitField<24, 7, u32> beard_color; - }; - - union { - u32 word_2{}; - - BitField<0, 7, u32> glasses_color; - BitField<8, 6, u32> eye_type; - BitField<14, 2, u32> region_move; - BitField<16, 6, u32> mouth_type; - BitField<22, 2, FontRegion> font_region; - BitField<24, 5, u32> eye_y; - BitField<29, 3, u32> glasses_scale; - }; - - union { - u32 word_3{}; - - BitField<0, 5, u32> eyebrow_type; - BitField<5, 3, MustacheType> mustache_type; - BitField<8, 5, u32> nose_type; - BitField<13, 3, BeardType> beard_type; - BitField<16, 5, u32> nose_y; - BitField<21, 3, u32> mouth_aspect; - BitField<24, 5, u32> mouth_y; - BitField<29, 3, u32> eyebrow_aspect; - }; - - union { - u32 word_4{}; - - BitField<0, 5, u32> mustache_y; - BitField<5, 3, u32> eye_rotate; - BitField<8, 5, u32> glasses_y; - BitField<13, 3, u32> eye_aspect; - BitField<16, 5, u32> mole_x; - BitField<21, 3, u32> eye_scale; - BitField<24, 5, u32> mole_y; - }; - - union { - u32 word_5{}; - - BitField<0, 5, u32> glasses_type; - BitField<8, 4, u32> favorite_color; - BitField<12, 4, u32> faceline_type; - BitField<16, 4, u32> faceline_color; - BitField<20, 4, u32> faceline_wrinkle; - BitField<24, 4, u32> faceline_makeup; - BitField<28, 4, u32> eye_x; - }; - - union { - u32 word_6{}; - - BitField<0, 4, u32> eyebrow_scale; - BitField<4, 4, u32> eyebrow_rotate; - BitField<8, 4, u32> eyebrow_x; - BitField<12, 4, u32> eyebrow_y; - BitField<16, 4, u32> nose_scale; - BitField<20, 4, u32> mouth_scale; - BitField<24, 4, u32> mustache_scale; - BitField<28, 4, u32> mole_scale; - }; -}; -static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); -static_assert(std::is_trivially_copyable_v, - "MiiStoreBitFields is not trivially copyable."); - -struct MiiStoreData { - using Name = std::array; - - MiiStoreData(); - MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, - const Common::UUID& user_id); - - // This corresponds to the above structure MiiStoreBitFields. I did it like this because the - // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is - // not suitable for our uses. - struct { - std::array data{}; - static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); - - Name name{}; - Common::UUID uuid{Common::INVALID_UUID}; - } data; - - u16 data_crc{}; - u16 device_crc{}; -}; -static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); - -struct MiiStoreDataElement { - MiiStoreData data{}; - Source source{}; -}; -static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); - -struct MiiDatabase { - u32 magic{}; // 'NFDB' - std::array miis{}; - INSERT_PADDING_BYTES(1); - u8 count{}; - u16 crc{}; -}; -static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); - -struct RandomMiiValues { - std::array values{}; -}; -static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); - -struct RandomMiiData4 { - Gender gender{}; - Age age{}; - Race race{}; - u32 values_count{}; - std::array values{}; -}; -static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); - -struct RandomMiiData3 { - u32 arg_1; - u32 arg_2; - u32 values_count; - std::array values{}; -}; -static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); - -struct RandomMiiData2 { - u32 arg_1; - u32 values_count; - std::array values{}; -}; -static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); - -struct DefaultMii { - u32 face_type{}; - u32 face_color{}; - u32 face_wrinkle{}; - u32 face_makeup{}; - u32 hair_type{}; - u32 hair_color{}; - u32 hair_flip{}; - u32 eye_type{}; - u32 eye_color{}; - u32 eye_scale{}; - u32 eye_aspect{}; - u32 eye_rotate{}; - u32 eye_x{}; - u32 eye_y{}; - u32 eyebrow_type{}; - u32 eyebrow_color{}; - u32 eyebrow_scale{}; - u32 eyebrow_aspect{}; - u32 eyebrow_rotate{}; - u32 eyebrow_x{}; - u32 eyebrow_y{}; - u32 nose_type{}; - u32 nose_scale{}; - u32 nose_y{}; - u32 mouth_type{}; - u32 mouth_color{}; - u32 mouth_scale{}; - u32 mouth_aspect{}; - u32 mouth_y{}; - u32 mustache_type{}; - u32 beard_type{}; - u32 beard_color{}; - u32 mustache_scale{}; - u32 mustache_y{}; - u32 glasses_type{}; - u32 glasses_color{}; - u32 glasses_scale{}; - u32 glasses_y{}; - u32 mole_type{}; - u32 mole_scale{}; - u32 mole_x{}; - u32 mole_y{}; - u32 height{}; - u32 weight{}; - Gender gender{}; - u32 favorite_color{}; - u32 region{}; - FontRegion font_region{}; - u32 type{}; - INSERT_PADDING_WORDS(5); -}; -static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); - -#pragma pack(pop) - -// The Mii manager is responsible for loading and storing the Miis to the database in NAND along -// with providing an easy interface for HLE emulation of the mii service. -class MiiManager { -public: - MiiManager(); - - bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); - bool IsFullDatabase() const; - u32 GetCount(SourceFlag source_flag) const; - ResultVal UpdateLatest(const MiiInfo& info, SourceFlag source_flag); - MiiInfo BuildRandom(Age age, Gender gender, Race race); - MiiInfo BuildDefault(std::size_t index); - ResultVal> GetDefault(SourceFlag source_flag); - ResultCode GetIndex(const MiiInfo& info, u32& index); - -private: - const Common::UUID user_id{Common::INVALID_UUID}; - u64 update_counter{}; -}; - -}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index bbd81a88a..9d863486a 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -7,8 +7,8 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" -#include "core/hle/service/mii/manager.h" #include "core/hle/service/mii/mii.h" +#include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp new file mode 100644 index 000000000..4fef2aea4 --- /dev/null +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -0,0 +1,465 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/string_util.h" + +#include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/mii/raw_data.h" +#include "core/hle/service/mii/types.h" + +namespace Service::Mii { + +namespace { + +constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; + +constexpr std::size_t BaseMiiCount{2}; +constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; + +constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; +constexpr std::array HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; +constexpr std::array EyeColorLookup{8, 9, 10, 11, 12, 13}; +constexpr std::array MouthColorLookup{19, 20, 21, 22, 23}; +constexpr std::array GlassesColorLookup{8, 14, 15, 16, 17, 18, 0}; +constexpr std::array EyeRotateLookup{ + {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, + 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, + 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}}; +constexpr std::array EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, + 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, + 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}}; + +template +std::array ResizeArray(const std::array& in) { + std::array out{}; + std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize)); + return out; +} + +MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { + MiiStoreBitFields bf; + std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); + + return { + .uuid = data.data.uuid, + .name = ResizeArray(data.data.name), + .font_region = static_cast(bf.font_region.Value()), + .favorite_color = static_cast(bf.favorite_color.Value()), + .gender = static_cast(bf.gender.Value()), + .height = static_cast(bf.height.Value()), + .build = static_cast(bf.build.Value()), + .type = static_cast(bf.type.Value()), + .region_move = static_cast(bf.region_move.Value()), + .faceline_type = static_cast(bf.faceline_type.Value()), + .faceline_color = static_cast(bf.faceline_color.Value()), + .faceline_wrinkle = static_cast(bf.faceline_wrinkle.Value()), + .faceline_make = static_cast(bf.faceline_makeup.Value()), + .hair_type = static_cast(bf.hair_type.Value()), + .hair_color = static_cast(bf.hair_color.Value()), + .hair_flip = static_cast(bf.hair_flip.Value()), + .eye_type = static_cast(bf.eye_type.Value()), + .eye_color = static_cast(bf.eye_color.Value()), + .eye_scale = static_cast(bf.eye_scale.Value()), + .eye_aspect = static_cast(bf.eye_aspect.Value()), + .eye_rotate = static_cast(bf.eye_rotate.Value()), + .eye_x = static_cast(bf.eye_x.Value()), + .eye_y = static_cast(bf.eye_y.Value()), + .eyebrow_type = static_cast(bf.eyebrow_type.Value()), + .eyebrow_color = static_cast(bf.eyebrow_color.Value()), + .eyebrow_scale = static_cast(bf.eyebrow_scale.Value()), + .eyebrow_aspect = static_cast(bf.eyebrow_aspect.Value()), + .eyebrow_rotate = static_cast(bf.eyebrow_rotate.Value()), + .eyebrow_x = static_cast(bf.eyebrow_x.Value()), + .eyebrow_y = static_cast(bf.eyebrow_y.Value() + 3), + .nose_type = static_cast(bf.nose_type.Value()), + .nose_scale = static_cast(bf.nose_scale.Value()), + .nose_y = static_cast(bf.nose_y.Value()), + .mouth_type = static_cast(bf.mouth_type.Value()), + .mouth_color = static_cast(bf.mouth_color.Value()), + .mouth_scale = static_cast(bf.mouth_scale.Value()), + .mouth_aspect = static_cast(bf.mouth_aspect.Value()), + .mouth_y = static_cast(bf.mouth_y.Value()), + .beard_color = static_cast(bf.beard_color.Value()), + .beard_type = static_cast(bf.beard_type.Value()), + .mustache_type = static_cast(bf.mustache_type.Value()), + .mustache_scale = static_cast(bf.mustache_scale.Value()), + .mustache_y = static_cast(bf.mustache_y.Value()), + .glasses_type = static_cast(bf.glasses_type.Value()), + .glasses_color = static_cast(bf.glasses_color.Value()), + .glasses_scale = static_cast(bf.glasses_scale.Value()), + .glasses_y = static_cast(bf.glasses_y.Value()), + .mole_type = static_cast(bf.mole_type.Value()), + .mole_scale = static_cast(bf.mole_scale.Value()), + .mole_x = static_cast(bf.mole_x.Value()), + .mole_y = static_cast(bf.mole_y.Value()), + .padding = 0, + }; +} + +u16 GenerateCrc16(const void* data, std::size_t size) { + s32 crc{}; + for (std::size_t i = 0; i < size; i++) { + crc ^= static_cast(data)[i] << 8; + for (std::size_t j = 0; j < 8; j++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = (crc ^ 0x1021) & 0xFFFF; + } + } + } + return Common::swap16(static_cast(crc)); +} + +Common::UUID GenerateValidUUID() { + auto uuid{Common::UUID::Generate()}; + + // Bit 7 must be set, and bit 6 unset for the UUID to be valid + uuid.uuid[1] &= 0xFFFFFFFFFFFFFF3FULL; + uuid.uuid[1] |= 0x0000000000000080ULL; + + return uuid; +} + +template +T GetRandomValue(T min, T max) { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution distribution(static_cast(min), static_cast(max)); + return static_cast(distribution(gen)); +} + +template +T GetRandomValue(T max) { + return GetRandomValue({}, max); +} + +MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) { + MiiStoreBitFields bf{}; + + if (gender == Gender::All) { + gender = GetRandomValue(Gender::Maximum); + } + + bf.gender.Assign(gender); + bf.favorite_color.Assign(GetRandomValue(11)); + bf.region_move.Assign(0); + bf.font_region.Assign(FontRegion::Standard); + bf.type.Assign(0); + bf.height.Assign(64); + bf.build.Assign(64); + + if (age == Age::All) { + const auto temp{GetRandomValue(10)}; + if (temp >= 8) { + age = Age::Old; + } else if (temp >= 4) { + age = Age::Normal; + } else { + age = Age::Young; + } + } + + if (race == Race::All) { + const auto temp{GetRandomValue(10)}; + if (temp >= 8) { + race = Race::Black; + } else if (temp >= 4) { + race = Race::White; + } else { + race = Race::Asian; + } + } + + u32 axis_y{}; + if (gender == Gender::Female && age == Age::Young) { + axis_y = GetRandomValue(3); + } + + const std::size_t index{3 * static_cast(age) + + 9 * static_cast(gender) + static_cast(race)}; + + const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)}; + const auto faceline_color_info{RawData::RandomMiiFacelineColor.at( + 3 * static_cast(gender) + static_cast(race))}; + const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; + const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; + const auto hair_type_info{RawData::RandomMiiHairType.at(index)}; + const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast(race) + + static_cast(age))}; + const auto eye_type_info{RawData::RandomMiiEyeType.at(index)}; + const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast(race))}; + const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; + const auto nose_type_info{RawData::RandomMiiNoseType.at(index)}; + const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)}; + const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast(age))}; + + bf.faceline_type.Assign( + faceline_type_info.values[GetRandomValue(faceline_type_info.values_count)]); + bf.faceline_color.Assign( + faceline_color_info.values[GetRandomValue(faceline_color_info.values_count)]); + bf.faceline_wrinkle.Assign( + faceline_wrinkle_info + .values[GetRandomValue(faceline_wrinkle_info.values_count)]); + bf.faceline_makeup.Assign( + faceline_makeup_info + .values[GetRandomValue(faceline_makeup_info.values_count)]); + + bf.hair_type.Assign( + hair_type_info.values[GetRandomValue(hair_type_info.values_count)]); + bf.hair_color.Assign( + HairColorLookup[hair_color_info + .values[GetRandomValue(hair_color_info.values_count)]]); + bf.hair_flip.Assign(GetRandomValue(HairFlip::Maximum)); + + bf.eye_type.Assign( + eye_type_info.values[GetRandomValue(eye_type_info.values_count)]); + + const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; + const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; + const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; + const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]}; + + bf.eye_color.Assign( + EyeColorLookup[eye_color_info + .values[GetRandomValue(eye_color_info.values_count)]]); + bf.eye_scale.Assign(4); + bf.eye_aspect.Assign(3); + bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate); + bf.eye_x.Assign(2); + bf.eye_y.Assign(axis_y + 12); + + bf.eyebrow_type.Assign( + eyebrow_type_info.values[GetRandomValue(eyebrow_type_info.values_count)]); + + const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; + const auto eyebrow_y{race == Race::Asian ? 9 : 10}; + const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6}; + const auto eyebrow_rotate{ + 32 - EyebrowRotateLookup[static_cast(bf.eyebrow_type.Value())]}; + + bf.eyebrow_color.Assign(bf.hair_color); + bf.eyebrow_scale.Assign(4); + bf.eyebrow_aspect.Assign(3); + bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate); + bf.eyebrow_x.Assign(2); + bf.eyebrow_y.Assign(axis_y + eyebrow_y); + + const auto nose_scale{gender == Gender::Female ? 3 : 4}; + + bf.nose_type.Assign( + nose_type_info.values[GetRandomValue(nose_type_info.values_count)]); + bf.nose_scale.Assign(nose_scale); + bf.nose_y.Assign(axis_y + 9); + + const auto mouth_color{gender == Gender::Female ? GetRandomValue(4) : 0}; + + bf.mouth_type.Assign( + mouth_type_info.values[GetRandomValue(mouth_type_info.values_count)]); + bf.mouth_color.Assign(MouthColorLookup[mouth_color]); + bf.mouth_scale.Assign(4); + bf.mouth_aspect.Assign(3); + bf.mouth_y.Assign(axis_y + 13); + + bf.beard_color.Assign(bf.hair_color); + bf.mustache_scale.Assign(4); + + if (gender == Gender::Male && age != Age::Young && GetRandomValue(10) < 2) { + const auto mustache_and_beard_flag{ + GetRandomValue(BeardAndMustacheFlag::All)}; + + auto beard_type{BeardType::None}; + auto mustache_type{MustacheType::None}; + + if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == + BeardAndMustacheFlag::Beard) { + beard_type = GetRandomValue(BeardType::Beard1, BeardType::Beard5); + } + + if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == + BeardAndMustacheFlag::Mustache) { + mustache_type = + GetRandomValue(MustacheType::Mustache1, MustacheType::Mustache5); + } + + bf.mustache_type.Assign(mustache_type); + bf.beard_type.Assign(beard_type); + bf.mustache_y.Assign(10); + } else { + bf.mustache_type.Assign(MustacheType::None); + bf.beard_type.Assign(BeardType::None); + bf.mustache_y.Assign(axis_y + 10); + } + + const auto glasses_type_start{GetRandomValue(100)}; + u8 glasses_type{}; + while (glasses_type_start < glasses_type_info.values[glasses_type]) { + if (++glasses_type >= glasses_type_info.values_count) { + UNREACHABLE(); + break; + } + } + + bf.glasses_type.Assign(glasses_type); + bf.glasses_color.Assign(GlassesColorLookup[0]); + bf.glasses_scale.Assign(4); + bf.glasses_y.Assign(axis_y + 10); + + bf.mole_type.Assign(0); + bf.mole_scale.Assign(4); + bf.mole_x.Assign(2); + bf.mole_y.Assign(20); + + return {DefaultMiiName, bf, user_id}; +} + +MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) { + MiiStoreBitFields bf{}; + + bf.font_region.Assign(info.font_region); + bf.favorite_color.Assign(info.favorite_color); + bf.gender.Assign(info.gender); + bf.height.Assign(info.height); + bf.build.Assign(info.weight); + bf.type.Assign(info.type); + bf.region_move.Assign(info.region); + bf.faceline_type.Assign(info.face_type); + bf.faceline_color.Assign(info.face_color); + bf.faceline_wrinkle.Assign(info.face_wrinkle); + bf.faceline_makeup.Assign(info.face_makeup); + bf.hair_type.Assign(info.hair_type); + bf.hair_color.Assign(HairColorLookup[info.hair_color]); + bf.hair_flip.Assign(static_cast(info.hair_flip)); + bf.eye_type.Assign(info.eye_type); + bf.eye_color.Assign(EyeColorLookup[info.eye_color]); + bf.eye_scale.Assign(info.eye_scale); + bf.eye_aspect.Assign(info.eye_aspect); + bf.eye_rotate.Assign(info.eye_rotate); + bf.eye_x.Assign(info.eye_x); + bf.eye_y.Assign(info.eye_y); + bf.eyebrow_type.Assign(info.eyebrow_type); + bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]); + bf.eyebrow_scale.Assign(info.eyebrow_scale); + bf.eyebrow_aspect.Assign(info.eyebrow_aspect); + bf.eyebrow_rotate.Assign(info.eyebrow_rotate); + bf.eyebrow_x.Assign(info.eyebrow_x); + bf.eyebrow_y.Assign(info.eyebrow_y - 3); + bf.nose_type.Assign(info.nose_type); + bf.nose_scale.Assign(info.nose_scale); + bf.nose_y.Assign(info.nose_y); + bf.mouth_type.Assign(info.mouth_type); + bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]); + bf.mouth_scale.Assign(info.mouth_scale); + bf.mouth_aspect.Assign(info.mouth_aspect); + bf.mouth_y.Assign(info.mouth_y); + bf.beard_color.Assign(HairColorLookup[info.beard_color]); + bf.beard_type.Assign(static_cast(info.beard_type)); + bf.mustache_type.Assign(static_cast(info.mustache_type)); + bf.mustache_scale.Assign(info.mustache_scale); + bf.mustache_y.Assign(info.mustache_y); + bf.glasses_type.Assign(info.glasses_type); + bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]); + bf.glasses_scale.Assign(info.glasses_scale); + bf.glasses_y.Assign(info.glasses_y); + bf.mole_type.Assign(info.mole_type); + bf.mole_scale.Assign(info.mole_scale); + bf.mole_x.Assign(info.mole_x); + bf.mole_y.Assign(info.mole_y); + + return {DefaultMiiName, bf, user_id}; +} + +} // namespace + +MiiStoreData::MiiStoreData() = default; + +MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields, + const Common::UUID& user_id) { + data.name = name; + data.uuid = GenerateValidUUID(); + + std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields)); + data_crc = GenerateCrc16(data.data.data(), sizeof(data)); + device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID)); +} + +MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} + +bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return false; + } + + const bool result{current_update_counter != update_counter}; + + current_update_counter = update_counter; + + return result; +} + +bool MiiManager::IsFullDatabase() const { + // TODO(bunnei): We don't implement the Mii database, so it cannot be full + return false; +} + +u32 MiiManager::GetCount(SourceFlag source_flag) const { + std::size_t count{}; + if ((source_flag & SourceFlag::Database) != SourceFlag::None) { + // TODO(bunnei): We don't implement the Mii database, but when we do, update this + count += 0; + } + if ((source_flag & SourceFlag::Default) != SourceFlag::None) { + count += (DefaultMiiCount - BaseMiiCount); + } + return static_cast(count); +} + +ResultVal MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, + SourceFlag source_flag) { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return ERROR_CANNOT_FIND_ENTRY; + } + + // TODO(bunnei): We don't implement the Mii database, so we can't have an entry + return ERROR_CANNOT_FIND_ENTRY; +} + +MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { + return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); +} + +MiiInfo MiiManager::BuildDefault(std::size_t index) { + return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); +} + +ResultVal> MiiManager::GetDefault(SourceFlag source_flag) { + std::vector result; + + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return MakeResult(std::move(result)); + } + + for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) { + result.emplace_back(BuildDefault(index), Source::Default); + } + + return MakeResult(std::move(result)); +} + +ResultCode MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) { + constexpr u32 INVALID_INDEX{0xFFFFFFFF}; + + index = INVALID_INDEX; + + // TODO(bunnei): We don't implement the Mii database, so we can't have an index + return ERROR_CANNOT_FIND_ENTRY; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h new file mode 100644 index 000000000..8e048fc56 --- /dev/null +++ b/src/core/hle/service/mii/mii_manager.h @@ -0,0 +1,333 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/uuid.h" +#include "core/hle/result.h" +#include "core/hle/service/mii/types.h" + +namespace Service::Mii { + +enum class Source : u32 { + Database = 0, + Default = 1, + Account = 2, + Friend = 3, +}; + +enum class SourceFlag : u32 { + None = 0, + Database = 1 << 0, + Default = 1 << 1, +}; +DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); + +struct MiiInfo { + Common::UUID uuid; + std::array name; + u8 font_region; + u8 favorite_color; + u8 gender; + u8 height; + u8 build; + u8 type; + u8 region_move; + u8 faceline_type; + u8 faceline_color; + u8 faceline_wrinkle; + u8 faceline_make; + u8 hair_type; + u8 hair_color; + u8 hair_flip; + u8 eye_type; + u8 eye_color; + u8 eye_scale; + u8 eye_aspect; + u8 eye_rotate; + u8 eye_x; + u8 eye_y; + u8 eyebrow_type; + u8 eyebrow_color; + u8 eyebrow_scale; + u8 eyebrow_aspect; + u8 eyebrow_rotate; + u8 eyebrow_x; + u8 eyebrow_y; + u8 nose_type; + u8 nose_scale; + u8 nose_y; + u8 mouth_type; + u8 mouth_color; + u8 mouth_scale; + u8 mouth_aspect; + u8 mouth_y; + u8 beard_color; + u8 beard_type; + u8 mustache_type; + u8 mustache_scale; + u8 mustache_y; + u8 glasses_type; + u8 glasses_color; + u8 glasses_scale; + u8 glasses_y; + u8 mole_type; + u8 mole_scale; + u8 mole_x; + u8 mole_y; + u8 padding; + + std::u16string Name() const; +}; +static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); +static_assert(std::has_unique_object_representations_v, + "All bits of MiiInfo must contribute to its value."); + +#pragma pack(push, 4) + +struct MiiInfoElement { + MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {} + + MiiInfo info{}; + Source source{}; +}; +static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); + +struct MiiStoreBitFields { + union { + u32 word_0{}; + + BitField<0, 8, u32> hair_type; + BitField<8, 7, u32> height; + BitField<15, 1, u32> mole_type; + BitField<16, 7, u32> build; + BitField<23, 1, HairFlip> hair_flip; + BitField<24, 7, u32> hair_color; + BitField<31, 1, u32> type; + }; + + union { + u32 word_1{}; + + BitField<0, 7, u32> eye_color; + BitField<7, 1, Gender> gender; + BitField<8, 7, u32> eyebrow_color; + BitField<16, 7, u32> mouth_color; + BitField<24, 7, u32> beard_color; + }; + + union { + u32 word_2{}; + + BitField<0, 7, u32> glasses_color; + BitField<8, 6, u32> eye_type; + BitField<14, 2, u32> region_move; + BitField<16, 6, u32> mouth_type; + BitField<22, 2, FontRegion> font_region; + BitField<24, 5, u32> eye_y; + BitField<29, 3, u32> glasses_scale; + }; + + union { + u32 word_3{}; + + BitField<0, 5, u32> eyebrow_type; + BitField<5, 3, MustacheType> mustache_type; + BitField<8, 5, u32> nose_type; + BitField<13, 3, BeardType> beard_type; + BitField<16, 5, u32> nose_y; + BitField<21, 3, u32> mouth_aspect; + BitField<24, 5, u32> mouth_y; + BitField<29, 3, u32> eyebrow_aspect; + }; + + union { + u32 word_4{}; + + BitField<0, 5, u32> mustache_y; + BitField<5, 3, u32> eye_rotate; + BitField<8, 5, u32> glasses_y; + BitField<13, 3, u32> eye_aspect; + BitField<16, 5, u32> mole_x; + BitField<21, 3, u32> eye_scale; + BitField<24, 5, u32> mole_y; + }; + + union { + u32 word_5{}; + + BitField<0, 5, u32> glasses_type; + BitField<8, 4, u32> favorite_color; + BitField<12, 4, u32> faceline_type; + BitField<16, 4, u32> faceline_color; + BitField<20, 4, u32> faceline_wrinkle; + BitField<24, 4, u32> faceline_makeup; + BitField<28, 4, u32> eye_x; + }; + + union { + u32 word_6{}; + + BitField<0, 4, u32> eyebrow_scale; + BitField<4, 4, u32> eyebrow_rotate; + BitField<8, 4, u32> eyebrow_x; + BitField<12, 4, u32> eyebrow_y; + BitField<16, 4, u32> nose_scale; + BitField<20, 4, u32> mouth_scale; + BitField<24, 4, u32> mustache_scale; + BitField<28, 4, u32> mole_scale; + }; +}; +static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); +static_assert(std::is_trivially_copyable_v, + "MiiStoreBitFields is not trivially copyable."); + +struct MiiStoreData { + using Name = std::array; + + MiiStoreData(); + MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, + const Common::UUID& user_id); + + // This corresponds to the above structure MiiStoreBitFields. I did it like this because the + // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is + // not suitable for our uses. + struct { + std::array data{}; + static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); + + Name name{}; + Common::UUID uuid{Common::INVALID_UUID}; + } data; + + u16 data_crc{}; + u16 device_crc{}; +}; +static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); + +struct MiiStoreDataElement { + MiiStoreData data{}; + Source source{}; +}; +static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); + +struct MiiDatabase { + u32 magic{}; // 'NFDB' + std::array miis{}; + INSERT_PADDING_BYTES(1); + u8 count{}; + u16 crc{}; +}; +static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); + +struct RandomMiiValues { + std::array values{}; +}; +static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); + +struct RandomMiiData4 { + Gender gender{}; + Age age{}; + Race race{}; + u32 values_count{}; + std::array values{}; +}; +static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); + +struct RandomMiiData3 { + u32 arg_1; + u32 arg_2; + u32 values_count; + std::array values{}; +}; +static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); + +struct RandomMiiData2 { + u32 arg_1; + u32 values_count; + std::array values{}; +}; +static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); + +struct DefaultMii { + u32 face_type{}; + u32 face_color{}; + u32 face_wrinkle{}; + u32 face_makeup{}; + u32 hair_type{}; + u32 hair_color{}; + u32 hair_flip{}; + u32 eye_type{}; + u32 eye_color{}; + u32 eye_scale{}; + u32 eye_aspect{}; + u32 eye_rotate{}; + u32 eye_x{}; + u32 eye_y{}; + u32 eyebrow_type{}; + u32 eyebrow_color{}; + u32 eyebrow_scale{}; + u32 eyebrow_aspect{}; + u32 eyebrow_rotate{}; + u32 eyebrow_x{}; + u32 eyebrow_y{}; + u32 nose_type{}; + u32 nose_scale{}; + u32 nose_y{}; + u32 mouth_type{}; + u32 mouth_color{}; + u32 mouth_scale{}; + u32 mouth_aspect{}; + u32 mouth_y{}; + u32 mustache_type{}; + u32 beard_type{}; + u32 beard_color{}; + u32 mustache_scale{}; + u32 mustache_y{}; + u32 glasses_type{}; + u32 glasses_color{}; + u32 glasses_scale{}; + u32 glasses_y{}; + u32 mole_type{}; + u32 mole_scale{}; + u32 mole_x{}; + u32 mole_y{}; + u32 height{}; + u32 weight{}; + Gender gender{}; + u32 favorite_color{}; + u32 region{}; + FontRegion font_region{}; + u32 type{}; + INSERT_PADDING_WORDS(5); +}; +static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); + +#pragma pack(pop) + +// The Mii manager is responsible for loading and storing the Miis to the database in NAND along +// with providing an easy interface for HLE emulation of the mii service. +class MiiManager { +public: + MiiManager(); + + bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); + bool IsFullDatabase() const; + u32 GetCount(SourceFlag source_flag) const; + ResultVal UpdateLatest(const MiiInfo& info, SourceFlag source_flag); + MiiInfo BuildRandom(Age age, Gender gender, Race race); + MiiInfo BuildDefault(std::size_t index); + ResultVal> GetDefault(SourceFlag source_flag); + ResultCode GetIndex(const MiiInfo& info, u32& index); + +private: + const Common::UUID user_id{Common::INVALID_UUID}; + u64 update_counter{}; +}; + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h index 0e35d69d2..a0d2b9d3a 100644 --- a/src/core/hle/service/mii/raw_data.h +++ b/src/core/hle/service/mii/raw_data.h @@ -7,7 +7,7 @@ #include #include "common/common_types.h" -#include "core/hle/service/mii/manager.h" +#include "core/hle/service/mii/mii_manager.h" namespace Service::Mii::RawData { diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp deleted file mode 100644 index e4d495000..000000000 --- a/src/core/hle/service/nvdrv/interface.cpp +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include "common/logging/log.h" -#include "core/core.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/k_readable_event.h" -#include "core/hle/kernel/k_thread.h" -#include "core/hle/kernel/k_writable_event.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/service/nvdrv/interface.h" -#include "core/hle/service/nvdrv/nvdata.h" -#include "core/hle/service/nvdrv/nvdrv.h" - -namespace Service::Nvidia { - -void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) { - nvdrv->SignalSyncpt(syncpoint_id, value); -} - -void NVDRV::Open(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); - - if (!is_initialized) { - rb.Push(0); - rb.PushEnum(NvResult::NotInitialized); - - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); - return; - } - - const auto& buffer = ctx.ReadBuffer(); - const std::string device_name(buffer.begin(), buffer.end()); - - if (device_name == "/dev/nvhost-prof-gpu") { - rb.Push(0); - rb.PushEnum(NvResult::NotSupported); - - LOG_WARNING(Service_NVDRV, "/dev/nvhost-prof-gpu cannot be opened in production"); - return; - } - - DeviceFD fd = nvdrv->Open(device_name); - - rb.Push(fd); - rb.PushEnum(fd != INVALID_NVDRV_FD ? NvResult::Success : NvResult::FileOperationFailed); -} - -void NVDRV::ServiceError(Kernel::HLERequestContext& ctx, NvResult result) { - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(result); -} - -void NVDRV::Ioctl1(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto fd = rp.Pop(); - const auto command = rp.PopRaw(); - LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); - - if (!is_initialized) { - ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); - return; - } - - // Check device - std::vector output_buffer(ctx.GetWriteBufferSize(0)); - const auto input_buffer = ctx.ReadBuffer(0); - - const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer); - if (command.is_out != 0) { - ctx.WriteBuffer(output_buffer); - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(nv_result); -} - -void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto fd = rp.Pop(); - const auto command = rp.PopRaw(); - LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); - - if (!is_initialized) { - ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); - return; - } - - const auto input_buffer = ctx.ReadBuffer(0); - const auto input_inlined_buffer = ctx.ReadBuffer(1); - std::vector output_buffer(ctx.GetWriteBufferSize(0)); - - const auto nv_result = - nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer); - if (command.is_out != 0) { - ctx.WriteBuffer(output_buffer); - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(nv_result); -} - -void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto fd = rp.Pop(); - const auto command = rp.PopRaw(); - LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); - - if (!is_initialized) { - ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); - return; - } - - const auto input_buffer = ctx.ReadBuffer(0); - std::vector output_buffer(ctx.GetWriteBufferSize(0)); - std::vector output_buffer_inline(ctx.GetWriteBufferSize(1)); - - const auto nv_result = - nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, output_buffer_inline); - if (command.is_out != 0) { - ctx.WriteBuffer(output_buffer, 0); - ctx.WriteBuffer(output_buffer_inline, 1); - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(nv_result); -} - -void NVDRV::Close(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - - if (!is_initialized) { - ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); - return; - } - - IPC::RequestParser rp{ctx}; - const auto fd = rp.Pop(); - const auto result = nvdrv->Close(fd); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(result); -} - -void NVDRV::Initialize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NVDRV, "(STUBBED) called"); - - is_initialized = true; - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(NvResult::Success); -} - -void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto fd = rp.Pop(); - const auto event_id = rp.Pop() & 0x00FF; - LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id); - - if (!is_initialized) { - ServiceError(ctx, NvResult::NotInitialized); - LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); - return; - } - - const auto nv_result = nvdrv->VerifyFD(fd); - if (nv_result != NvResult::Success) { - LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd); - ServiceError(ctx, nv_result); - return; - } - - if (event_id < MaxNvEvents) { - IPC::ResponseBuilder rb{ctx, 3, 1}; - rb.Push(ResultSuccess); - auto& event = nvdrv->GetEvent(event_id); - event.Clear(); - rb.PushCopyObjects(event); - rb.PushEnum(NvResult::Success); - } else { - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(NvResult::BadParameter); - } -} - -void NVDRV::SetAruid(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - pid = rp.Pop(); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x{:X}", pid); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(NvResult::Success); -} - -void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NVDRV, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} - -void NVDRV::GetStatus(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NVDRV, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(NvResult::Success); -} - -void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) { - // According to SwitchBrew, this has no inputs and no outputs, so effectively does nothing on - // retail hardware. - LOG_DEBUG(Service_NVDRV, "called"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} - -NVDRV::NVDRV(Core::System& system_, std::shared_ptr nvdrv_, const char* name) - : ServiceFramework{system_, name}, nvdrv{std::move(nvdrv_)} { - static const FunctionInfo functions[] = { - {0, &NVDRV::Open, "Open"}, - {1, &NVDRV::Ioctl1, "Ioctl"}, - {2, &NVDRV::Close, "Close"}, - {3, &NVDRV::Initialize, "Initialize"}, - {4, &NVDRV::QueryEvent, "QueryEvent"}, - {5, nullptr, "MapSharedMem"}, - {6, &NVDRV::GetStatus, "GetStatus"}, - {7, nullptr, "SetAruidForTest"}, - {8, &NVDRV::SetAruid, "SetAruid"}, - {9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"}, - {10, nullptr, "InitializeDevtools"}, - {11, &NVDRV::Ioctl2, "Ioctl2"}, - {12, &NVDRV::Ioctl3, "Ioctl3"}, - {13, &NVDRV::SetGraphicsFirmwareMemoryMarginEnabled, - "SetGraphicsFirmwareMemoryMarginEnabled"}, - }; - RegisterHandlers(functions); -} - -NVDRV::~NVDRV() = default; - -} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h deleted file mode 100644 index 0e764c53f..000000000 --- a/src/core/hle/service/nvdrv/interface.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/hle/service/nvdrv/nvdrv.h" -#include "core/hle/service/service.h" - -namespace Kernel { -class KWritableEvent; -} - -namespace Service::Nvidia { - -class NVDRV final : public ServiceFramework { -public: - explicit NVDRV(Core::System& system_, std::shared_ptr nvdrv_, const char* name); - ~NVDRV() override; - - void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value); - -private: - void Open(Kernel::HLERequestContext& ctx); - void Ioctl1(Kernel::HLERequestContext& ctx); - void Ioctl2(Kernel::HLERequestContext& ctx); - void Ioctl3(Kernel::HLERequestContext& ctx); - void Close(Kernel::HLERequestContext& ctx); - void Initialize(Kernel::HLERequestContext& ctx); - void QueryEvent(Kernel::HLERequestContext& ctx); - void SetAruid(Kernel::HLERequestContext& ctx); - void SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx); - void GetStatus(Kernel::HLERequestContext& ctx); - void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx); - - void ServiceError(Kernel::HLERequestContext& ctx, NvResult result); - - std::shared_ptr nvdrv; - - u64 pid{}; - bool is_initialized{}; -}; - -} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 74796dce1..03992af5e 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -20,8 +20,8 @@ #include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h" #include "core/hle/service/nvdrv/devices/nvhost_vic.h" #include "core/hle/service/nvdrv/devices/nvmap.h" -#include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvdrv/nvdrv_interface.h" #include "core/hle/service/nvdrv/nvmemp.h" #include "core/hle/service/nvdrv/syncpoint_manager.h" #include "core/hle/service/nvflinger/nvflinger.h" diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp new file mode 100644 index 000000000..d61fb73dc --- /dev/null +++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp @@ -0,0 +1,259 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/k_writable_event.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/service/nvdrv/nvdata.h" +#include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvdrv/nvdrv_interface.h" + +namespace Service::Nvidia { + +void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) { + nvdrv->SignalSyncpt(syncpoint_id, value); +} + +void NVDRV::Open(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + + if (!is_initialized) { + rb.Push(0); + rb.PushEnum(NvResult::NotInitialized); + + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto& buffer = ctx.ReadBuffer(); + const std::string device_name(buffer.begin(), buffer.end()); + + if (device_name == "/dev/nvhost-prof-gpu") { + rb.Push(0); + rb.PushEnum(NvResult::NotSupported); + + LOG_WARNING(Service_NVDRV, "/dev/nvhost-prof-gpu cannot be opened in production"); + return; + } + + DeviceFD fd = nvdrv->Open(device_name); + + rb.Push(fd); + rb.PushEnum(fd != INVALID_NVDRV_FD ? NvResult::Success : NvResult::FileOperationFailed); +} + +void NVDRV::ServiceError(Kernel::HLERequestContext& ctx, NvResult result) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(result); +} + +void NVDRV::Ioctl1(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto fd = rp.Pop(); + const auto command = rp.PopRaw(); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + // Check device + std::vector output_buffer(ctx.GetWriteBufferSize(0)); + const auto input_buffer = ctx.ReadBuffer(0); + + const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(nv_result); +} + +void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto fd = rp.Pop(); + const auto command = rp.PopRaw(); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto input_buffer = ctx.ReadBuffer(0); + const auto input_inlined_buffer = ctx.ReadBuffer(1); + std::vector output_buffer(ctx.GetWriteBufferSize(0)); + + const auto nv_result = + nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(nv_result); +} + +void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto fd = rp.Pop(); + const auto command = rp.PopRaw(); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto input_buffer = ctx.ReadBuffer(0); + std::vector output_buffer(ctx.GetWriteBufferSize(0)); + std::vector output_buffer_inline(ctx.GetWriteBufferSize(1)); + + const auto nv_result = + nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, output_buffer_inline); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer, 0); + ctx.WriteBuffer(output_buffer_inline, 1); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(nv_result); +} + +void NVDRV::Close(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + IPC::RequestParser rp{ctx}; + const auto fd = rp.Pop(); + const auto result = nvdrv->Close(fd); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(result); +} + +void NVDRV::Initialize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + + is_initialized = true; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(NvResult::Success); +} + +void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto fd = rp.Pop(); + const auto event_id = rp.Pop() & 0x00FF; + LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto nv_result = nvdrv->VerifyFD(fd); + if (nv_result != NvResult::Success) { + LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd); + ServiceError(ctx, nv_result); + return; + } + + if (event_id < MaxNvEvents) { + IPC::ResponseBuilder rb{ctx, 3, 1}; + rb.Push(ResultSuccess); + auto& event = nvdrv->GetEvent(event_id); + event.Clear(); + rb.PushCopyObjects(event); + rb.PushEnum(NvResult::Success); + } else { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(NvResult::BadParameter); + } +} + +void NVDRV::SetAruid(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + pid = rp.Pop(); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x{:X}", pid); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(NvResult::Success); +} + +void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void NVDRV::GetStatus(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(NvResult::Success); +} + +void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) { + // According to SwitchBrew, this has no inputs and no outputs, so effectively does nothing on + // retail hardware. + LOG_DEBUG(Service_NVDRV, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +NVDRV::NVDRV(Core::System& system_, std::shared_ptr nvdrv_, const char* name) + : ServiceFramework{system_, name}, nvdrv{std::move(nvdrv_)} { + static const FunctionInfo functions[] = { + {0, &NVDRV::Open, "Open"}, + {1, &NVDRV::Ioctl1, "Ioctl"}, + {2, &NVDRV::Close, "Close"}, + {3, &NVDRV::Initialize, "Initialize"}, + {4, &NVDRV::QueryEvent, "QueryEvent"}, + {5, nullptr, "MapSharedMem"}, + {6, &NVDRV::GetStatus, "GetStatus"}, + {7, nullptr, "SetAruidForTest"}, + {8, &NVDRV::SetAruid, "SetAruid"}, + {9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"}, + {10, nullptr, "InitializeDevtools"}, + {11, &NVDRV::Ioctl2, "Ioctl2"}, + {12, &NVDRV::Ioctl3, "Ioctl3"}, + {13, &NVDRV::SetGraphicsFirmwareMemoryMarginEnabled, + "SetGraphicsFirmwareMemoryMarginEnabled"}, + }; + RegisterHandlers(functions); +} + +NVDRV::~NVDRV() = default; + +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.h b/src/core/hle/service/nvdrv/nvdrv_interface.h new file mode 100644 index 000000000..0e764c53f --- /dev/null +++ b/src/core/hle/service/nvdrv/nvdrv_interface.h @@ -0,0 +1,45 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class KWritableEvent; +} + +namespace Service::Nvidia { + +class NVDRV final : public ServiceFramework { +public: + explicit NVDRV(Core::System& system_, std::shared_ptr nvdrv_, const char* name); + ~NVDRV() override; + + void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value); + +private: + void Open(Kernel::HLERequestContext& ctx); + void Ioctl1(Kernel::HLERequestContext& ctx); + void Ioctl2(Kernel::HLERequestContext& ctx); + void Ioctl3(Kernel::HLERequestContext& ctx); + void Close(Kernel::HLERequestContext& ctx); + void Initialize(Kernel::HLERequestContext& ctx); + void QueryEvent(Kernel::HLERequestContext& ctx); + void SetAruid(Kernel::HLERequestContext& ctx); + void SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx); + void GetStatus(Kernel::HLERequestContext& ctx); + void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx); + + void ServiceError(Kernel::HLERequestContext& ctx, NvResult result); + + std::shared_ptr nvdrv; + + u64 pid{}; + bool is_initialized{}; +}; + +} // namespace Service::Nvidia diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp deleted file mode 100644 index 1e31d05a6..000000000 --- a/src/core/hle/service/pctl/module.cpp +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/logging/log.h" -#include "core/core.h" -#include "core/file_sys/control_metadata.h" -#include "core/file_sys/patch_manager.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/k_process.h" -#include "core/hle/service/pctl/module.h" -#include "core/hle/service/pctl/pctl.h" - -namespace Service::PCTL { - -namespace Error { - -constexpr ResultCode ResultNoFreeCommunication{ErrorModule::PCTL, 101}; -constexpr ResultCode ResultStereoVisionRestricted{ErrorModule::PCTL, 104}; -constexpr ResultCode ResultNoCapability{ErrorModule::PCTL, 131}; -constexpr ResultCode ResultNoRestrictionEnabled{ErrorModule::PCTL, 181}; - -} // namespace Error - -class IParentalControlService final : public ServiceFramework { -public: - explicit IParentalControlService(Core::System& system_, Capability capability_) - : ServiceFramework{system_, "IParentalControlService"}, capability{capability_} { - // clang-format off - static const FunctionInfo functions[] = { - {1, &IParentalControlService::Initialize, "Initialize"}, - {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"}, - {1002, nullptr, "ConfirmLaunchApplicationPermission"}, - {1003, nullptr, "ConfirmResumeApplicationPermission"}, - {1004, nullptr, "ConfirmSnsPostPermission"}, - {1005, nullptr, "ConfirmSystemSettingsPermission"}, - {1006, nullptr, "IsRestrictionTemporaryUnlocked"}, - {1007, nullptr, "RevertRestrictionTemporaryUnlocked"}, - {1008, nullptr, "EnterRestrictedSystemSettings"}, - {1009, nullptr, "LeaveRestrictedSystemSettings"}, - {1010, nullptr, "IsRestrictedSystemSettingsEntered"}, - {1011, nullptr, "RevertRestrictedSystemSettingsEntered"}, - {1012, nullptr, "GetRestrictedFeatures"}, - {1013, &IParentalControlService::ConfirmStereoVisionPermission, "ConfirmStereoVisionPermission"}, - {1014, nullptr, "ConfirmPlayableApplicationVideoOld"}, - {1015, nullptr, "ConfirmPlayableApplicationVideo"}, - {1016, nullptr, "ConfirmShowNewsPermission"}, - {1017, nullptr, "EndFreeCommunication"}, - {1018, &IParentalControlService::IsFreeCommunicationAvailable, "IsFreeCommunicationAvailable"}, - {1031, &IParentalControlService::IsRestrictionEnabled, "IsRestrictionEnabled"}, - {1032, nullptr, "GetSafetyLevel"}, - {1033, nullptr, "SetSafetyLevel"}, - {1034, nullptr, "GetSafetyLevelSettings"}, - {1035, nullptr, "GetCurrentSettings"}, - {1036, nullptr, "SetCustomSafetyLevelSettings"}, - {1037, nullptr, "GetDefaultRatingOrganization"}, - {1038, nullptr, "SetDefaultRatingOrganization"}, - {1039, nullptr, "GetFreeCommunicationApplicationListCount"}, - {1042, nullptr, "AddToFreeCommunicationApplicationList"}, - {1043, nullptr, "DeleteSettings"}, - {1044, nullptr, "GetFreeCommunicationApplicationList"}, - {1045, nullptr, "UpdateFreeCommunicationApplicationList"}, - {1046, nullptr, "DisableFeaturesForReset"}, - {1047, nullptr, "NotifyApplicationDownloadStarted"}, - {1048, nullptr, "NotifyNetworkProfileCreated"}, - {1049, nullptr, "ResetFreeCommunicationApplicationList"}, - {1061, &IParentalControlService::ConfirmStereoVisionRestrictionConfigurable, "ConfirmStereoVisionRestrictionConfigurable"}, - {1062, &IParentalControlService::GetStereoVisionRestriction, "GetStereoVisionRestriction"}, - {1063, &IParentalControlService::SetStereoVisionRestriction, "SetStereoVisionRestriction"}, - {1064, &IParentalControlService::ResetConfirmedStereoVisionPermission, "ResetConfirmedStereoVisionPermission"}, - {1065, &IParentalControlService::IsStereoVisionPermitted, "IsStereoVisionPermitted"}, - {1201, nullptr, "UnlockRestrictionTemporarily"}, - {1202, nullptr, "UnlockSystemSettingsRestriction"}, - {1203, nullptr, "SetPinCode"}, - {1204, nullptr, "GenerateInquiryCode"}, - {1205, nullptr, "CheckMasterKey"}, - {1206, nullptr, "GetPinCodeLength"}, - {1207, nullptr, "GetPinCodeChangedEvent"}, - {1208, nullptr, "GetPinCode"}, - {1403, nullptr, "IsPairingActive"}, - {1406, nullptr, "GetSettingsLastUpdated"}, - {1411, nullptr, "GetPairingAccountInfo"}, - {1421, nullptr, "GetAccountNickname"}, - {1424, nullptr, "GetAccountState"}, - {1425, nullptr, "RequestPostEvents"}, - {1426, nullptr, "GetPostEventInterval"}, - {1427, nullptr, "SetPostEventInterval"}, - {1432, nullptr, "GetSynchronizationEvent"}, - {1451, nullptr, "StartPlayTimer"}, - {1452, nullptr, "StopPlayTimer"}, - {1453, nullptr, "IsPlayTimerEnabled"}, - {1454, nullptr, "GetPlayTimerRemainingTime"}, - {1455, nullptr, "IsRestrictedByPlayTimer"}, - {1456, nullptr, "GetPlayTimerSettings"}, - {1457, nullptr, "GetPlayTimerEventToRequestSuspension"}, - {1458, nullptr, "IsPlayTimerAlarmDisabled"}, - {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"}, - {1472, nullptr, "CancelNetworkRequest"}, - {1473, nullptr, "GetUnlinkedEvent"}, - {1474, nullptr, "ClearUnlinkedEvent"}, - {1601, nullptr, "DisableAllFeatures"}, - {1602, nullptr, "PostEnableAllFeatures"}, - {1603, nullptr, "IsAllFeaturesDisabled"}, - {1901, nullptr, "DeleteFromFreeCommunicationApplicationListForDebug"}, - {1902, nullptr, "ClearFreeCommunicationApplicationListForDebug"}, - {1903, nullptr, "GetExemptApplicationListCountForDebug"}, - {1904, nullptr, "GetExemptApplicationListForDebug"}, - {1905, nullptr, "UpdateExemptApplicationListForDebug"}, - {1906, nullptr, "AddToExemptApplicationListForDebug"}, - {1907, nullptr, "DeleteFromExemptApplicationListForDebug"}, - {1908, nullptr, "ClearExemptApplicationListForDebug"}, - {1941, nullptr, "DeletePairing"}, - {1951, nullptr, "SetPlayTimerSettingsForDebug"}, - {1952, nullptr, "GetPlayTimerSpentTimeForTest"}, - {1953, nullptr, "SetPlayTimerAlarmDisabledForDebug"}, - {2001, nullptr, "RequestPairingAsync"}, - {2002, nullptr, "FinishRequestPairing"}, - {2003, nullptr, "AuthorizePairingAsync"}, - {2004, nullptr, "FinishAuthorizePairing"}, - {2005, nullptr, "RetrievePairingInfoAsync"}, - {2006, nullptr, "FinishRetrievePairingInfo"}, - {2007, nullptr, "UnlinkPairingAsync"}, - {2008, nullptr, "FinishUnlinkPairing"}, - {2009, nullptr, "GetAccountMiiImageAsync"}, - {2010, nullptr, "FinishGetAccountMiiImage"}, - {2011, nullptr, "GetAccountMiiImageContentTypeAsync"}, - {2012, nullptr, "FinishGetAccountMiiImageContentType"}, - {2013, nullptr, "SynchronizeParentalControlSettingsAsync"}, - {2014, nullptr, "FinishSynchronizeParentalControlSettings"}, - {2015, nullptr, "FinishSynchronizeParentalControlSettingsWithLastUpdated"}, - {2016, nullptr, "RequestUpdateExemptionListAsync"}, - }; - // clang-format on - RegisterHandlers(functions); - } - -private: - bool CheckFreeCommunicationPermissionImpl() const { - if (states.temporary_unlocked) { - return true; - } - if ((states.application_info.parental_control_flag & 1) == 0) { - return true; - } - if (pin_code[0] == '\0') { - return true; - } - if (!settings.is_free_communication_default_on) { - return true; - } - // TODO(ogniK): Check for blacklisted/exempted applications. Return false can happen here - // but as we don't have multiproceses support yet, we can just assume our application is - // valid for the time being - return true; - } - - bool ConfirmStereoVisionPermissionImpl() const { - if (states.temporary_unlocked) { - return true; - } - if (pin_code[0] == '\0') { - return true; - } - if (!settings.is_stero_vision_restricted) { - return false; - } - return true; - } - - void SetStereoVisionRestrictionImpl(bool is_restricted) { - if (settings.disabled) { - return; - } - - if (pin_code[0] == '\0') { - return; - } - settings.is_stero_vision_restricted = is_restricted; - } - - void Initialize(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - IPC::ResponseBuilder rb{ctx, 2}; - - if (False(capability & (Capability::Application | Capability::System))) { - LOG_ERROR(Service_PCTL, "Invalid capability! capability={:X}", capability); - return; - } - - // TODO(ogniK): Recovery flag initialization for pctl:r - - const auto tid = system.CurrentProcess()->GetTitleID(); - if (tid != 0) { - const FileSys::PatchManager pm{tid, system.GetFileSystemController(), - system.GetContentProvider()}; - const auto control = pm.GetControlMetadata(); - if (control.first) { - states.tid_from_event = 0; - states.launch_time_valid = false; - states.is_suspended = false; - states.free_communication = false; - states.stereo_vision = false; - states.application_info = ApplicationInfo{ - .tid = tid, - .age_rating = control.first->GetRatingAge(), - .parental_control_flag = control.first->GetParentalControlFlag(), - .capability = capability, - }; - - if (False(capability & (Capability::System | Capability::Recovery))) { - // TODO(ogniK): Signal application launch event - } - } - } - - rb.Push(ResultSuccess); - } - - void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - - IPC::ResponseBuilder rb{ctx, 2}; - if (!CheckFreeCommunicationPermissionImpl()) { - rb.Push(Error::ResultNoFreeCommunication); - } else { - rb.Push(ResultSuccess); - } - - states.free_communication = true; - } - - void ConfirmStereoVisionPermission(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - states.stereo_vision = true; - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - void IsFreeCommunicationAvailable(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_PCTL, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 2}; - if (!CheckFreeCommunicationPermissionImpl()) { - rb.Push(Error::ResultNoFreeCommunication); - } else { - rb.Push(ResultSuccess); - } - } - - void IsRestrictionEnabled(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - if (False(capability & (Capability::Status | Capability::Recovery))) { - LOG_ERROR(Service_PCTL, "Application does not have Status or Recovery capabilities!"); - rb.Push(Error::ResultNoCapability); - rb.Push(false); - return; - } - - rb.Push(pin_code[0] != '\0'); - } - - void ConfirmStereoVisionRestrictionConfigurable(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - - IPC::ResponseBuilder rb{ctx, 2}; - - if (False(capability & Capability::StereoVision)) { - LOG_ERROR(Service_PCTL, "Application does not have StereoVision capability!"); - rb.Push(Error::ResultNoCapability); - return; - } - - if (pin_code[0] == '\0') { - rb.Push(Error::ResultNoRestrictionEnabled); - return; - } - - rb.Push(ResultSuccess); - } - - void IsStereoVisionPermitted(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - if (!ConfirmStereoVisionPermissionImpl()) { - rb.Push(Error::ResultStereoVisionRestricted); - rb.Push(false); - } else { - rb.Push(ResultSuccess); - rb.Push(true); - } - } - - void SetStereoVisionRestriction(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto can_use = rp.Pop(); - LOG_DEBUG(Service_PCTL, "called, can_use={}", can_use); - - IPC::ResponseBuilder rb{ctx, 2}; - if (False(capability & Capability::StereoVision)) { - LOG_ERROR(Service_PCTL, "Application does not have StereoVision capability!"); - rb.Push(Error::ResultNoCapability); - return; - } - - SetStereoVisionRestrictionImpl(can_use); - rb.Push(ResultSuccess); - } - - void GetStereoVisionRestriction(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - if (False(capability & Capability::StereoVision)) { - LOG_ERROR(Service_PCTL, "Application does not have StereoVision capability!"); - rb.Push(Error::ResultNoCapability); - rb.Push(false); - return; - } - - rb.Push(ResultSuccess); - rb.Push(settings.is_stero_vision_restricted); - } - - void ResetConfirmedStereoVisionPermission(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - - states.stereo_vision = false; - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - struct ApplicationInfo { - u64 tid{}; - std::array age_rating{}; - u32 parental_control_flag{}; - Capability capability{}; - }; - - struct States { - u64 current_tid{}; - ApplicationInfo application_info{}; - u64 tid_from_event{}; - bool launch_time_valid{}; - bool is_suspended{}; - bool temporary_unlocked{}; - bool free_communication{}; - bool stereo_vision{}; - }; - - struct ParentalControlSettings { - bool is_stero_vision_restricted{}; - bool is_free_communication_default_on{}; - bool disabled{}; - }; - - States states{}; - ParentalControlSettings settings{}; - std::array pin_code{}; - Capability capability{}; -}; - -void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - // TODO(ogniK): Get TID from process - - rb.PushIpcInterface(system, capability); -} - -void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PCTL, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, capability); -} - -Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, - const char* name_, Capability capability_) - : ServiceFramework{system_, name_}, module{std::move(module_)}, capability{capability_} {} - -Module::Interface::~Interface() = default; - -void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { - auto module = std::make_shared(); - std::make_shared(system, module, "pctl", - Capability::Application | Capability::SnsPost | Capability::Status | - Capability::StereoVision) - ->InstallAsService(service_manager); - // TODO(ogniK): Implement remaining capabilities - std::make_shared(system, module, "pctl:a", Capability::None) - ->InstallAsService(service_manager); - std::make_shared(system, module, "pctl:r", Capability::None) - ->InstallAsService(service_manager); - std::make_shared(system, module, "pctl:s", Capability::None) - ->InstallAsService(service_manager); -} - -} // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/module.h b/src/core/hle/service/pctl/module.h deleted file mode 100644 index f25c5c557..000000000 --- a/src/core/hle/service/pctl/module.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 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 "core/hle/service/service.h" - -namespace Core { -class System; -} - -namespace Service::PCTL { - -enum class Capability : u32 { - None = 0, - Application = 1 << 0, - SnsPost = 1 << 1, - Recovery = 1 << 6, - Status = 1 << 8, - StereoVision = 1 << 9, - System = 1 << 15, -}; -DECLARE_ENUM_FLAG_OPERATORS(Capability); - -class Module final { -public: - class Interface : public ServiceFramework { - public: - explicit Interface(Core::System& system_, std::shared_ptr module_, - const char* name_, Capability capability_); - ~Interface() override; - - void CreateService(Kernel::HLERequestContext& ctx); - void CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx); - - protected: - std::shared_ptr module; - - private: - Capability capability{}; - }; -}; - -/// Registers all PCTL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); - -} // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h index ea3b97823..1d28900b2 100644 --- a/src/core/hle/service/pctl/pctl.h +++ b/src/core/hle/service/pctl/pctl.h @@ -4,7 +4,7 @@ #pragma once -#include "core/hle/service/pctl/module.h" +#include "core/hle/service/pctl/pctl_module.h" namespace Core { class System; diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp new file mode 100644 index 000000000..6949fcf3b --- /dev/null +++ b/src/core/hle/service/pctl/pctl_module.cpp @@ -0,0 +1,406 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/service/pctl/pctl.h" +#include "core/hle/service/pctl/pctl_module.h" + +namespace Service::PCTL { + +namespace Error { + +constexpr ResultCode ResultNoFreeCommunication{ErrorModule::PCTL, 101}; +constexpr ResultCode ResultStereoVisionRestricted{ErrorModule::PCTL, 104}; +constexpr ResultCode ResultNoCapability{ErrorModule::PCTL, 131}; +constexpr ResultCode ResultNoRestrictionEnabled{ErrorModule::PCTL, 181}; + +} // namespace Error + +class IParentalControlService final : public ServiceFramework { +public: + explicit IParentalControlService(Core::System& system_, Capability capability_) + : ServiceFramework{system_, "IParentalControlService"}, capability{capability_} { + // clang-format off + static const FunctionInfo functions[] = { + {1, &IParentalControlService::Initialize, "Initialize"}, + {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"}, + {1002, nullptr, "ConfirmLaunchApplicationPermission"}, + {1003, nullptr, "ConfirmResumeApplicationPermission"}, + {1004, nullptr, "ConfirmSnsPostPermission"}, + {1005, nullptr, "ConfirmSystemSettingsPermission"}, + {1006, nullptr, "IsRestrictionTemporaryUnlocked"}, + {1007, nullptr, "RevertRestrictionTemporaryUnlocked"}, + {1008, nullptr, "EnterRestrictedSystemSettings"}, + {1009, nullptr, "LeaveRestrictedSystemSettings"}, + {1010, nullptr, "IsRestrictedSystemSettingsEntered"}, + {1011, nullptr, "RevertRestrictedSystemSettingsEntered"}, + {1012, nullptr, "GetRestrictedFeatures"}, + {1013, &IParentalControlService::ConfirmStereoVisionPermission, "ConfirmStereoVisionPermission"}, + {1014, nullptr, "ConfirmPlayableApplicationVideoOld"}, + {1015, nullptr, "ConfirmPlayableApplicationVideo"}, + {1016, nullptr, "ConfirmShowNewsPermission"}, + {1017, nullptr, "EndFreeCommunication"}, + {1018, &IParentalControlService::IsFreeCommunicationAvailable, "IsFreeCommunicationAvailable"}, + {1031, &IParentalControlService::IsRestrictionEnabled, "IsRestrictionEnabled"}, + {1032, nullptr, "GetSafetyLevel"}, + {1033, nullptr, "SetSafetyLevel"}, + {1034, nullptr, "GetSafetyLevelSettings"}, + {1035, nullptr, "GetCurrentSettings"}, + {1036, nullptr, "SetCustomSafetyLevelSettings"}, + {1037, nullptr, "GetDefaultRatingOrganization"}, + {1038, nullptr, "SetDefaultRatingOrganization"}, + {1039, nullptr, "GetFreeCommunicationApplicationListCount"}, + {1042, nullptr, "AddToFreeCommunicationApplicationList"}, + {1043, nullptr, "DeleteSettings"}, + {1044, nullptr, "GetFreeCommunicationApplicationList"}, + {1045, nullptr, "UpdateFreeCommunicationApplicationList"}, + {1046, nullptr, "DisableFeaturesForReset"}, + {1047, nullptr, "NotifyApplicationDownloadStarted"}, + {1048, nullptr, "NotifyNetworkProfileCreated"}, + {1049, nullptr, "ResetFreeCommunicationApplicationList"}, + {1061, &IParentalControlService::ConfirmStereoVisionRestrictionConfigurable, "ConfirmStereoVisionRestrictionConfigurable"}, + {1062, &IParentalControlService::GetStereoVisionRestriction, "GetStereoVisionRestriction"}, + {1063, &IParentalControlService::SetStereoVisionRestriction, "SetStereoVisionRestriction"}, + {1064, &IParentalControlService::ResetConfirmedStereoVisionPermission, "ResetConfirmedStereoVisionPermission"}, + {1065, &IParentalControlService::IsStereoVisionPermitted, "IsStereoVisionPermitted"}, + {1201, nullptr, "UnlockRestrictionTemporarily"}, + {1202, nullptr, "UnlockSystemSettingsRestriction"}, + {1203, nullptr, "SetPinCode"}, + {1204, nullptr, "GenerateInquiryCode"}, + {1205, nullptr, "CheckMasterKey"}, + {1206, nullptr, "GetPinCodeLength"}, + {1207, nullptr, "GetPinCodeChangedEvent"}, + {1208, nullptr, "GetPinCode"}, + {1403, nullptr, "IsPairingActive"}, + {1406, nullptr, "GetSettingsLastUpdated"}, + {1411, nullptr, "GetPairingAccountInfo"}, + {1421, nullptr, "GetAccountNickname"}, + {1424, nullptr, "GetAccountState"}, + {1425, nullptr, "RequestPostEvents"}, + {1426, nullptr, "GetPostEventInterval"}, + {1427, nullptr, "SetPostEventInterval"}, + {1432, nullptr, "GetSynchronizationEvent"}, + {1451, nullptr, "StartPlayTimer"}, + {1452, nullptr, "StopPlayTimer"}, + {1453, nullptr, "IsPlayTimerEnabled"}, + {1454, nullptr, "GetPlayTimerRemainingTime"}, + {1455, nullptr, "IsRestrictedByPlayTimer"}, + {1456, nullptr, "GetPlayTimerSettings"}, + {1457, nullptr, "GetPlayTimerEventToRequestSuspension"}, + {1458, nullptr, "IsPlayTimerAlarmDisabled"}, + {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"}, + {1472, nullptr, "CancelNetworkRequest"}, + {1473, nullptr, "GetUnlinkedEvent"}, + {1474, nullptr, "ClearUnlinkedEvent"}, + {1601, nullptr, "DisableAllFeatures"}, + {1602, nullptr, "PostEnableAllFeatures"}, + {1603, nullptr, "IsAllFeaturesDisabled"}, + {1901, nullptr, "DeleteFromFreeCommunicationApplicationListForDebug"}, + {1902, nullptr, "ClearFreeCommunicationApplicationListForDebug"}, + {1903, nullptr, "GetExemptApplicationListCountForDebug"}, + {1904, nullptr, "GetExemptApplicationListForDebug"}, + {1905, nullptr, "UpdateExemptApplicationListForDebug"}, + {1906, nullptr, "AddToExemptApplicationListForDebug"}, + {1907, nullptr, "DeleteFromExemptApplicationListForDebug"}, + {1908, nullptr, "ClearExemptApplicationListForDebug"}, + {1941, nullptr, "DeletePairing"}, + {1951, nullptr, "SetPlayTimerSettingsForDebug"}, + {1952, nullptr, "GetPlayTimerSpentTimeForTest"}, + {1953, nullptr, "SetPlayTimerAlarmDisabledForDebug"}, + {2001, nullptr, "RequestPairingAsync"}, + {2002, nullptr, "FinishRequestPairing"}, + {2003, nullptr, "AuthorizePairingAsync"}, + {2004, nullptr, "FinishAuthorizePairing"}, + {2005, nullptr, "RetrievePairingInfoAsync"}, + {2006, nullptr, "FinishRetrievePairingInfo"}, + {2007, nullptr, "UnlinkPairingAsync"}, + {2008, nullptr, "FinishUnlinkPairing"}, + {2009, nullptr, "GetAccountMiiImageAsync"}, + {2010, nullptr, "FinishGetAccountMiiImage"}, + {2011, nullptr, "GetAccountMiiImageContentTypeAsync"}, + {2012, nullptr, "FinishGetAccountMiiImageContentType"}, + {2013, nullptr, "SynchronizeParentalControlSettingsAsync"}, + {2014, nullptr, "FinishSynchronizeParentalControlSettings"}, + {2015, nullptr, "FinishSynchronizeParentalControlSettingsWithLastUpdated"}, + {2016, nullptr, "RequestUpdateExemptionListAsync"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + bool CheckFreeCommunicationPermissionImpl() const { + if (states.temporary_unlocked) { + return true; + } + if ((states.application_info.parental_control_flag & 1) == 0) { + return true; + } + if (pin_code[0] == '\0') { + return true; + } + if (!settings.is_free_communication_default_on) { + return true; + } + // TODO(ogniK): Check for blacklisted/exempted applications. Return false can happen here + // but as we don't have multiproceses support yet, we can just assume our application is + // valid for the time being + return true; + } + + bool ConfirmStereoVisionPermissionImpl() const { + if (states.temporary_unlocked) { + return true; + } + if (pin_code[0] == '\0') { + return true; + } + if (!settings.is_stero_vision_restricted) { + return false; + } + return true; + } + + void SetStereoVisionRestrictionImpl(bool is_restricted) { + if (settings.disabled) { + return; + } + + if (pin_code[0] == '\0') { + return; + } + settings.is_stero_vision_restricted = is_restricted; + } + + void Initialize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + + if (False(capability & (Capability::Application | Capability::System))) { + LOG_ERROR(Service_PCTL, "Invalid capability! capability={:X}", capability); + return; + } + + // TODO(ogniK): Recovery flag initialization for pctl:r + + const auto tid = system.CurrentProcess()->GetTitleID(); + if (tid != 0) { + const FileSys::PatchManager pm{tid, system.GetFileSystemController(), + system.GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + if (control.first) { + states.tid_from_event = 0; + states.launch_time_valid = false; + states.is_suspended = false; + states.free_communication = false; + states.stereo_vision = false; + states.application_info = ApplicationInfo{ + .tid = tid, + .age_rating = control.first->GetRatingAge(), + .parental_control_flag = control.first->GetParentalControlFlag(), + .capability = capability, + }; + + if (False(capability & (Capability::System | Capability::Recovery))) { + // TODO(ogniK): Signal application launch event + } + } + } + + rb.Push(ResultSuccess); + } + + void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + if (!CheckFreeCommunicationPermissionImpl()) { + rb.Push(Error::ResultNoFreeCommunication); + } else { + rb.Push(ResultSuccess); + } + + states.free_communication = true; + } + + void ConfirmStereoVisionPermission(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + states.stereo_vision = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void IsFreeCommunicationAvailable(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + if (!CheckFreeCommunicationPermissionImpl()) { + rb.Push(Error::ResultNoFreeCommunication); + } else { + rb.Push(ResultSuccess); + } + } + + void IsRestrictionEnabled(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + if (False(capability & (Capability::Status | Capability::Recovery))) { + LOG_ERROR(Service_PCTL, "Application does not have Status or Recovery capabilities!"); + rb.Push(Error::ResultNoCapability); + rb.Push(false); + return; + } + + rb.Push(pin_code[0] != '\0'); + } + + void ConfirmStereoVisionRestrictionConfigurable(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + + if (False(capability & Capability::StereoVision)) { + LOG_ERROR(Service_PCTL, "Application does not have StereoVision capability!"); + rb.Push(Error::ResultNoCapability); + return; + } + + if (pin_code[0] == '\0') { + rb.Push(Error::ResultNoRestrictionEnabled); + return; + } + + rb.Push(ResultSuccess); + } + + void IsStereoVisionPermitted(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + if (!ConfirmStereoVisionPermissionImpl()) { + rb.Push(Error::ResultStereoVisionRestricted); + rb.Push(false); + } else { + rb.Push(ResultSuccess); + rb.Push(true); + } + } + + void SetStereoVisionRestriction(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto can_use = rp.Pop(); + LOG_DEBUG(Service_PCTL, "called, can_use={}", can_use); + + IPC::ResponseBuilder rb{ctx, 2}; + if (False(capability & Capability::StereoVision)) { + LOG_ERROR(Service_PCTL, "Application does not have StereoVision capability!"); + rb.Push(Error::ResultNoCapability); + return; + } + + SetStereoVisionRestrictionImpl(can_use); + rb.Push(ResultSuccess); + } + + void GetStereoVisionRestriction(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + if (False(capability & Capability::StereoVision)) { + LOG_ERROR(Service_PCTL, "Application does not have StereoVision capability!"); + rb.Push(Error::ResultNoCapability); + rb.Push(false); + return; + } + + rb.Push(ResultSuccess); + rb.Push(settings.is_stero_vision_restricted); + } + + void ResetConfirmedStereoVisionPermission(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + + states.stereo_vision = false; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + struct ApplicationInfo { + u64 tid{}; + std::array age_rating{}; + u32 parental_control_flag{}; + Capability capability{}; + }; + + struct States { + u64 current_tid{}; + ApplicationInfo application_info{}; + u64 tid_from_event{}; + bool launch_time_valid{}; + bool is_suspended{}; + bool temporary_unlocked{}; + bool free_communication{}; + bool stereo_vision{}; + }; + + struct ParentalControlSettings { + bool is_stero_vision_restricted{}; + bool is_free_communication_default_on{}; + bool disabled{}; + }; + + States states{}; + ParentalControlSettings settings{}; + std::array pin_code{}; + Capability capability{}; +}; + +void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + // TODO(ogniK): Get TID from process + + rb.PushIpcInterface(system, capability); +} + +void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, capability); +} + +Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, + const char* name_, Capability capability_) + : ServiceFramework{system_, name_}, module{std::move(module_)}, capability{capability_} {} + +Module::Interface::~Interface() = default; + +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + auto module = std::make_shared(); + std::make_shared(system, module, "pctl", + Capability::Application | Capability::SnsPost | Capability::Status | + Capability::StereoVision) + ->InstallAsService(service_manager); + // TODO(ogniK): Implement remaining capabilities + std::make_shared(system, module, "pctl:a", Capability::None) + ->InstallAsService(service_manager); + std::make_shared(system, module, "pctl:r", Capability::None) + ->InstallAsService(service_manager); + std::make_shared(system, module, "pctl:s", Capability::None) + ->InstallAsService(service_manager); +} + +} // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/pctl_module.h b/src/core/hle/service/pctl/pctl_module.h new file mode 100644 index 000000000..f25c5c557 --- /dev/null +++ b/src/core/hle/service/pctl/pctl_module.h @@ -0,0 +1,49 @@ +// Copyright 2018 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 "core/hle/service/service.h" + +namespace Core { +class System; +} + +namespace Service::PCTL { + +enum class Capability : u32 { + None = 0, + Application = 1 << 0, + SnsPost = 1 << 1, + Recovery = 1 << 6, + Status = 1 << 8, + StereoVision = 1 << 9, + System = 1 << 15, +}; +DECLARE_ENUM_FLAG_OPERATORS(Capability); + +class Module final { +public: + class Interface : public ServiceFramework { + public: + explicit Interface(Core::System& system_, std::shared_ptr module_, + const char* name_, Capability capability_); + ~Interface() override; + + void CreateService(Kernel::HLERequestContext& ctx); + void CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr module; + + private: + Capability capability{}; + }; +}; + +/// Registers all PCTL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); + +} // namespace Service::PCTL diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 663b83cd3..e6fba88b2 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -21,7 +21,7 @@ #include "core/hle/service/aoc/aoc_u.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/audio/audio.h" -#include "core/hle/service/bcat/module.h" +#include "core/hle/service/bcat/bcat_module.h" #include "core/hle/service/bpc/bpc.h" #include "core/hle/service/btdrv/btdrv.h" #include "core/hle/service/btm/btm.h" @@ -54,7 +54,7 @@ #include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/olsc/olsc.h" #include "core/hle/service/pcie/pcie.h" -#include "core/hle/service/pctl/module.h" +#include "core/hle/service/pctl/pctl_module.h" #include "core/hle/service/pcv/pcv.h" #include "core/hle/service/pm/pm.h" #include "core/hle/service/prepo/prepo.h" @@ -64,7 +64,7 @@ #include "core/hle/service/set/settings.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/sockets/sockets.h" -#include "core/hle/service/spl/module.h" +#include "core/hle/service/spl/spl_module.h" #include "core/hle/service/ssl/ssl.h" #include "core/hle/service/time/time.h" #include "core/hle/service/usb/usb.h" diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp deleted file mode 100644 index 8b9418e0f..000000000 --- a/src/core/hle/service/sm/controller.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2018 yuzu emulator team -// 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/hle/ipc_helpers.h" -#include "core/hle/kernel/k_client_port.h" -#include "core/hle/kernel/k_client_session.h" -#include "core/hle/kernel/k_port.h" -#include "core/hle/kernel/k_scoped_resource_reservation.h" -#include "core/hle/kernel/k_server_port.h" -#include "core/hle/kernel/k_server_session.h" -#include "core/hle/kernel/k_session.h" -#include "core/hle/service/sm/controller.h" - -namespace Service::SM { - -void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) { - ASSERT_MSG(!ctx.Session()->IsDomain(), "Session is already a domain"); - LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetId()); - ctx.Session()->ConvertToDomain(); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(1); // Converted sessions start with 1 request handler -} - -void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service, "called"); - - auto& parent_session = *ctx.Session()->GetParent(); - auto& parent_port = parent_session.GetParent()->GetParent()->GetClientPort(); - auto& session_manager = parent_session.GetServerSession().GetSessionRequestManager(); - - // Create a session. - Kernel::KClientSession* session{}; - const ResultCode result = parent_port.CreateSession(std::addressof(session), session_manager); - if (result.IsError()) { - LOG_CRITICAL(Service, "CreateSession failed with error 0x{:08X}", result.raw); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); - } - - // We succeeded. - IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; - rb.Push(ResultSuccess); - rb.PushMoveObjects(session); -} - -void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service, "called"); - - CloneCurrentObject(ctx); -} - -void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(0x8000); -} - -// https://switchbrew.org/wiki/IPC_Marshalling -Controller::Controller(Core::System& system_) : ServiceFramework{system_, "IpcController"} { - static const FunctionInfo functions[] = { - {0, &Controller::ConvertCurrentObjectToDomain, "ConvertCurrentObjectToDomain"}, - {1, nullptr, "CopyFromCurrentDomain"}, - {2, &Controller::CloneCurrentObject, "CloneCurrentObject"}, - {3, &Controller::QueryPointerBufferSize, "QueryPointerBufferSize"}, - {4, &Controller::CloneCurrentObjectEx, "CloneCurrentObjectEx"}, - }; - RegisterHandlers(functions); -} - -Controller::~Controller() = default; - -} // namespace Service::SM diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h deleted file mode 100644 index 7494f898d..000000000 --- a/src/core/hle/service/sm/controller.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 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 Core { -class System; -} - -namespace Service::SM { - -class Controller final : public ServiceFramework { -public: - explicit Controller(Core::System& system_); - ~Controller() override; - -private: - void ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx); - void CloneCurrentObject(Kernel::HLERequestContext& ctx); - void CloneCurrentObjectEx(Kernel::HLERequestContext& ctx); - void QueryPointerBufferSize(Kernel::HLERequestContext& ctx); -}; - -} // namespace Service::SM diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index c7828c3bd..15034abed 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -14,8 +14,8 @@ #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/k_session.h" #include "core/hle/result.h" -#include "core/hle/service/sm/controller.h" #include "core/hle/service/sm/sm.h" +#include "core/hle/service/sm/sm_controller.h" namespace Service::SM { diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp new file mode 100644 index 000000000..b5fbc4569 --- /dev/null +++ b/src/core/hle/service/sm/sm_controller.cpp @@ -0,0 +1,80 @@ +// Copyright 2018 yuzu emulator team +// 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/hle/ipc_helpers.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_port.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_server_port.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/service/sm/sm_controller.h" + +namespace Service::SM { + +void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) { + ASSERT_MSG(!ctx.Session()->IsDomain(), "Session is already a domain"); + LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetId()); + ctx.Session()->ConvertToDomain(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(1); // Converted sessions start with 1 request handler +} + +void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service, "called"); + + auto& parent_session = *ctx.Session()->GetParent(); + auto& parent_port = parent_session.GetParent()->GetParent()->GetClientPort(); + auto& session_manager = parent_session.GetServerSession().GetSessionRequestManager(); + + // Create a session. + Kernel::KClientSession* session{}; + const ResultCode result = parent_port.CreateSession(std::addressof(session), session_manager); + if (result.IsError()) { + LOG_CRITICAL(Service, "CreateSession failed with error 0x{:08X}", result.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + // We succeeded. + IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; + rb.Push(ResultSuccess); + rb.PushMoveObjects(session); +} + +void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service, "called"); + + CloneCurrentObject(ctx); +} + +void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0x8000); +} + +// https://switchbrew.org/wiki/IPC_Marshalling +Controller::Controller(Core::System& system_) : ServiceFramework{system_, "IpcController"} { + static const FunctionInfo functions[] = { + {0, &Controller::ConvertCurrentObjectToDomain, "ConvertCurrentObjectToDomain"}, + {1, nullptr, "CopyFromCurrentDomain"}, + {2, &Controller::CloneCurrentObject, "CloneCurrentObject"}, + {3, &Controller::QueryPointerBufferSize, "QueryPointerBufferSize"}, + {4, &Controller::CloneCurrentObjectEx, "CloneCurrentObjectEx"}, + }; + RegisterHandlers(functions); +} + +Controller::~Controller() = default; + +} // namespace Service::SM diff --git a/src/core/hle/service/sm/sm_controller.h b/src/core/hle/service/sm/sm_controller.h new file mode 100644 index 000000000..7494f898d --- /dev/null +++ b/src/core/hle/service/sm/sm_controller.h @@ -0,0 +1,27 @@ +// Copyright 2018 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 Core { +class System; +} + +namespace Service::SM { + +class Controller final : public ServiceFramework { +public: + explicit Controller(Core::System& system_); + ~Controller() override; + +private: + void ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx); + void CloneCurrentObject(Kernel::HLERequestContext& ctx); + void CloneCurrentObjectEx(Kernel::HLERequestContext& ctx); + void QueryPointerBufferSize(Kernel::HLERequestContext& ctx); +}; + +} // namespace Service::SM diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h index 5c0bd2199..0d03cc6cb 100644 --- a/src/core/hle/service/spl/csrng.h +++ b/src/core/hle/service/spl/csrng.h @@ -4,7 +4,7 @@ #pragma once -#include "core/hle/service/spl/module.h" +#include "core/hle/service/spl/spl_module.h" namespace Core { class System; diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp deleted file mode 100644 index ebb179aa8..000000000 --- a/src/core/hle/service/spl/module.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include "common/logging/log.h" -#include "common/settings.h" -#include "core/hle/api_version.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/spl/csrng.h" -#include "core/hle/service/spl/module.h" -#include "core/hle/service/spl/spl.h" - -namespace Service::SPL { - -Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, - const char* name) - : ServiceFramework{system_, name}, module{std::move(module_)}, - rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {} - -Module::Interface::~Interface() = default; - -void Module::Interface::GetConfig(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto config_item = rp.PopEnum(); - - // This should call svcCallSecureMonitor with the appropriate args. - // Since we do not have it implemented yet, we will use this for now. - const auto smc_result = GetConfigImpl(config_item); - const auto result_code = smc_result.Code(); - - if (smc_result.Failed()) { - LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item, - result_code.raw); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result_code); - } - - LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item, - result_code.raw, *smc_result); - - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(result_code); - rb.Push(*smc_result); -} - -void Module::Interface::ModularExponentiate(Kernel::HLERequestContext& ctx) { - UNIMPLEMENTED_MSG("ModularExponentiate is not implemented!"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSecureMonitorNotImplemented); -} - -void Module::Interface::SetConfig(Kernel::HLERequestContext& ctx) { - UNIMPLEMENTED_MSG("SetConfig is not implemented!"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSecureMonitorNotImplemented); -} - -void Module::Interface::GenerateRandomBytes(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_SPL, "called"); - - const std::size_t size = ctx.GetWriteBufferSize(); - - std::uniform_int_distribution distribution(0, std::numeric_limits::max()); - std::vector data(size); - std::generate(data.begin(), data.end(), [&] { return static_cast(distribution(rng)); }); - - ctx.WriteBuffer(data); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} - -void Module::Interface::IsDevelopment(Kernel::HLERequestContext& ctx) { - UNIMPLEMENTED_MSG("IsDevelopment is not implemented!"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSecureMonitorNotImplemented); -} - -void Module::Interface::SetBootReason(Kernel::HLERequestContext& ctx) { - UNIMPLEMENTED_MSG("SetBootReason is not implemented!"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSecureMonitorNotImplemented); -} - -void Module::Interface::GetBootReason(Kernel::HLERequestContext& ctx) { - UNIMPLEMENTED_MSG("GetBootReason is not implemented!"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSecureMonitorNotImplemented); -} - -ResultVal Module::Interface::GetConfigImpl(ConfigItem config_item) const { - switch (config_item) { - case ConfigItem::DisableProgramVerification: - case ConfigItem::DramId: - case ConfigItem::SecurityEngineInterruptNumber: - case ConfigItem::FuseVersion: - case ConfigItem::HardwareType: - case ConfigItem::HardwareState: - case ConfigItem::IsRecoveryBoot: - case ConfigItem::DeviceId: - case ConfigItem::BootReason: - case ConfigItem::MemoryMode: - case ConfigItem::IsDevelopmentFunctionEnabled: - case ConfigItem::KernelConfiguration: - case ConfigItem::IsChargerHiZModeEnabled: - case ConfigItem::QuestState: - case ConfigItem::RegulatorType: - case ConfigItem::DeviceUniqueKeyGeneration: - case ConfigItem::Package2Hash: - return ResultSecureMonitorNotImplemented; - case ConfigItem::ExosphereApiVersion: - // Get information about the current exosphere version. - return MakeResult((u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) | - (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) | - (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) | - (static_cast(HLE::ApiVersion::GetTargetFirmware()))); - case ConfigItem::ExosphereNeedsReboot: - // We are executing, so we aren't in the process of rebooting. - return MakeResult(u64{0}); - case ConfigItem::ExosphereNeedsShutdown: - // We are executing, so we aren't in the process of shutting down. - return MakeResult(u64{0}); - case ConfigItem::ExosphereGitCommitHash: - // Get information about the current exosphere git commit hash. - return MakeResult(u64{0}); - case ConfigItem::ExosphereHasRcmBugPatch: - // Get information about whether this unit has the RCM bug patched. - return MakeResult(u64{0}); - case ConfigItem::ExosphereBlankProdInfo: - // Get whether this unit should simulate a "blanked" PRODINFO. - return MakeResult(u64{0}); - case ConfigItem::ExosphereAllowCalWrites: - // Get whether this unit should allow writing to the calibration partition. - return MakeResult(u64{0}); - case ConfigItem::ExosphereEmummcType: - // Get what kind of emummc this unit has active. - return MakeResult(u64{0}); - case ConfigItem::ExospherePayloadAddress: - // Gets the physical address of the reboot payload buffer, if one exists. - return ResultSecureMonitorNotInitialized; - case ConfigItem::ExosphereLogConfiguration: - // Get the log configuration. - return MakeResult(u64{0}); - case ConfigItem::ExosphereForceEnableUsb30: - // Get whether usb 3.0 should be force-enabled. - return MakeResult(u64{0}); - default: - return ResultSecureMonitorInvalidArgument; - } -} - -void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { - auto module = std::make_shared(); - std::make_shared(system, module)->InstallAsService(service_manager); - std::make_shared(system, module)->InstallAsService(service_manager); - std::make_shared(system, module)->InstallAsService(service_manager); - std::make_shared(system, module)->InstallAsService(service_manager); - std::make_shared(system, module)->InstallAsService(service_manager); - std::make_shared(system, module)->InstallAsService(service_manager); - std::make_shared(system, module)->InstallAsService(service_manager); -} - -} // namespace Service::SPL diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h deleted file mode 100644 index 61630df80..000000000 --- a/src/core/hle/service/spl/module.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "core/hle/service/service.h" -#include "core/hle/service/spl/spl_results.h" -#include "core/hle/service/spl/spl_types.h" - -namespace Core { -class System; -} - -namespace Service::SPL { - -class Module final { -public: - class Interface : public ServiceFramework { - public: - explicit Interface(Core::System& system_, std::shared_ptr module_, - const char* name); - ~Interface() override; - - // General - void GetConfig(Kernel::HLERequestContext& ctx); - void ModularExponentiate(Kernel::HLERequestContext& ctx); - void SetConfig(Kernel::HLERequestContext& ctx); - void GenerateRandomBytes(Kernel::HLERequestContext& ctx); - void IsDevelopment(Kernel::HLERequestContext& ctx); - void SetBootReason(Kernel::HLERequestContext& ctx); - void GetBootReason(Kernel::HLERequestContext& ctx); - - protected: - std::shared_ptr module; - - private: - ResultVal GetConfigImpl(ConfigItem config_item) const; - - std::mt19937 rng; - }; -}; - -/// Registers all SPL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); - -} // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h index 9b35012ed..5599c0c01 100644 --- a/src/core/hle/service/spl/spl.h +++ b/src/core/hle/service/spl/spl.h @@ -4,7 +4,7 @@ #pragma once -#include "core/hle/service/spl/module.h" +#include "core/hle/service/spl/spl_module.h" namespace Core { class System; diff --git a/src/core/hle/service/spl/spl_module.cpp b/src/core/hle/service/spl/spl_module.cpp new file mode 100644 index 000000000..918633af5 --- /dev/null +++ b/src/core/hle/service/spl/spl_module.cpp @@ -0,0 +1,175 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/hle/api_version.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/spl/csrng.h" +#include "core/hle/service/spl/spl.h" +#include "core/hle/service/spl/spl_module.h" + +namespace Service::SPL { + +Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)}, + rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {} + +Module::Interface::~Interface() = default; + +void Module::Interface::GetConfig(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto config_item = rp.PopEnum(); + + // This should call svcCallSecureMonitor with the appropriate args. + // Since we do not have it implemented yet, we will use this for now. + const auto smc_result = GetConfigImpl(config_item); + const auto result_code = smc_result.Code(); + + if (smc_result.Failed()) { + LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item, + result_code.raw); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result_code); + } + + LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item, + result_code.raw, *smc_result); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result_code); + rb.Push(*smc_result); +} + +void Module::Interface::ModularExponentiate(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("ModularExponentiate is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::SetConfig(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("SetConfig is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::GenerateRandomBytes(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SPL, "called"); + + const std::size_t size = ctx.GetWriteBufferSize(); + + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + std::vector data(size); + std::generate(data.begin(), data.end(), [&] { return static_cast(distribution(rng)); }); + + ctx.WriteBuffer(data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void Module::Interface::IsDevelopment(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("IsDevelopment is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::SetBootReason(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("SetBootReason is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::GetBootReason(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("GetBootReason is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +ResultVal Module::Interface::GetConfigImpl(ConfigItem config_item) const { + switch (config_item) { + case ConfigItem::DisableProgramVerification: + case ConfigItem::DramId: + case ConfigItem::SecurityEngineInterruptNumber: + case ConfigItem::FuseVersion: + case ConfigItem::HardwareType: + case ConfigItem::HardwareState: + case ConfigItem::IsRecoveryBoot: + case ConfigItem::DeviceId: + case ConfigItem::BootReason: + case ConfigItem::MemoryMode: + case ConfigItem::IsDevelopmentFunctionEnabled: + case ConfigItem::KernelConfiguration: + case ConfigItem::IsChargerHiZModeEnabled: + case ConfigItem::QuestState: + case ConfigItem::RegulatorType: + case ConfigItem::DeviceUniqueKeyGeneration: + case ConfigItem::Package2Hash: + return ResultSecureMonitorNotImplemented; + case ConfigItem::ExosphereApiVersion: + // Get information about the current exosphere version. + return MakeResult((u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) | + (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) | + (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) | + (static_cast(HLE::ApiVersion::GetTargetFirmware()))); + case ConfigItem::ExosphereNeedsReboot: + // We are executing, so we aren't in the process of rebooting. + return MakeResult(u64{0}); + case ConfigItem::ExosphereNeedsShutdown: + // We are executing, so we aren't in the process of shutting down. + return MakeResult(u64{0}); + case ConfigItem::ExosphereGitCommitHash: + // Get information about the current exosphere git commit hash. + return MakeResult(u64{0}); + case ConfigItem::ExosphereHasRcmBugPatch: + // Get information about whether this unit has the RCM bug patched. + return MakeResult(u64{0}); + case ConfigItem::ExosphereBlankProdInfo: + // Get whether this unit should simulate a "blanked" PRODINFO. + return MakeResult(u64{0}); + case ConfigItem::ExosphereAllowCalWrites: + // Get whether this unit should allow writing to the calibration partition. + return MakeResult(u64{0}); + case ConfigItem::ExosphereEmummcType: + // Get what kind of emummc this unit has active. + return MakeResult(u64{0}); + case ConfigItem::ExospherePayloadAddress: + // Gets the physical address of the reboot payload buffer, if one exists. + return ResultSecureMonitorNotInitialized; + case ConfigItem::ExosphereLogConfiguration: + // Get the log configuration. + return MakeResult(u64{0}); + case ConfigItem::ExosphereForceEnableUsb30: + // Get whether usb 3.0 should be force-enabled. + return MakeResult(u64{0}); + default: + return ResultSecureMonitorInvalidArgument; + } +} + +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + auto module = std::make_shared(); + std::make_shared(system, module)->InstallAsService(service_manager); + std::make_shared(system, module)->InstallAsService(service_manager); + std::make_shared(system, module)->InstallAsService(service_manager); + std::make_shared(system, module)->InstallAsService(service_manager); + std::make_shared(system, module)->InstallAsService(service_manager); + std::make_shared(system, module)->InstallAsService(service_manager); + std::make_shared(system, module)->InstallAsService(service_manager); +} + +} // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl_module.h b/src/core/hle/service/spl/spl_module.h new file mode 100644 index 000000000..61630df80 --- /dev/null +++ b/src/core/hle/service/spl/spl_module.h @@ -0,0 +1,48 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/hle/service/service.h" +#include "core/hle/service/spl/spl_results.h" +#include "core/hle/service/spl/spl_types.h" + +namespace Core { +class System; +} + +namespace Service::SPL { + +class Module final { +public: + class Interface : public ServiceFramework { + public: + explicit Interface(Core::System& system_, std::shared_ptr module_, + const char* name); + ~Interface() override; + + // General + void GetConfig(Kernel::HLERequestContext& ctx); + void ModularExponentiate(Kernel::HLERequestContext& ctx); + void SetConfig(Kernel::HLERequestContext& ctx); + void GenerateRandomBytes(Kernel::HLERequestContext& ctx); + void IsDevelopment(Kernel::HLERequestContext& ctx); + void SetBootReason(Kernel::HLERequestContext& ctx); + void GetBootReason(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr module; + + private: + ResultVal GetConfigImpl(ConfigItem config_item) const; + + std::mt19937 rng; + }; +}; + +/// Registers all SPL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); + +} // namespace Service::SPL diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp deleted file mode 100644 index 53a204796..000000000 --- a/src/core/hle/service/time/interface.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/hle/service/time/interface.h" - -namespace Service::Time { - -Time::Time(std::shared_ptr module_, Core::System& system_, const char* name_) - : Interface{std::move(module_), system_, name_} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, - {1, &Time::GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"}, - {2, &Time::GetStandardSteadyClock, "GetStandardSteadyClock"}, - {3, &Time::GetTimeZoneService, "GetTimeZoneService"}, - {4, &Time::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"}, - {5, nullptr, "GetEphemeralNetworkSystemClock"}, - {20, &Time::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, - {30, nullptr, "GetStandardNetworkClockOperationEventReadableHandle"}, - {31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"}, - {50, nullptr, "SetStandardSteadyClockInternalOffset"}, - {51, nullptr, "GetStandardSteadyClockRtcValue"}, - {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, - {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, - {102, nullptr, "GetStandardUserSystemClockInitialYear"}, - {200, &Time::IsStandardNetworkSystemClockAccuracySufficient, "IsStandardNetworkSystemClockAccuracySufficient"}, - {201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"}, - {300, &Time::CalculateMonotonicSystemClockBaseTimePoint, "CalculateMonotonicSystemClockBaseTimePoint"}, - {400, &Time::GetClockSnapshot, "GetClockSnapshot"}, - {401, &Time::GetClockSnapshotFromSystemClockContext, "GetClockSnapshotFromSystemClockContext"}, - {500, &Time::CalculateStandardUserSystemClockDifferenceByUser, "CalculateStandardUserSystemClockDifferenceByUser"}, - {501, &Time::CalculateSpanBetween, "CalculateSpanBetween"}, - }; - // clang-format on - - RegisterHandlers(functions); -} - -Time::~Time() = default; - -} // namespace Service::Time diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h deleted file mode 100644 index c41766f1a..000000000 --- a/src/core/hle/service/time/interface.h +++ /dev/null @@ -1,21 +0,0 @@ -// 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/time/time.h" - -namespace Core { -class System; -} - -namespace Service::Time { - -class Time final : public Module::Interface { -public: - explicit Time(std::shared_ptr time, Core::System& system_, const char* name_); - ~Time() override; -}; - -} // namespace Service::Time diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index d6f710eba..8fdd5076f 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -11,8 +11,8 @@ #include "core/hle/kernel/k_client_port.h" #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/service/time/interface.h" #include "core/hle/service/time/time.h" +#include "core/hle/service/time/time_interface.h" #include "core/hle/service/time/time_sharedmemory.h" #include "core/hle/service/time/time_zone_service.h" diff --git a/src/core/hle/service/time/time_interface.cpp b/src/core/hle/service/time/time_interface.cpp new file mode 100644 index 000000000..bb7b6b5c1 --- /dev/null +++ b/src/core/hle/service/time/time_interface.cpp @@ -0,0 +1,42 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/time/time_interface.h" + +namespace Service::Time { + +Time::Time(std::shared_ptr module_, Core::System& system_, const char* name_) + : Interface{std::move(module_), system_, name_} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, + {1, &Time::GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"}, + {2, &Time::GetStandardSteadyClock, "GetStandardSteadyClock"}, + {3, &Time::GetTimeZoneService, "GetTimeZoneService"}, + {4, &Time::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"}, + {5, nullptr, "GetEphemeralNetworkSystemClock"}, + {20, &Time::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, + {30, nullptr, "GetStandardNetworkClockOperationEventReadableHandle"}, + {31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"}, + {50, nullptr, "SetStandardSteadyClockInternalOffset"}, + {51, nullptr, "GetStandardSteadyClockRtcValue"}, + {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, + {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, + {102, nullptr, "GetStandardUserSystemClockInitialYear"}, + {200, &Time::IsStandardNetworkSystemClockAccuracySufficient, "IsStandardNetworkSystemClockAccuracySufficient"}, + {201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"}, + {300, &Time::CalculateMonotonicSystemClockBaseTimePoint, "CalculateMonotonicSystemClockBaseTimePoint"}, + {400, &Time::GetClockSnapshot, "GetClockSnapshot"}, + {401, &Time::GetClockSnapshotFromSystemClockContext, "GetClockSnapshotFromSystemClockContext"}, + {500, &Time::CalculateStandardUserSystemClockDifferenceByUser, "CalculateStandardUserSystemClockDifferenceByUser"}, + {501, &Time::CalculateSpanBetween, "CalculateSpanBetween"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +Time::~Time() = default; + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_interface.h b/src/core/hle/service/time/time_interface.h new file mode 100644 index 000000000..c41766f1a --- /dev/null +++ b/src/core/hle/service/time/time_interface.h @@ -0,0 +1,21 @@ +// 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/time/time.h" + +namespace Core { +class System; +} + +namespace Service::Time { + +class Time final : public Module::Interface { +public: + explicit Time(std::shared_ptr time, Core::System& system_, const char* name_); + ~Time() override; +}; + +} // namespace Service::Time -- cgit v1.2.3