diff options
98 files changed, 2460 insertions, 1113 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9727c5712..ae11db988 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,7 +1,7 @@  <!---  Please read the FAQ: -https://citra-emu.org/wiki/FAQ +https://citra-emu.org/wiki/faq/  THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:  https://community.citra-emu.org/ @@ -22,4 +22,4 @@ When submitting an issue, please check the following:    - For graphical issues, comparison screenshots with real hardware.    - For emulation inaccuracies, a test-case (if able). ---->
\ No newline at end of file +---> @@ -7,7 +7,7 @@ Citra Emulator  Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward. -Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://citra-emu.org/wiki/FAQ/) before getting started with the project. +Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://citra-emu.org/wiki/faq/) before getting started with the project.  Check out our [website](https://citra-emu.org/)! diff --git a/externals/boost b/externals/boost -Subproject 351972396392c97a659b9a02f34ce9269293d21 +Subproject 3abc84abaf63a068cb59a9f9b5675c1947bc6fd diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index c0dac9e8f..dd357ff72 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -144,7 +144,7 @@ int main(int argc, char** argv) {          LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before "                                 "being used with Citra. \n\n For more information on dumping and "                                 "decrypting games, please refer to: " -                               "https://citra-emu.org/wiki/Dumping-Game-Cartridges"); +                               "https://citra-emu.org/wiki/dumping-game-cartridges/");          return -1;      case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:          LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 06b62f44c..a8a4aed8b 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -37,7 +37,10 @@ void EmuThread::run() {              if (!was_active)                  emit DebugModeLeft(); -            Core::System::GetInstance().RunLoop(); +            Core::System::ResultStatus result = Core::System::GetInstance().RunLoop(); +            if (result != Core::System::ResultStatus::Success) { +                emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails()); +            }              was_active = running || exec_step;              if (!was_active && !stop_run) diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 9d39f1af8..4b3a3b3cc 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -10,6 +10,7 @@  #include <QGLWidget>  #include <QThread>  #include "common/thread.h" +#include "core/core.h"  #include "core/frontend/emu_window.h"  #include "core/frontend/motion_emu.h" @@ -97,6 +98,8 @@ signals:       * Qt::BlockingQueuedConnection (additionally block source thread until slot returns)       */      void DebugModeLeft(); + +    void ErrorThrown(Core::System::ResultStatus, std::string);  };  class GRenderWindow : public QWidget, public EmuWindow { diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp index c68fe753b..7d06ec28a 100644 --- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp @@ -17,6 +17,7 @@  #include "citra_qt/util/spinbox.h"  #include "citra_qt/util/util.h"  #include "common/vector_math.h" +#include "core/memory.h"  #include "video_core/debug_utils/debug_utils.h"  #include "video_core/pica_state.h"  #include "video_core/regs.h" diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp index b6ecf3819..8c244b6b2 100644 --- a/src/citra_qt/debugger/wait_tree.cpp +++ b/src/citra_qt/debugger/wait_tree.cpp @@ -10,6 +10,7 @@  #include "core/hle/kernel/semaphore.h"  #include "core/hle/kernel/thread.h"  #include "core/hle/kernel/timer.h" +#include "core/hle/kernel/wait_object.h"  WaitTreeItem::~WaitTreeItem() {} diff --git a/src/citra_qt/debugger/wait_tree.h b/src/citra_qt/debugger/wait_tree.h index ee9708fc1..2b38712b9 100644 --- a/src/citra_qt/debugger/wait_tree.h +++ b/src/citra_qt/debugger/wait_tree.h @@ -4,12 +4,10 @@  #pragma once -#include <boost/container/flat_set.hpp> -  #include <QAbstractItemModel>  #include <QDockWidget>  #include <QTreeView> - +#include <boost/container/flat_set.hpp>  #include "core/core.h"  #include "core/hle/kernel/kernel.h" @@ -20,7 +18,6 @@ class WaitObject;  class Event;  class Mutex;  class Semaphore; -class Session;  class Thread;  class Timer;  } diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index d1118ff7f..12212a3a4 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -10,10 +10,8 @@  #include <QStandardItem>  #include <QString>  #include "citra_qt/util/util.h" -#include "common/color.h"  #include "common/string_util.h"  #include "core/loader/smdh.h" -#include "video_core/utils.h"  /**   * Gets the game icon from SMDH data. diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index eb2c7d613..4f5b2ddab 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -93,6 +93,14 @@ void GMainWindow::InitializeWidgets() {      ui.horizontalLayout->addWidget(game_list);      // Create status bar +    message_label = new QLabel(); +    // Configured separately for left alignment +    message_label->setVisible(false); +    message_label->setFrameStyle(QFrame::NoFrame); +    message_label->setContentsMargins(4, 0, 4, 0); +    message_label->setAlignment(Qt::AlignLeft); +    statusBar()->addPermanentWidget(message_label, 1); +      emu_speed_label = new QLabel();      emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "                                     "indicate emulation is running faster or slower than a 3DS.")); @@ -108,7 +116,7 @@ void GMainWindow::InitializeWidgets() {          label->setVisible(false);          label->setFrameStyle(QFrame::NoFrame);          label->setContentsMargins(4, 0, 4, 0); -        statusBar()->addPermanentWidget(label); +        statusBar()->addPermanentWidget(label, 0);      }      statusBar()->setVisible(true);      setStyleSheet("QStatusBar::item{border: none;}"); @@ -300,9 +308,8 @@ bool GMainWindow::LoadROM(const QString& filename) {      render_window->MakeCurrent();      if (!gladLoadGL()) { -        QMessageBox::critical(this, tr("Error while starting Citra!"), -                              tr("Failed to initialize the video core!\n\n" -                                 "Please ensure that your GPU supports OpenGL 3.3 and that you " +        QMessageBox::critical(this, tr("Error while initializing OpenGL 3.3 Core!"), +                              tr("Your GPU may not support OpenGL 3.3, or you do not"                                   "have the latest graphics driver."));          return false;      } @@ -327,18 +334,17 @@ bool GMainWindow::LoadROM(const QString& filename) {              break;          case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: { -            // Build the MessageBox ourselves to have clickable link -            QMessageBox popup_error; -            popup_error.setTextFormat(Qt::RichText); -            popup_error.setWindowTitle(tr("Error while loading ROM!")); -            popup_error.setText( +            QMessageBox::critical( +                this, tr("Error while loading ROM!"),                  tr("The game that you are trying to load must be decrypted before being used with " -                   "Citra.<br/><br/>" -                   "For more information on dumping and decrypting games, please see: <a " -                   "href='https://citra-emu.org/wiki/Dumping-Game-Cartridges'>https://" -                   "citra-emu.org/wiki/Dumping-Game-Cartridges</a>")); -            popup_error.setIcon(QMessageBox::Critical); -            popup_error.exec(); +                   "Citra. A real 3DS is required.<br/><br/>" +                   "For more information on dumping and decrypting games, please see the following " +                   "wiki pages: <ul>" +                   "<li><a href='https://citra-emu.org/wiki/dumping-game-cartridges/'>Dumping Game " +                   "Cartridges</a></li>" +                   "<li><a href='https://citra-emu.org/wiki/dumping-installed-titles/'>Dumping " +                   "Installed Titles</a></li>" +                   "</ul>"));              break;          }          case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: @@ -346,8 +352,23 @@ bool GMainWindow::LoadROM(const QString& filename) {                                    tr("The ROM format is not supported."));              break; +        case Core::System::ResultStatus::ErrorVideoCore: +            QMessageBox::critical( +                this, tr("An error occured in the video core."), +                tr("Citra has encountered an error while running the video core, please see the " +                   "log for more details." +                   "For more information on accessing the log, please see the following page: " +                   "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How " +                   "to " +                   "Upload the Log File</a>." +                   "Ensure that you have the latest graphics drivers for your GPU.")); + +            break; +          default: -            QMessageBox::critical(this, tr("Error while loading ROM!"), tr("Unknown error!")); +            QMessageBox::critical( +                this, tr("Error while loading ROM!"), +                tr("An unknown error occured. Please see the log for more details."));              break;          }          return false; @@ -424,6 +445,7 @@ void GMainWindow::ShutdownGame() {      // Disable status bar updates      status_bar_update_timer.stop(); +    message_label->setVisible(false);      emu_speed_label->setVisible(false);      game_fps_label->setVisible(false);      emu_frametime_label->setVisible(false); @@ -530,6 +552,10 @@ void GMainWindow::OnMenuRecentFile() {  void GMainWindow::OnStartGame() {      emu_thread->SetRunning(true); +    qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); +    qRegisterMetaType<std::string>("std::string"); +    connect(emu_thread.get(), SIGNAL(ErrorThrown(Core::System::ResultStatus, std::string)), this, +            SLOT(OnCoreError(Core::System::ResultStatus, std::string)));      ui.action_Start->setEnabled(false);      ui.action_Start->setText(tr("Continue")); @@ -622,11 +648,74 @@ void GMainWindow::UpdateStatusBar() {      emu_frametime_label->setVisible(true);  } +void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) { +    QMessageBox::StandardButton answer; +    QString status_message; +    const QString common_message = +        tr("The game you are trying to load requires additional files from your 3DS to be dumped " +           "before playing.<br/><br/>For more information on dumping these files, please see the " +           "following wiki page: <a " +           "href='https://citra-emu.org/wiki/" +           "dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>Dumping System " +           "Archives and the Shared Fonts from a 3DS Console</a>.<br/><br/>Would you like to quit " +           "back to the game list? Continuing emulation may result in crashes, corrupted save " +           "data, or other bugs."); +    switch (result) { +    case Core::System::ResultStatus::ErrorSystemFiles: { +        QString message = "Citra was unable to locate a 3DS system archive"; +        if (!details.empty()) { +            message.append(tr(": %1. ").arg(details.c_str())); +        } else { +            message.append(". "); +        } +        message.append(common_message); + +        answer = QMessageBox::question(this, tr("System Archive Not Found"), message, +                                       QMessageBox::Yes | QMessageBox::No, QMessageBox::No); +        status_message = "System Archive Missing"; +        break; +    } + +    case Core::System::ResultStatus::ErrorSharedFont: { +        QString message = tr("Citra was unable to locate the 3DS shared fonts. "); +        message.append(common_message); +        answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message, +                                       QMessageBox::Yes | QMessageBox::No, QMessageBox::No); +        status_message = "Shared Font Missing"; +        break; +    } + +    default: +        answer = QMessageBox::question( +            this, tr("Fatal Error"), +            tr("Citra has encountered a fatal error, please see the log for more details. " +               "For more information on accessing the log, please see the following page: " +               "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to " +               "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? " +               "Continuing emulation may result in crashes, corrupted save data, or other bugs."), +            QMessageBox::Yes | QMessageBox::No, QMessageBox::No); +        status_message = "Fatal Error encountered"; +        break; +    } + +    if (answer == QMessageBox::Yes) { +        if (emu_thread) { +            ShutdownGame(); +        } +    } else { +        // Only show the message if the game is still running. +        if (emu_thread) { +            message_label->setText(status_message); +            message_label->setVisible(true); +        } +    } +} +  bool GMainWindow::ConfirmClose() {      if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)          return true; -    auto answer = +    QMessageBox::StandardButton answer =          QMessageBox::question(this, tr("Citra"), tr("Are you sure you want to close Citra?"),                                QMessageBox::Yes | QMessageBox::No, QMessageBox::No);      return answer != QMessageBox::No; diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index cb2e87cbd..952a50974 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -8,6 +8,7 @@  #include <memory>  #include <QMainWindow>  #include <QTimer> +#include "core/core.h"  #include "ui_main.h"  class Config; @@ -125,6 +126,7 @@ private slots:      void OnDisplayTitleBars(bool);      void ToggleWindowMode();      void OnCreateGraphicsSurfaceViewer(); +    void OnCoreError(Core::System::ResultStatus, std::string);  private:      void UpdateStatusBar(); @@ -135,6 +137,7 @@ private:      GameList* game_list;      // Status bar elements +    QLabel* message_label = nullptr;      QLabel* emu_speed_label = nullptr;      QLabel* game_fps_label = nullptr;      QLabel* emu_frametime_label = nullptr; diff --git a/src/common/break_points.cpp b/src/common/break_points.cpp index 03a19acba..fa367a4ca 100644 --- a/src/common/break_points.cpp +++ b/src/common/break_points.cpp @@ -5,7 +5,6 @@  #include <algorithm>  #include <sstream>  #include "common/break_points.h" -#include "common/logging/log.h"  bool BreakPoints::IsAddressBreakPoint(u32 iAddress) const {      auto cond = [&iAddress](const TBreakPoint& bp) { return bp.iAddress == iAddress; }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3cdb2b817..b16a89990 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -45,6 +45,8 @@ set(SRCS              hle/kernel/client_port.cpp              hle/kernel/client_session.cpp              hle/kernel/event.cpp +            hle/kernel/handle_table.cpp +            hle/kernel/hle_ipc.cpp              hle/kernel/kernel.cpp              hle/kernel/memory.cpp              hle/kernel/mutex.cpp @@ -57,6 +59,7 @@ set(SRCS              hle/kernel/thread.cpp              hle/kernel/timer.cpp              hle/kernel/vm_manager.cpp +            hle/kernel/wait_object.cpp              hle/service/ac/ac.cpp              hle/service/ac/ac_i.cpp              hle/service/ac/ac_u.cpp @@ -153,8 +156,9 @@ set(SRCS              hle/service/qtm/qtm_sp.cpp              hle/service/qtm/qtm_u.cpp              hle/service/service.cpp +            hle/service/sm/sm.cpp +            hle/service/sm/srv.cpp              hle/service/soc_u.cpp -            hle/service/srv.cpp              hle/service/ssl_c.cpp              hle/service/y2r_u.cpp              hle/shared_page.cpp @@ -236,6 +240,8 @@ set(HEADERS              hle/kernel/client_session.h              hle/kernel/errors.h              hle/kernel/event.h +            hle/kernel/handle_table.h +            hle/kernel/hle_ipc.h              hle/kernel/kernel.h              hle/kernel/memory.h              hle/kernel/mutex.h @@ -249,6 +255,7 @@ set(HEADERS              hle/kernel/thread.h              hle/kernel/timer.h              hle/kernel/vm_manager.h +            hle/kernel/wait_object.h              hle/result.h              hle/service/ac/ac.h              hle/service/ac/ac_i.h @@ -346,8 +353,9 @@ set(HEADERS              hle/service/qtm/qtm_sp.h              hle/service/qtm/qtm_u.h              hle/service/service.h +            hle/service/sm/sm.h +            hle/service/sm/srv.h              hle/service/soc_u.h -            hle/service/srv.h              hle/service/ssl_c.h              hle/service/y2r_u.h              hle/shared_page.h @@ -377,4 +385,4 @@ set(HEADERS  create_directory_groups(${SRCS} ${HEADERS})  add_library(core STATIC ${SRCS} ${HEADERS})  target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic) +target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt) diff --git a/src/core/core.cpp b/src/core/core.cpp index 450e7566d..5429bcb26 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -3,7 +3,7 @@  // Refer to the license.txt file included.  #include <memory> - +#include <utility>  #include "audio_core/audio_core.h"  #include "common/logging/log.h"  #include "core/arm/arm_interface.h" @@ -26,6 +26,7 @@ namespace Core {  /*static*/ System System::s_instance;  System::ResultStatus System::RunLoop(int tight_loop) { +    status = ResultStatus::Success;      if (!cpu_core) {          return ResultStatus::ErrorNotInitialized;      } @@ -59,7 +60,7 @@ System::ResultStatus System::RunLoop(int tight_loop) {      HW::Update();      Reschedule(); -    return ResultStatus::Success; +    return status;  }  System::ResultStatus System::SingleStep() { @@ -73,14 +74,25 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file          LOG_CRITICAL(Core, "Failed to obtain loader for %s!", filepath.c_str());          return ResultStatus::ErrorGetLoader;      } +    std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode = +        app_loader->LoadKernelSystemMode(); + +    if (system_mode.second != Loader::ResultStatus::Success) { +        LOG_CRITICAL(Core, "Failed to determine system mode (Error %i)!", +                     static_cast<int>(system_mode.second)); +        System::Shutdown(); -    boost::optional<u32> system_mode{app_loader->LoadKernelSystemMode()}; -    if (!system_mode) { -        LOG_CRITICAL(Core, "Failed to determine system mode!"); -        return ResultStatus::ErrorSystemMode; +        switch (system_mode.second) { +        case Loader::ResultStatus::ErrorEncrypted: +            return ResultStatus::ErrorLoader_ErrorEncrypted; +        case Loader::ResultStatus::ErrorInvalidFormat: +            return ResultStatus::ErrorLoader_ErrorInvalidFormat; +        default: +            return ResultStatus::ErrorSystemMode; +        }      } -    ResultStatus init_result{Init(emu_window, system_mode.get())}; +    ResultStatus init_result{Init(emu_window, system_mode.first.get())};      if (init_result != ResultStatus::Success) {          LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!", init_result);          System::Shutdown(); @@ -101,7 +113,8 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file              return ResultStatus::ErrorLoader;          }      } -    return ResultStatus::Success; +    status = ResultStatus::Success; +    return status;  }  void System::PrepareReschedule() { diff --git a/src/core/core.h b/src/core/core.h index 6af772831..4e3b6b409 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -40,7 +40,10 @@ public:          ErrorLoader_ErrorEncrypted, ///< Error loading the specified application due to encryption          ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an                                          /// invalid format +        ErrorSystemFiles,               ///< Error in finding system files +        ErrorSharedFont,                ///< Error in finding shared font          ErrorVideoCore,                 ///< Error in the video core +        ErrorUnknown                    ///< Any other error      };      /** @@ -105,6 +108,17 @@ public:      PerfStats perf_stats;      FrameLimiter frame_limiter; +    void SetStatus(ResultStatus new_status, const char* details = nullptr) { +        status = new_status; +        if (details) { +            status_details = details; +        } +    } + +    const std::string& GetStatusDetails() const { +        return status_details; +    } +  private:      /**       * Initialize the emulated system. @@ -130,6 +144,9 @@ private:      std::unique_ptr<Core::TelemetrySession> telemetry_session;      static System s_instance; + +    ResultStatus status = ResultStatus::Success; +    std::string status_details = "";  };  inline ARM_Interface& CPU() { diff --git a/src/core/file_sys/archive_ncch.cpp b/src/core/file_sys/archive_ncch.cpp index 89455e39c..6d9007731 100644 --- a/src/core/file_sys/archive_ncch.cpp +++ b/src/core/file_sys/archive_ncch.cpp @@ -9,7 +9,9 @@  #include "common/file_util.h"  #include "common/logging/log.h"  #include "common/string_util.h" +#include "core/core.h"  #include "core/file_sys/archive_ncch.h" +#include "core/file_sys/errors.h"  #include "core/file_sys/ivfc_archive.h"  #include "core/hle/service/fs/archive.h" @@ -33,11 +35,44 @@ ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory)  ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path) {      auto vec = path.AsBinary();      const u32* data = reinterpret_cast<u32*>(vec.data()); -    std::string file_path = GetNCCHPath(mount_point, data[1], data[0]); +    u32 high = data[1]; +    u32 low = data[0]; +    std::string file_path = GetNCCHPath(mount_point, high, low);      auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb");      if (!file->IsOpen()) { -        return ResultCode(-1); // TODO(Subv): Find the right error code +        // High Title ID of the archive: The category (https://3dbrew.org/wiki/Title_list). +        constexpr u32 shared_data_archive = 0x0004009B; +        constexpr u32 system_data_archive = 0x000400DB; + +        // Low Title IDs. +        constexpr u32 mii_data = 0x00010202; +        constexpr u32 region_manifest = 0x00010402; +        constexpr u32 ng_word_list = 0x00010302; + +        LOG_DEBUG(Service_FS, "Full Path: %s. Category: 0x%X. Path: 0x%X.", path.DebugStr().c_str(), +                  high, low); + +        if (high == shared_data_archive) { +            if (low == mii_data) { +                LOG_ERROR(Service_FS, "Failed to get a handle for shared data archive: Mii data. "); +                Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSystemFiles, +                                                      "Mii data"); +            } else if (low == region_manifest) { +                LOG_ERROR(Service_FS, +                          "Failed to get a handle for shared data archive: region manifest."); +                Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSystemFiles, +                                                      "Region manifest"); +            } +        } else if (high == system_data_archive) { +            if (low == ng_word_list) { +                LOG_ERROR(Service_FS, +                          "Failed to get a handle for system data archive: NG bad word list."); +                Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSystemFiles, +                                                      "NG bad word list"); +            } +        } +        return ERROR_NOT_FOUND;      }      auto size = file->GetSize(); diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 303ca090d..f7f96125a 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -44,6 +44,9 @@ inline u32* GetStaticBuffers(const int offset = 0) {  namespace IPC { +/// Size of the command buffer area, in 32-bit words. +constexpr size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32); +  // These errors are commonly returned by invalid IPC translations, so alias them here for  // convenience.  // TODO(yuriks): These will probably go away once translation is implemented inside the kernel. diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 06c4c5a85..f0d89cffe 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -3,18 +3,29 @@  // Refer to the license.txt file included.  #pragma once + +#include <array> +#include <tuple> +#include <type_traits> +#include <utility>  #include "core/hle/ipc.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/hle_ipc.h"  #include "core/hle/kernel/kernel.h"  namespace IPC {  class RequestHelperBase {  protected: +    Kernel::HLERequestContext* context = nullptr;      u32* cmdbuf;      ptrdiff_t index = 1;      Header header;  public: +    RequestHelperBase(Kernel::HLERequestContext& context, Header desired_header) +        : context(&context), cmdbuf(context.CommandBuffer()), header(desired_header) {} +      RequestHelperBase(u32* command_buffer, Header command_header)          : cmdbuf(command_buffer), header(command_header) {} @@ -49,12 +60,27 @@ public:  class RequestBuilder : public RequestHelperBase {  public: +    RequestBuilder(Kernel::HLERequestContext& context, Header command_header) +        : RequestHelperBase(context, command_header) { +        // From this point we will start overwriting the existing command buffer, so it's safe to +        // release all previous incoming Object pointers since they won't be usable anymore. +        context.ClearIncomingObjects(); +        cmdbuf[0] = header.raw; +    } + +    RequestBuilder(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size, +                   unsigned translate_params_size) +        : RequestBuilder( +              context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {} +      RequestBuilder(u32* command_buffer, Header command_header)          : RequestHelperBase(command_buffer, command_header) {          cmdbuf[0] = header.raw;      } +      explicit RequestBuilder(u32* command_buffer, u32 command_header)          : RequestBuilder(command_buffer, Header{command_header}) {} +      RequestBuilder(u32* command_buffer, u16 command_id, unsigned normal_params_size,                     unsigned translate_params_size)          : RequestBuilder(command_buffer, @@ -86,6 +112,9 @@ public:      template <typename... H>      void PushMoveHandles(H... handles); +    template <typename... O> +    void PushObjects(Kernel::SharedPtr<O>... pointers); +      void PushCurrentPIDHandle();      void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id); @@ -151,6 +180,11 @@ inline void RequestBuilder::PushMoveHandles(H... handles) {      Push(static_cast<Kernel::Handle>(handles)...);  } +template <typename... O> +inline void RequestBuilder::PushObjects(Kernel::SharedPtr<O>... pointers) { +    PushMoveHandles(context->AddOutgoingHandle(std::move(pointers))...); +} +  inline void RequestBuilder::PushCurrentPIDHandle() {      Push(CallingPidDesc());      Push(u32(0)); @@ -169,10 +203,21 @@ inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, u32 size,  class RequestParser : public RequestHelperBase {  public: +    RequestParser(Kernel::HLERequestContext& context, Header desired_header) +        : RequestHelperBase(context, desired_header) {} + +    RequestParser(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size, +                  unsigned translate_params_size) +        : RequestParser(context, +                        Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) { +    } +      RequestParser(u32* command_buffer, Header command_header)          : RequestHelperBase(command_buffer, command_header) {} +      explicit RequestParser(u32* command_buffer, u32 command_header)          : RequestParser(command_buffer, Header{command_header}) {} +      RequestParser(u32* command_buffer, u16 command_id, unsigned normal_params_size,                    unsigned translate_params_size)          : RequestParser(command_buffer, @@ -184,7 +229,10 @@ public:              ValidateHeader();          Header builderHeader{              MakeHeader(header.command_id, normal_params_size, translate_params_size)}; -        return {cmdbuf, builderHeader}; +        if (context != nullptr) +            return {*context, builderHeader}; +        else +            return {cmdbuf, builderHeader};      }      template <typename T> @@ -196,10 +244,52 @@ public:      template <typename First, typename... Other>      void Pop(First& first_value, Other&... other_values); +    /// Equivalent to calling `PopHandles<1>()[0]`.      Kernel::Handle PopHandle(); +    /** +     * Pops a descriptor containing `N` handles. The handles are returned as an array. The +     * descriptor must contain exactly `N` handles, it is not permitted to, for example, call +     * PopHandles<1>() twice to read a multi-handle descriptor with 2 handles, or to make a single +     * PopHandles<2>() call to read 2 single-handle descriptors. +     */ +    template <unsigned int N> +    std::array<Kernel::Handle, N> PopHandles(); + +    /// Convenience wrapper around PopHandles() which assigns the handles to the passed references.      template <typename... H> -    void PopHandles(H&... handles); +    void PopHandles(H&... handles) { +        std::tie(handles...) = PopHandles<sizeof...(H)>(); +    } + +    /// Equivalent to calling `PopGenericObjects<1>()[0]`. +    Kernel::SharedPtr<Kernel::Object> PopGenericObject(); + +    /// Equivalent to calling `std::get<0>(PopObjects<T>())`. +    template <typename T> +    Kernel::SharedPtr<T> PopObject(); + +    /** +     * Pop a descriptor containing `N` handles and resolves them to Kernel::Object pointers. If a +     * handle is invalid, null is returned for that object instead. The same caveats from +     * PopHandles() apply regarding `N` matching the number of handles in the descriptor. +     */ +    template <unsigned int N> +    std::array<Kernel::SharedPtr<Kernel::Object>, N> PopGenericObjects(); + +    /** +     * Resolves handles to Kernel::Objects as in PopGenericsObjects(), but then also casts them to +     * the passed `T` types, while verifying that the cast is valid. If the type of an object does +     * not match, null is returned instead. +     */ +    template <typename... T> +    std::tuple<Kernel::SharedPtr<T>...> PopObjects(); + +    /// Convenience wrapper around PopObjects() which assigns the handles to the passed references. +    template <typename... T> +    void PopObjects(Kernel::SharedPtr<T>&... pointers) { +        std::tie(pointers...) = PopObjects<T...>(); +    }      /**       * @brief Pops the static buffer vaddr @@ -311,15 +401,54 @@ inline Kernel::Handle RequestParser::PopHandle() {      return Pop<Kernel::Handle>();  } -template <typename... H> -void RequestParser::PopHandles(H&... handles) { -    const u32 handle_descriptor = Pop<u32>(); -    const int handles_number = sizeof...(H); -    DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor), -                     "Tried to pop handle(s) but the descriptor is not a handle descriptor"); -    DEBUG_ASSERT_MSG(handles_number == HandleNumberFromDesc(handle_descriptor), -                     "Number of handles doesn't match the descriptor"); -    Pop(static_cast<Kernel::Handle&>(handles)...); +template <unsigned int N> +std::array<Kernel::Handle, N> RequestParser::PopHandles() { +    u32 handle_descriptor = Pop<u32>(); +    ASSERT_MSG(IsHandleDescriptor(handle_descriptor), +               "Tried to pop handle(s) but the descriptor is not a handle descriptor"); +    ASSERT_MSG(N == HandleNumberFromDesc(handle_descriptor), +               "Number of handles doesn't match the descriptor"); + +    std::array<Kernel::Handle, N> handles{}; +    for (Kernel::Handle& handle : handles) { +        handle = Pop<Kernel::Handle>(); +    } +    return handles; +} + +inline Kernel::SharedPtr<Kernel::Object> RequestParser::PopGenericObject() { +    Kernel::Handle handle = PopHandle(); +    return context->GetIncomingHandle(handle); +} + +template <typename T> +Kernel::SharedPtr<T> RequestParser::PopObject() { +    return Kernel::DynamicObjectCast<T>(PopGenericObject()); +} + +template <unsigned int N> +inline std::array<Kernel::SharedPtr<Kernel::Object>, N> RequestParser::PopGenericObjects() { +    std::array<Kernel::Handle, N> handles = PopHandles<N>(); +    std::array<Kernel::SharedPtr<Kernel::Object>, N> pointers; +    for (int i = 0; i < N; ++i) { +        pointers[i] = context->GetIncomingHandle(handles[i]); +    } +    return pointers; +} + +namespace detail { +template <typename... T, size_t... I> +std::tuple<Kernel::SharedPtr<T>...> PopObjectsHelper( +    std::array<Kernel::SharedPtr<Kernel::Object>, sizeof...(T)>&& pointers, +    std::index_sequence<I...>) { +    return std::make_tuple(Kernel::DynamicObjectCast<T>(std::move(pointers[I]))...); +} +} // namespace detail + +template <typename... T> +inline std::tuple<Kernel::SharedPtr<T>...> RequestParser::PopObjects() { +    return detail::PopObjectsHelper<T...>(PopGenericObjects<sizeof...(T)>(), +                                          std::index_sequence_for<T...>{});  }  inline VAddr RequestParser::PopStaticBuffer(size_t* data_size, bool useStaticBuffersToGetVaddr) { diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h index 6a7af93a9..1d24401b1 100644 --- a/src/core/hle/kernel/address_arbiter.h +++ b/src/core/hle/kernel/address_arbiter.h @@ -6,6 +6,7 @@  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/result.h"  // Address arbiters are an underlying kernel synchronization object that can be created/used via  // supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index 03ffdece1..ce5d94e99 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -6,6 +6,7 @@  #include "core/hle/kernel/client_port.h"  #include "core/hle/kernel/client_session.h"  #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/hle_ipc.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/server_port.h"  #include "core/hle/kernel/server_session.h" @@ -25,20 +26,17 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {      active_sessions++;      // Create a new session pair, let the created sessions inherit the parent port's HLE handler. -    auto sessions = -        ServerSession::CreateSessionPair(server_port->GetName(), server_port->hle_handler, this); -    auto client_session = std::get<SharedPtr<ClientSession>>(sessions); -    auto server_session = std::get<SharedPtr<ServerSession>>(sessions); +    auto sessions = ServerSession::CreateSessionPair(server_port->GetName(), this);      if (server_port->hle_handler) -        server_port->hle_handler->ClientConnected(server_session); +        server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));      else -        server_port->pending_sessions.push_back(std::move(server_session)); +        server_port->pending_sessions.push_back(std::get<SharedPtr<ServerSession>>(sessions));      // Wake the threads waiting on the ServerPort      server_port->WakeupAllWaitingThreads(); -    return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); +    return MakeResult(std::get<SharedPtr<ClientSession>>(sessions));  }  } // namespace diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h index 511490c7c..8f7d6ac44 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -7,6 +7,7 @@  #include <string>  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/result.h"  namespace Kernel { diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index 783b1c061..fef97af1f 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -5,7 +5,10 @@  #include "common/assert.h"  #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/hle_ipc.h"  #include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/session.h"  namespace Kernel { @@ -14,9 +17,13 @@ ClientSession::~ClientSession() {      // This destructor will be called automatically when the last ClientSession handle is closed by      // the emulated application. -    if (parent->server) { -        if (parent->server->hle_handler) -            parent->server->hle_handler->ClientDisconnected(parent->server); +    // Local references to ServerSession and SessionRequestHandler are necessary to guarantee they +    // will be kept alive until after ClientDisconnected() returns. +    SharedPtr<ServerSession> server = parent->server; +    if (server) { +        std::shared_ptr<SessionRequestHandler> hle_handler = server->hle_handler; +        if (hle_handler) +            hle_handler->ClientDisconnected(server);          // TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set          // their WaitSynchronization result to 0xC920181A. @@ -26,11 +33,13 @@ ClientSession::~ClientSession() {  }  ResultCode ClientSession::SendSyncRequest() { -    // Signal the server session that new data is available -    if (parent->server) -        return parent->server->HandleSyncRequest(); +    // Keep ServerSession alive until we're done working with it. +    SharedPtr<ServerSession> server = parent->server; +    if (server == nullptr) +        return ERR_SESSION_CLOSED_BY_REMOTE; -    return ERR_SESSION_CLOSED_BY_REMOTE; +    // Signal the server session that new data is available +    return server->HandleSyncRequest();  }  } // namespace diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h index 9f3adb72b..2de379c09 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -6,10 +6,9 @@  #include <memory>  #include <string> -  #include "common/common_types.h" -  #include "core/hle/kernel/kernel.h" +#include "core/hle/result.h"  namespace Kernel { diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h index 3e3673508..cc41abb85 100644 --- a/src/core/hle/kernel/event.h +++ b/src/core/hle/kernel/event.h @@ -6,6 +6,7 @@  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  namespace Kernel { diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp new file mode 100644 index 000000000..c7322d883 --- /dev/null +++ b/src/core/hle/kernel/handle_table.cpp @@ -0,0 +1,97 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <utility> +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +HandleTable g_handle_table; + +HandleTable::HandleTable() { +    next_generation = 1; +    Clear(); +} + +ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { +    DEBUG_ASSERT(obj != nullptr); + +    u16 slot = next_free_slot; +    if (slot >= generations.size()) { +        LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use."); +        return ERR_OUT_OF_HANDLES; +    } +    next_free_slot = generations[slot]; + +    u16 generation = next_generation++; + +    // Overflow count so it fits in the 15 bits dedicated to the generation in the handle. +    // CTR-OS doesn't use generation 0, so skip straight to 1. +    if (next_generation >= (1 << 15)) +        next_generation = 1; + +    generations[slot] = generation; +    objects[slot] = std::move(obj); + +    Handle handle = generation | (slot << 15); +    return MakeResult<Handle>(handle); +} + +ResultVal<Handle> HandleTable::Duplicate(Handle handle) { +    SharedPtr<Object> object = GetGeneric(handle); +    if (object == nullptr) { +        LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle); +        return ERR_INVALID_HANDLE; +    } +    return Create(std::move(object)); +} + +ResultCode HandleTable::Close(Handle handle) { +    if (!IsValid(handle)) +        return ERR_INVALID_HANDLE; + +    u16 slot = GetSlot(handle); + +    objects[slot] = nullptr; + +    generations[slot] = next_free_slot; +    next_free_slot = slot; +    return RESULT_SUCCESS; +} + +bool HandleTable::IsValid(Handle handle) const { +    size_t slot = GetSlot(handle); +    u16 generation = GetGeneration(handle); + +    return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation; +} + +SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { +    if (handle == CurrentThread) { +        return GetCurrentThread(); +    } else if (handle == CurrentProcess) { +        return g_current_process; +    } + +    if (!IsValid(handle)) { +        return nullptr; +    } +    return objects[GetSlot(handle)]; +} + +void HandleTable::Clear() { +    for (u16 i = 0; i < MAX_COUNT; ++i) { +        generations[i] = i + 1; +        objects[i] = nullptr; +    } +    next_free_slot = 0; +} + +} // namespace diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h new file mode 100644 index 000000000..d6aaefbf7 --- /dev/null +++ b/src/core/hle/kernel/handle_table.h @@ -0,0 +1,126 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include "common/common_types.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/result.h" + +namespace Kernel { + +enum KernelHandle : Handle { +    CurrentThread = 0xFFFF8000, +    CurrentProcess = 0xFFFF8001, +}; + +/** + * This class allows the creation of Handles, which are references to objects that can be tested + * for validity and looked up. Here they are used to pass references to kernel objects to/from the + * emulated process. it has been designed so that it follows the same handle format and has + * approximately the same restrictions as the handle manager in the CTR-OS. + * + * Handles contain two sub-fields: a slot index (bits 31:15) and a generation value (bits 14:0). + * The slot index is used to index into the arrays in this class to access the data corresponding + * to the Handle. + * + * To prevent accidental use of a freed Handle whose slot has already been reused, a global counter + * is kept and incremented every time a Handle is created. This is the Handle's "generation". The + * value of the counter is stored into the Handle as well as in the handle table (in the + * "generations" array). When looking up a handle, the Handle's generation must match with the + * value stored on the class, otherwise the Handle is considered invalid. + * + * To find free slots when allocating a Handle without needing to scan the entire object array, the + * generations field of unallocated slots is re-purposed as a linked list of indices to free slots. + * When a Handle is created, an index is popped off the list and used for the new Handle. When it + * is destroyed, it is again pushed onto the list to be re-used by the next allocation. It is + * likely that this allocation strategy differs from the one used in CTR-OS, but this hasn't been + * verified and isn't likely to cause any problems. + */ +class HandleTable final : NonCopyable { +public: +    HandleTable(); + +    /** +     * Allocates a handle for the given object. +     * @return The created Handle or one of the following errors: +     *           - `ERR_OUT_OF_HANDLES`: the maximum number of handles has been exceeded. +     */ +    ResultVal<Handle> Create(SharedPtr<Object> obj); + +    /** +     * Returns a new handle that points to the same object as the passed in handle. +     * @return The duplicated Handle or one of the following errors: +     *           - `ERR_INVALID_HANDLE`: an invalid handle was passed in. +     *           - Any errors returned by `Create()`. +     */ +    ResultVal<Handle> Duplicate(Handle handle); + +    /** +     * Closes a handle, removing it from the table and decreasing the object's ref-count. +     * @return `RESULT_SUCCESS` or one of the following errors: +     *           - `ERR_INVALID_HANDLE`: an invalid handle was passed in. +     */ +    ResultCode Close(Handle handle); + +    /// Checks if a handle is valid and points to an existing object. +    bool IsValid(Handle handle) const; + +    /** +     * Looks up a handle. +     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid. +     */ +    SharedPtr<Object> GetGeneric(Handle handle) const; + +    /** +     * Looks up a handle while verifying its type. +     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or its +     *         type differs from the requested one. +     */ +    template <class T> +    SharedPtr<T> Get(Handle handle) const { +        return DynamicObjectCast<T>(GetGeneric(handle)); +    } + +    /// Closes all handles held in this table. +    void Clear(); + +private: +    /** +     * This is the maximum limit of handles allowed per process in CTR-OS. It can be further +     * reduced by ExHeader values, but this is not emulated here. +     */ +    static const size_t MAX_COUNT = 4096; + +    static u16 GetSlot(Handle handle) { +        return handle >> 15; +    } +    static u16 GetGeneration(Handle handle) { +        return handle & 0x7FFF; +    } + +    /// Stores the Object referenced by the handle or null if the slot is empty. +    std::array<SharedPtr<Object>, MAX_COUNT> objects; + +    /** +     * The value of `next_generation` when the handle was created, used to check for validity. For +     * empty slots, contains the index of the next free slot in the list. +     */ +    std::array<u16, MAX_COUNT> generations; + +    /** +     * Global counter of the number of created handles. Stored in `generations` when a handle is +     * created, and wraps around to 1 when it hits 0x8000. +     */ +    u16 next_generation; + +    /// Head of the free slots linked list. +    u16 next_free_slot; +}; + +extern HandleTable g_handle_table; + +} // namespace diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp new file mode 100644 index 000000000..6cf1886cf --- /dev/null +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -0,0 +1,125 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <boost/range/algorithm_ext/erase.hpp> +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/server_session.h" + +namespace Kernel { + +void SessionRequestHandler::ClientConnected(SharedPtr<ServerSession> server_session) { +    server_session->SetHleHandler(shared_from_this()); +    connected_sessions.push_back(server_session); +} + +void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_session) { +    server_session->SetHleHandler(nullptr); +    boost::range::remove_erase(connected_sessions, server_session); +} + +HLERequestContext::~HLERequestContext() = default; + +SharedPtr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const { +    ASSERT(id_from_cmdbuf < request_handles.size()); +    return request_handles[id_from_cmdbuf]; +} + +u32 HLERequestContext::AddOutgoingHandle(SharedPtr<Object> object) { +    request_handles.push_back(std::move(object)); +    return request_handles.size() - 1; +} + +void HLERequestContext::ClearIncomingObjects() { +    request_handles.clear(); +} + +ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, +                                                                Process& src_process, +                                                                HandleTable& src_table) { +    IPC::Header header{src_cmdbuf[0]}; + +    size_t untranslated_size = 1u + header.normal_params_size; +    size_t command_size = untranslated_size + header.translate_params_size; +    ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH); // TODO(yuriks): Return error + +    std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin()); + +    size_t i = untranslated_size; +    while (i < command_size) { +        u32 descriptor = cmd_buf[i] = src_cmdbuf[i]; +        i += 1; + +        switch (IPC::GetDescriptorType(descriptor)) { +        case IPC::DescriptorType::CopyHandle: +        case IPC::DescriptorType::MoveHandle: { +            u32 num_handles = IPC::HandleNumberFromDesc(descriptor); +            ASSERT(i + num_handles <= command_size); // TODO(yuriks): Return error +            for (u32 j = 0; j < num_handles; ++j) { +                Handle handle = src_cmdbuf[i]; +                SharedPtr<Object> object = src_table.GetGeneric(handle); +                ASSERT(object != nullptr); // TODO(yuriks): Return error +                if (descriptor == IPC::DescriptorType::MoveHandle) { +                    src_table.Close(handle); +                } + +                cmd_buf[i++] = AddOutgoingHandle(std::move(object)); +            } +            break; +        } +        case IPC::DescriptorType::CallingPid: { +            cmd_buf[i++] = src_process.process_id; +            break; +        } +        default: +            UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor); +        } +    } + +    return RESULT_SUCCESS; +} + +ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, +                                                           HandleTable& dst_table) const { +    IPC::Header header{cmd_buf[0]}; + +    size_t untranslated_size = 1u + header.normal_params_size; +    size_t command_size = untranslated_size + header.translate_params_size; +    ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH); + +    std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf); + +    size_t i = untranslated_size; +    while (i < command_size) { +        u32 descriptor = dst_cmdbuf[i] = cmd_buf[i]; +        i += 1; + +        switch (IPC::GetDescriptorType(descriptor)) { +        case IPC::DescriptorType::CopyHandle: +        case IPC::DescriptorType::MoveHandle: { +            // HLE services don't use handles, so we treat both CopyHandle and MoveHandle equally +            u32 num_handles = IPC::HandleNumberFromDesc(descriptor); +            ASSERT(i + num_handles <= command_size); +            for (u32 j = 0; j < num_handles; ++j) { +                SharedPtr<Object> object = GetIncomingHandle(cmd_buf[i]); + +                // TODO(yuriks): Figure out the proper error handling for if this fails +                Handle handle = dst_table.Create(object).Unwrap(); +                dst_cmdbuf[i++] = handle; +            } +            break; +        } +        default: +            UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor); +        } +    } + +    return RESULT_SUCCESS; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h new file mode 100644 index 000000000..cbb109d8f --- /dev/null +++ b/src/core/hle/kernel/hle_ipc.h @@ -0,0 +1,135 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <vector> +#include <boost/container/small_vector.hpp> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/server_session.h" + +namespace Service { +class ServiceFrameworkBase; +} + +namespace Kernel { + +class HandleTable; +class Process; + +/** + * Interface implemented by HLE Session handlers. + * This can be provided to a ServerSession in order to hook into several relevant events + * (such as a new connection or a SyncRequest) so they can be implemented in the emulator. + */ +class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> { +public: +    virtual ~SessionRequestHandler() = default; + +    /** +     * Handles a sync request from the emulated application. +     * @param server_session The ServerSession that was triggered for this sync request, +     * it should be used to differentiate which client (As in ClientSession) we're answering to. +     * TODO(Subv): Use a wrapper structure to hold all the information relevant to +     * this request (ServerSession, Originator thread, Translated command buffer, etc). +     * @returns ResultCode the result code of the translate operation. +     */ +    virtual void HandleSyncRequest(SharedPtr<ServerSession> server_session) = 0; + +    /** +     * Signals that a client has just connected to this HLE handler and keeps the +     * associated ServerSession alive for the duration of the connection. +     * @param server_session Owning pointer to the ServerSession associated with the connection. +     */ +    void ClientConnected(SharedPtr<ServerSession> server_session); + +    /** +     * Signals that a client has just disconnected from this HLE handler and releases the +     * associated ServerSession. +     * @param server_session ServerSession associated with the connection. +     */ +    void ClientDisconnected(SharedPtr<ServerSession> server_session); + +protected: +    /// List of sessions that are connected to this handler. +    /// A ServerSession whose server endpoint is an HLE implementation is kept alive by this list +    // for the duration of the connection. +    std::vector<SharedPtr<ServerSession>> connected_sessions; +}; + +/** + * Class containing information about an in-flight IPC request being handled by an HLE service + * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and + * when possible use the APIs in this class to service the request. + * + * HLE handle protocol + * =================== + * + * To avoid needing HLE services to keep a separate handle table, or having to directly modify the + * requester's table, a tweaked protocol is used to receive and send handles in requests. The kernel + * will decode the incoming handles into object pointers and insert a id in the buffer where the + * handle would normally be. The service then calls GetIncomingHandle() with that id to get the + * pointer to the object. Similarly, instead of inserting a handle into the command buffer, the + * service calls AddOutgoingHandle() and stores the returned id where the handle would normally go. + * + * The end result is similar to just giving services their own real handle tables, but since these + * ids are local to a specific context, it avoids requiring services to manage handles for objects + * across multiple calls and ensuring that unneeded handles are cleaned up. + */ +class HLERequestContext { +public: +    ~HLERequestContext(); + +    /// Returns a pointer to the IPC command buffer for this request. +    u32* CommandBuffer() { +        return cmd_buf.data(); +    } + +    /** +     * Returns the session through which this request was made. This can be used as a map key to +     * access per-client data on services. +     */ +    SharedPtr<ServerSession> Session() const { +        return session; +    } + +    /** +     * Resolves a object id from the request command buffer into a pointer to an object. See the +     * "HLE handle protocol" section in the class documentation for more details. +     */ +    SharedPtr<Object> GetIncomingHandle(u32 id_from_cmdbuf) const; + +    /** +     * Adds an outgoing object to the response, returning the id which should be used to reference +     * it. See the "HLE handle protocol" section in the class documentation for more details. +     */ +    u32 AddOutgoingHandle(SharedPtr<Object> object); + +    /** +     * Discards all Objects from the context, invalidating all ids. This may be called after reading +     * out all incoming objects, so that the buffer memory can be re-used for outgoing handles, but +     * this is not required. +     */ +    void ClearIncomingObjects(); + +private: +    friend class Service::ServiceFrameworkBase; + +    ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process, +                                                 HandleTable& src_table); +    ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, +                                            HandleTable& dst_table) const; + +    std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; +    SharedPtr<ServerSession> session; +    // TODO(yuriks): Check common usage of this and optimize size accordingly +    boost::container::small_vector<SharedPtr<Object>, 8> request_handles; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 7f84e01aa..7470a97ca 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -2,11 +2,8 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. -#include <algorithm> -#include "common/assert.h" -#include "common/logging/log.h"  #include "core/hle/config_mem.h" -#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/memory.h"  #include "core/hle/kernel/process.h" @@ -18,165 +15,6 @@  namespace Kernel {  unsigned int Object::next_object_id; -HandleTable g_handle_table; - -void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { -    auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); -    if (itr == waiting_threads.end()) -        waiting_threads.push_back(std::move(thread)); -} - -void WaitObject::RemoveWaitingThread(Thread* thread) { -    auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); -    // If a thread passed multiple handles to the same object, -    // the kernel might attempt to remove the thread from the object's -    // waiting threads list multiple times. -    if (itr != waiting_threads.end()) -        waiting_threads.erase(itr); -} - -SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { -    Thread* candidate = nullptr; -    s32 candidate_priority = THREADPRIO_LOWEST + 1; - -    for (const auto& thread : waiting_threads) { -        // The list of waiting threads must not contain threads that are not waiting to be awakened. -        ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || -                       thread->status == THREADSTATUS_WAIT_SYNCH_ALL, -                   "Inconsistent thread statuses in waiting_threads"); - -        if (thread->current_priority >= candidate_priority) -            continue; - -        if (ShouldWait(thread.get())) -            continue; - -        // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or -        // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready. -        bool ready_to_run = true; -        if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) { -            ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), -                                        [&thread](const SharedPtr<WaitObject>& object) { -                                            return object->ShouldWait(thread.get()); -                                        }); -        } - -        if (ready_to_run) { -            candidate = thread.get(); -            candidate_priority = thread->current_priority; -        } -    } - -    return candidate; -} - -void WaitObject::WakeupAllWaitingThreads() { -    while (auto thread = GetHighestPriorityReadyThread()) { -        if (!thread->IsSleepingOnWaitAll()) { -            Acquire(thread.get()); -            // Set the output index of the WaitSynchronizationN call to the index of this object. -            if (thread->wait_set_output) { -                thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); -                thread->wait_set_output = false; -            } -        } else { -            for (auto& object : thread->wait_objects) { -                object->Acquire(thread.get()); -            } -            // Note: This case doesn't update the output index of WaitSynchronizationN. -        } - -        for (auto& object : thread->wait_objects) -            object->RemoveWaitingThread(thread.get()); -        thread->wait_objects.clear(); - -        thread->SetWaitSynchronizationResult(RESULT_SUCCESS); -        thread->ResumeFromWait(); -    } -} - -const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const { -    return waiting_threads; -} - -HandleTable::HandleTable() { -    next_generation = 1; -    Clear(); -} - -ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { -    DEBUG_ASSERT(obj != nullptr); - -    u16 slot = next_free_slot; -    if (slot >= generations.size()) { -        LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use."); -        return ERR_OUT_OF_HANDLES; -    } -    next_free_slot = generations[slot]; - -    u16 generation = next_generation++; - -    // Overflow count so it fits in the 15 bits dedicated to the generation in the handle. -    // CTR-OS doesn't use generation 0, so skip straight to 1. -    if (next_generation >= (1 << 15)) -        next_generation = 1; - -    generations[slot] = generation; -    objects[slot] = std::move(obj); - -    Handle handle = generation | (slot << 15); -    return MakeResult<Handle>(handle); -} - -ResultVal<Handle> HandleTable::Duplicate(Handle handle) { -    SharedPtr<Object> object = GetGeneric(handle); -    if (object == nullptr) { -        LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle); -        return ERR_INVALID_HANDLE; -    } -    return Create(std::move(object)); -} - -ResultCode HandleTable::Close(Handle handle) { -    if (!IsValid(handle)) -        return ERR_INVALID_HANDLE; - -    u16 slot = GetSlot(handle); - -    objects[slot] = nullptr; - -    generations[slot] = next_free_slot; -    next_free_slot = slot; -    return RESULT_SUCCESS; -} - -bool HandleTable::IsValid(Handle handle) const { -    size_t slot = GetSlot(handle); -    u16 generation = GetGeneration(handle); - -    return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation; -} - -SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { -    if (handle == CurrentThread) { -        return GetCurrentThread(); -    } else if (handle == CurrentProcess) { -        return g_current_process; -    } - -    if (!IsValid(handle)) { -        return nullptr; -    } -    return objects[GetSlot(handle)]; -} - -void HandleTable::Clear() { -    for (u16 i = 0; i < MAX_COUNT; ++i) { -        generations[i] = i + 1; -        objects[i] = nullptr; -    } -    next_free_slot = 0; -}  /// Initialize the kernel  void Init(u32 system_mode) { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 94f2025a0..9cf288b08 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -4,26 +4,16 @@  #pragma once -#include <algorithm> -#include <array>  #include <cstddef>  #include <string> -#include <vector> +#include <utility>  #include <boost/smart_ptr/intrusive_ptr.hpp>  #include "common/common_types.h" -#include "core/hle/result.h"  namespace Kernel {  using Handle = u32; -class Thread; - -enum KernelHandle : Handle { -    CurrentThread = 0xFFFF8000, -    CurrentProcess = 0xFFFF8001, -}; -  enum class HandleType : u32 {      Unknown,      Event, @@ -121,170 +111,17 @@ inline void intrusive_ptr_release(Object* object) {  template <typename T>  using SharedPtr = boost::intrusive_ptr<T>; -/// Class that represents a Kernel object that a thread can be waiting on -class WaitObject : public Object { -public: -    /** -     * Check if the specified thread should wait until the object is available -     * @param thread The thread about which we're deciding. -     * @return True if the current thread should wait due to this object being unavailable -     */ -    virtual bool ShouldWait(Thread* thread) const = 0; - -    /// Acquire/lock the object for the specified thread if it is available -    virtual void Acquire(Thread* thread) = 0; - -    /** -     * Add a thread to wait on this object -     * @param thread Pointer to thread to add -     */ -    virtual void AddWaitingThread(SharedPtr<Thread> thread); - -    /** -     * Removes a thread from waiting on this object (e.g. if it was resumed already) -     * @param thread Pointer to thread to remove -     */ -    virtual void RemoveWaitingThread(Thread* thread); - -    /** -     * Wake up all threads waiting on this object that can be awoken, in priority order, -     * and set the synchronization result and output of the thread. -     */ -    virtual void WakeupAllWaitingThreads(); - -    /// Obtains the highest priority thread that is ready to run from this object's waiting list. -    SharedPtr<Thread> GetHighestPriorityReadyThread(); - -    /// Get a const reference to the waiting threads list for debug use -    const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const; - -private: -    /// Threads waiting for this object to become available -    std::vector<SharedPtr<Thread>> waiting_threads; -}; -  /** - * This class allows the creation of Handles, which are references to objects that can be tested - * for validity and looked up. Here they are used to pass references to kernel objects to/from the - * emulated process. it has been designed so that it follows the same handle format and has - * approximately the same restrictions as the handle manager in the CTR-OS. - * - * Handles contain two sub-fields: a slot index (bits 31:15) and a generation value (bits 14:0). - * The slot index is used to index into the arrays in this class to access the data corresponding - * to the Handle. - * - * To prevent accidental use of a freed Handle whose slot has already been reused, a global counter - * is kept and incremented every time a Handle is created. This is the Handle's "generation". The - * value of the counter is stored into the Handle as well as in the handle table (in the - * "generations" array). When looking up a handle, the Handle's generation must match with the - * value stored on the class, otherwise the Handle is considered invalid. - * - * To find free slots when allocating a Handle without needing to scan the entire object array, the - * generations field of unallocated slots is re-purposed as a linked list of indices to free slots. - * When a Handle is created, an index is popped off the list and used for the new Handle. When it - * is destroyed, it is again pushed onto the list to be re-used by the next allocation. It is - * likely that this allocation strategy differs from the one used in CTR-OS, but this hasn't been - * verified and isn't likely to cause any problems. + * Attempts to downcast the given Object pointer to a pointer to T. + * @return Derived pointer to the object, or `nullptr` if `object` isn't of type T.   */ -class HandleTable final : NonCopyable { -public: -    HandleTable(); - -    /** -     * Allocates a handle for the given object. -     * @return The created Handle or one of the following errors: -     *           - `ERR_OUT_OF_HANDLES`: the maximum number of handles has been exceeded. -     */ -    ResultVal<Handle> Create(SharedPtr<Object> obj); - -    /** -     * Returns a new handle that points to the same object as the passed in handle. -     * @return The duplicated Handle or one of the following errors: -     *           - `ERR_INVALID_HANDLE`: an invalid handle was passed in. -     *           - Any errors returned by `Create()`. -     */ -    ResultVal<Handle> Duplicate(Handle handle); - -    /** -     * Closes a handle, removing it from the table and decreasing the object's ref-count. -     * @return `RESULT_SUCCESS` or one of the following errors: -     *           - `ERR_INVALID_HANDLE`: an invalid handle was passed in. -     */ -    ResultCode Close(Handle handle); - -    /// Checks if a handle is valid and points to an existing object. -    bool IsValid(Handle handle) const; - -    /** -     * Looks up a handle. -     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid. -     */ -    SharedPtr<Object> GetGeneric(Handle handle) const; - -    /** -     * Looks up a handle while verifying its type. -     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or its -     *         type differs from the handle type `T::HANDLE_TYPE`. -     */ -    template <class T> -    SharedPtr<T> Get(Handle handle) const { -        SharedPtr<Object> object = GetGeneric(handle); -        if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) { -            return boost::static_pointer_cast<T>(std::move(object)); -        } -        return nullptr; -    } - -    /** -     * Looks up a handle while verifying that it is an object that a thread can wait on -     * @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or it is -     *         not a waitable object. -     */ -    SharedPtr<WaitObject> GetWaitObject(Handle handle) const { -        SharedPtr<Object> object = GetGeneric(handle); -        if (object != nullptr && object->IsWaitable()) { -            return boost::static_pointer_cast<WaitObject>(std::move(object)); -        } -        return nullptr; -    } - -    /// Closes all handles held in this table. -    void Clear(); - -private: -    /** -     * This is the maximum limit of handles allowed per process in CTR-OS. It can be further -     * reduced by ExHeader values, but this is not emulated here. -     */ -    static const size_t MAX_COUNT = 4096; - -    static u16 GetSlot(Handle handle) { -        return handle >> 15; -    } -    static u16 GetGeneration(Handle handle) { -        return handle & 0x7FFF; +template <typename T> +inline SharedPtr<T> DynamicObjectCast(SharedPtr<Object> object) { +    if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) { +        return boost::static_pointer_cast<T>(std::move(object));      } - -    /// Stores the Object referenced by the handle or null if the slot is empty. -    std::array<SharedPtr<Object>, MAX_COUNT> objects; - -    /** -     * The value of `next_generation` when the handle was created, used to check for validity. For -     * empty slots, contains the index of the next free slot in the list. -     */ -    std::array<u16, MAX_COUNT> generations; - -    /** -     * Global counter of the number of created handles. Stored in `generations` when a handle is -     * created, and wraps around to 1 when it hits 0x8000. -     */ -    u16 next_generation; - -    /// Head of the free slots linked list. -    u16 next_free_slot; -}; - -extern HandleTable g_handle_table; +    return nullptr; +}  /// Initialize the kernel with the specified system mode.  void Init(u32 system_mode); diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp index 8250a90b5..804f23b1c 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <algorithm>  #include <cinttypes>  #include <map>  #include <memory> diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index c57adf400..bacacd690 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -7,6 +7,7 @@  #include <string>  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  namespace Kernel { diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index 3f51bc5de..a8f10a3ee 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include <cstring> +#include "common/assert.h"  #include "common/logging/log.h"  #include "core/hle/kernel/resource_limit.h" diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h index cde94f7cc..7b0cacf2e 100644 --- a/src/core/hle/kernel/semaphore.h +++ b/src/core/hle/kernel/semaphore.h @@ -8,6 +8,8 @@  #include <string>  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h" +#include "core/hle/result.h"  namespace Kernel { diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index fd3bbbcad..4d20c39a1 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -24,14 +24,12 @@ void ServerPort::Acquire(Thread* thread) {  }  std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( -    u32 max_sessions, std::string name, -    std::shared_ptr<Service::SessionRequestHandler> hle_handler) { +    u32 max_sessions, std::string name) {      SharedPtr<ServerPort> server_port(new ServerPort);      SharedPtr<ClientPort> client_port(new ClientPort);      server_port->name = name + "_Server"; -    server_port->hle_handler = std::move(hle_handler);      client_port->name = name + "_Client";      client_port->server_port = server_port;      client_port->max_sessions = max_sessions; diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index 6f8bdb6a9..f1419cd46 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -9,28 +9,24 @@  #include <tuple>  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" - -namespace Service { -class SessionRequestHandler; -} +#include "core/hle/kernel/wait_object.h"  namespace Kernel {  class ClientPort; +class SessionRequestHandler;  class ServerPort final : public WaitObject {  public:      /**       * Creates a pair of ServerPort and an associated ClientPort. +     *       * @param max_sessions Maximum number of sessions to the port       * @param name Optional name of the ports -     * @param hle_handler Optional HLE handler template for the port, -     * ServerSessions crated from this port will inherit a reference to this handler.       * @return The created port tuple       */      static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair( -        u32 max_sessions, std::string name = "UnknownPort", -        std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); +        u32 max_sessions, std::string name = "UnknownPort");      std::string GetTypeName() const override {          return "ServerPort"; @@ -44,6 +40,14 @@ public:          return HANDLE_TYPE;      } +    /** +     * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port +     * will inherit a reference to this handler. +     */ +    void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) { +        hle_handler = std::move(hle_handler_); +    } +      std::string name; ///< Name of port (optional)      std::vector<SharedPtr<WaitObject>> @@ -51,7 +55,7 @@ public:      /// This session's HLE request handler template (optional)      /// ServerSessions created from this port inherit a reference to this handler. -    std::shared_ptr<Service::SessionRequestHandler> hle_handler; +    std::shared_ptr<SessionRequestHandler> hle_handler;      bool ShouldWait(Thread* thread) const override;      void Acquire(Thread* thread) override; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 500b909ab..2dc709bc9 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -4,8 +4,11 @@  #include <tuple> +#include "core/hle/kernel/client_port.h"  #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/hle_ipc.h"  #include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/session.h"  #include "core/hle/kernel/thread.h"  namespace Kernel { @@ -25,16 +28,14 @@ ServerSession::~ServerSession() {      parent->server = nullptr;  } -ResultVal<SharedPtr<ServerSession>> ServerSession::Create( -    std::string name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { +ResultVal<SharedPtr<ServerSession>> ServerSession::Create(std::string name) {      SharedPtr<ServerSession> server_session(new ServerSession);      server_session->name = std::move(name);      server_session->signaled = false; -    server_session->hle_handler = std::move(hle_handler);      server_session->parent = nullptr; -    return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); +    return MakeResult(std::move(server_session));  }  bool ServerSession::ShouldWait(Thread* thread) const { @@ -68,13 +69,9 @@ ResultCode ServerSession::HandleSyncRequest() {      return RESULT_SUCCESS;  } -ServerSession::SessionPair ServerSession::CreateSessionPair( -    const std::string& name, std::shared_ptr<Service::SessionRequestHandler> hle_handler, -    SharedPtr<ClientPort> port) { - -    auto server_session = -        ServerSession::Create(name + "_Server", std::move(hle_handler)).MoveFrom(); - +ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& name, +                                                            SharedPtr<ClientPort> port) { +    auto server_session = ServerSession::Create(name + "_Server").MoveFrom();      SharedPtr<ClientSession> client_session(new ClientSession);      client_session->name = name + "_Client"; diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index c907d487c..5365605da 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -9,10 +9,8 @@  #include "common/assert.h"  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/session.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/wait_object.h"  #include "core/hle/result.h" -#include "core/hle/service/service.h"  #include "core/memory.h"  namespace Kernel { @@ -20,6 +18,9 @@ namespace Kernel {  class ClientSession;  class ClientPort;  class ServerSession; +class Session; +class SessionRequestHandler; +class Thread;  /**   * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS @@ -49,14 +50,20 @@ public:      /**       * Creates a pair of ServerSession and an associated ClientSession.       * @param name        Optional name of the ports. -     * @param hle_handler Optional HLE handler for this server session.       * @param client_port Optional The ClientPort that spawned this session.       * @return The created session tuple       */ -    static SessionPair CreateSessionPair( -        const std::string& name = "Unknown", -        std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr, -        SharedPtr<ClientPort> client_port = nullptr); +    static SessionPair CreateSessionPair(const std::string& name = "Unknown", +                                         SharedPtr<ClientPort> client_port = nullptr); + +    /** +     * Sets the HLE handler for the session. This handler will be called to service IPC requests +     * instead of the regular IPC machinery. (The regular IPC machinery is currently not +     * implemented.) +     */ +    void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) { +        hle_handler = std::move(hle_handler_); +    }      /**       * Handle a sync request from the emulated application. @@ -71,7 +78,7 @@ public:      std::string name;                ///< The name of this session (optional)      bool signaled;                   ///< Whether there's new data available to this ServerSession      std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint. -    std::shared_ptr<Service::SessionRequestHandler> +    std::shared_ptr<SessionRequestHandler>          hle_handler; ///< This session's HLE request handler (optional)  private: @@ -82,12 +89,9 @@ private:       * Creates a server session. The server session can have an optional HLE handler,       * which will be invoked to handle the IPC requests that this session receives.       * @param name Optional name of the server session. -     * @param hle_handler Optional HLE handler for this server session.       * @return The created server session       */ -    static ResultVal<SharedPtr<ServerSession>> Create( -        std::string name = "Unknown", -        std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); +    static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown");  };  /** diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 519ff51a8..75ce626f8 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -15,6 +15,7 @@  #include "core/core.h"  #include "core/core_timing.h"  #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/memory.h"  #include "core/hle/kernel/mutex.h" diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 7b5169cfc..6a3566f15 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -12,6 +12,7 @@  #include "common/common_types.h"  #include "core/arm/arm_interface.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  #include "core/hle/result.h"  enum ThreadPriority : s32 { diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index a00c75679..6f2cf3b02 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -6,6 +6,7 @@  #include "common/assert.h"  #include "common/logging/log.h"  #include "core/core_timing.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/thread.h"  #include "core/hle/kernel/timer.h" diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index b0f818933..82552372d 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -6,6 +6,7 @@  #include "common/common_types.h"  #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/wait_object.h"  namespace Kernel { diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp new file mode 100644 index 000000000..f245eda6c --- /dev/null +++ b/src/core/hle/kernel/wait_object.cpp @@ -0,0 +1,99 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/hle/config_mem.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/memory.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/timer.h" +#include "core/hle/shared_page.h" + +namespace Kernel { + +void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { +    auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); +    if (itr == waiting_threads.end()) +        waiting_threads.push_back(std::move(thread)); +} + +void WaitObject::RemoveWaitingThread(Thread* thread) { +    auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); +    // If a thread passed multiple handles to the same object, +    // the kernel might attempt to remove the thread from the object's +    // waiting threads list multiple times. +    if (itr != waiting_threads.end()) +        waiting_threads.erase(itr); +} + +SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { +    Thread* candidate = nullptr; +    s32 candidate_priority = THREADPRIO_LOWEST + 1; + +    for (const auto& thread : waiting_threads) { +        // The list of waiting threads must not contain threads that are not waiting to be awakened. +        ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || +                       thread->status == THREADSTATUS_WAIT_SYNCH_ALL, +                   "Inconsistent thread statuses in waiting_threads"); + +        if (thread->current_priority >= candidate_priority) +            continue; + +        if (ShouldWait(thread.get())) +            continue; + +        // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or +        // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready. +        bool ready_to_run = true; +        if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) { +            ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), +                                        [&thread](const SharedPtr<WaitObject>& object) { +                                            return object->ShouldWait(thread.get()); +                                        }); +        } + +        if (ready_to_run) { +            candidate = thread.get(); +            candidate_priority = thread->current_priority; +        } +    } + +    return candidate; +} + +void WaitObject::WakeupAllWaitingThreads() { +    while (auto thread = GetHighestPriorityReadyThread()) { +        if (!thread->IsSleepingOnWaitAll()) { +            Acquire(thread.get()); +            // Set the output index of the WaitSynchronizationN call to the index of this object. +            if (thread->wait_set_output) { +                thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); +                thread->wait_set_output = false; +            } +        } else { +            for (auto& object : thread->wait_objects) { +                object->Acquire(thread.get()); +            } +            // Note: This case doesn't update the output index of WaitSynchronizationN. +        } + +        for (auto& object : thread->wait_objects) +            object->RemoveWaitingThread(thread.get()); +        thread->wait_objects.clear(); + +        thread->SetWaitSynchronizationResult(RESULT_SUCCESS); +        thread->ResumeFromWait(); +    } +} + +const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const { +    return waiting_threads; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/wait_object.h b/src/core/hle/kernel/wait_object.h new file mode 100644 index 000000000..861578186 --- /dev/null +++ b/src/core/hle/kernel/wait_object.h @@ -0,0 +1,67 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <boost/smart_ptr/intrusive_ptr.hpp> +#include "common/common_types.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class Thread; + +/// Class that represents a Kernel object that a thread can be waiting on +class WaitObject : public Object { +public: +    /** +     * Check if the specified thread should wait until the object is available +     * @param thread The thread about which we're deciding. +     * @return True if the current thread should wait due to this object being unavailable +     */ +    virtual bool ShouldWait(Thread* thread) const = 0; + +    /// Acquire/lock the object for the specified thread if it is available +    virtual void Acquire(Thread* thread) = 0; + +    /** +     * Add a thread to wait on this object +     * @param thread Pointer to thread to add +     */ +    virtual void AddWaitingThread(SharedPtr<Thread> thread); + +    /** +     * Removes a thread from waiting on this object (e.g. if it was resumed already) +     * @param thread Pointer to thread to remove +     */ +    virtual void RemoveWaitingThread(Thread* thread); + +    /** +     * Wake up all threads waiting on this object that can be awoken, in priority order, +     * and set the synchronization result and output of the thread. +     */ +    virtual void WakeupAllWaitingThreads(); + +    /// Obtains the highest priority thread that is ready to run from this object's waiting list. +    SharedPtr<Thread> GetHighestPriorityReadyThread(); + +    /// Get a const reference to the waiting threads list for debug use +    const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const; + +private: +    /// Threads waiting for this object to become available +    std::vector<SharedPtr<Thread>> waiting_threads; +}; + +// Specialization of DynamicObjectCast for WaitObjects +template <> +inline SharedPtr<WaitObject> DynamicObjectCast<WaitObject>(SharedPtr<Object> object) { +    if (object != nullptr && object->IsWaitable()) { +        return boost::static_pointer_cast<WaitObject>(std::move(object)); +    } +    return nullptr; +} + +} // namespace Kernel diff --git a/src/core/hle/result.h b/src/core/hle/result.h index c49650f7d..5f2cdbb96 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -416,6 +416,16 @@ ResultVal<T> MakeResult(Args&&... args) {  }  /** + * Deducible overload of MakeResult, allowing the template parameter to be ommited if you're just + * copy or move constructing. + */ +template <typename Arg> +ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) { +    return ResultVal<std::remove_reference_t<Arg>>::WithCode(RESULT_SUCCESS, +                                                             std::forward<Arg>(arg)); +} + +/**   * Check for the success of `source` (which must evaluate to a ResultVal). If it succeeds, unwraps   * the contained value and assigns it to `target`, which can be either an l-value expression or a   * variable declaration. If it fails the return code is returned from the current function. Thus it @@ -426,3 +436,12 @@ ResultVal<T> MakeResult(Args&&... args) {      if (CONCAT2(check_result_L, __LINE__).Failed())                                                \          return CONCAT2(check_result_L, __LINE__).Code();                                           \      target = std::move(*CONCAT2(check_result_L, __LINE__)) + +/** + * Analogous to CASCADE_RESULT, but for a bare ResultCode. The code will be propagated if + * non-success, or discarded otherwise. + */ +#define CASCADE_CODE(source)                                                                       \ +    auto CONCAT2(check_result_L, __LINE__) = source;                                               \ +    if (CONCAT2(check_result_L, __LINE__).IsError())                                               \ +        return CONCAT2(check_result_L, __LINE__); diff --git a/src/core/hle/service/ac/ac.cpp b/src/core/hle/service/ac/ac.cpp index aa270a2c3..e3dd23949 100644 --- a/src/core/hle/service/ac/ac.cpp +++ b/src/core/hle/service/ac/ac.cpp @@ -4,11 +4,16 @@  #include <array> +#include "common/common_types.h"  #include "common/logging/log.h" +#include "core/hle/ipc.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/result.h"  #include "core/hle/service/ac/ac.h"  #include "core/hle/service/ac/ac_i.h"  #include "core/hle/service/ac/ac_u.h" +#include "core/memory.h"  namespace Service {  namespace AC { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index d344a622f..961305e9f 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -2,8 +2,12 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <array>  #include <cinttypes> +#include "common/common_types.h"  #include "common/logging/log.h" +#include "core/hle/ipc.h" +#include "core/hle/result.h"  #include "core/hle/service/am/am.h"  #include "core/hle/service/am/am_app.h"  #include "core/hle/service/am/am_net.h" @@ -176,8 +180,6 @@ void GetTicketList(Service::Interface* self) {  }  void Init() { -    using namespace Kernel; -      AddService(new AM_APP_Interface);      AddService(new AM_NET_Interface);      AddService(new AM_SYS_Interface); diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 366d1eacf..4c587e3c8 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -5,6 +5,7 @@  #include "common/common_paths.h"  #include "common/file_util.h"  #include "common/logging/log.h" +#include "core/core.h"  #include "core/hle/applets/applet.h"  #include "core/hle/kernel/event.h"  #include "core/hle/kernel/mutex.h" @@ -74,6 +75,7 @@ void GetSharedFont(Service::Interface* self) {          LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds");          rb.Push<u32>(-1); // TODO: Find the right error code          rb.Skip(1 + 2, true); +        Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont);          return;      } diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index e63b61450..ee80926d2 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -4,6 +4,8 @@  #pragma once +#include <vector> +#include "common/common_funcs.h"  #include "common/common_types.h"  #include "common/swap.h"  #include "core/hle/kernel/kernel.h" diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp index 91056189a..2bba3aff6 100644 --- a/src/core/hle/service/boss/boss.cpp +++ b/src/core/hle/service/boss/boss.cpp @@ -3,6 +3,9 @@  // Refer to the license.txt file included.  #include <cinttypes> +#include "common/logging/log.h" +#include "core/hle/ipc.h" +#include "core/hle/result.h"  #include "core/hle/service/boss/boss.h"  #include "core/hle/service/boss/boss_p.h"  #include "core/hle/service/boss/boss_u.h" diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp index 95665e754..7394c844f 100644 --- a/src/core/hle/service/cam/cam.cpp +++ b/src/core/hle/service/cam/cam.cpp @@ -11,13 +11,17 @@  #include "common/logging/log.h"  #include "core/core_timing.h"  #include "core/frontend/camera/factory.h" +#include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h"  #include "core/hle/kernel/event.h" +#include "core/hle/result.h"  #include "core/hle/service/cam/cam.h"  #include "core/hle/service/cam/cam_c.h"  #include "core/hle/service/cam/cam_q.h"  #include "core/hle/service/cam/cam_s.h"  #include "core/hle/service/cam/cam_u.h"  #include "core/hle/service/service.h" +#include "core/memory.h"  #include "core/settings.h"  namespace Service { @@ -55,7 +59,7 @@ struct PortConfig {      u16 x1; // x-coordinate of ending position for trimming      u16 y1; // y-coordinate of ending position for trimming -    u32 transfer_bytes; +    u16 transfer_bytes;      Kernel::SharedPtr<Kernel::Event> completion_event;      Kernel::SharedPtr<Kernel::Event> buffer_error_interrupt_event; @@ -225,8 +229,7 @@ static void ActivatePort(int port_id, int camera_id) {  template <int max_index>  class CommandParamBitSet : public BitSet8 {  public: -    explicit CommandParamBitSet(u32 command_param) -        : BitSet8(static_cast<u8>(command_param & 0xFF)) {} +    explicit CommandParamBitSet(u8 command_param) : BitSet8(command_param) {}      bool IsValid() const {          return m_val < (1 << max_index); @@ -244,9 +247,10 @@ using CameraSet = CommandParamBitSet<3>;  } // namespace  void StartCapture(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (port_select.IsValid()) {          for (int i : port_select) { @@ -267,21 +271,20 @@ void StartCapture(Service::Interface* self) {                  LOG_WARNING(Service_CAM, "port %u already started", i);              }          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);  }  void StopCapture(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (port_select.IsValid()) {          for (int i : port_select) { @@ -293,21 +296,20 @@ void StopCapture(Service::Interface* self) {                  LOG_WARNING(Service_CAM, "port %u already stopped", i);              }          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);  }  void IsBusy(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);      if (port_select.IsValid()) {          bool is_busy = true; @@ -315,80 +317,74 @@ void IsBusy(Service::Interface* self) {          for (int i : port_select) {              is_busy &= ports[i].is_busy;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = is_busy ? 1 : 0; +        rb.Push(RESULT_SUCCESS); +        rb.Push(is_busy);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE); +        rb.Skip(1, false);      } -    cmd_buff[0] = IPC::MakeHeader(0x3, 2, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);  }  void ClearBuffer(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x04, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); -    cmd_buff[0] = IPC::MakeHeader(0x4, 1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS);      LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);  }  void GetVsyncInterruptEvent(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x05, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);      if (port_select.IsSingle()) {          int port = *port_select.begin(); -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = IPC::CopyHandleDesc(); -        cmd_buff[3] = Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).MoveFrom(); +        rb.Push(RESULT_SUCCESS); +        rb.PushCopyHandles( +            Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).MoveFrom());      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; -        cmd_buff[2] = IPC::CopyHandleDesc(); -        cmd_buff[2] = 0; +        rb.Push(ERROR_INVALID_ENUM_VALUE); +        rb.PushCopyHandles(0);      } -    cmd_buff[0] = IPC::MakeHeader(0x5, 1, 2); -      LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);  }  void GetBufferErrorInterruptEvent(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x06, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);      if (port_select.IsSingle()) {          int port = *port_select.begin(); -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = IPC::CopyHandleDesc(); -        cmd_buff[3] = -            Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).MoveFrom(); +        rb.Push(RESULT_SUCCESS); +        rb.PushCopyHandles( +            Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).MoveFrom());      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; -        cmd_buff[2] = IPC::CopyHandleDesc(); -        cmd_buff[2] = 0; +        rb.Push(ERROR_INVALID_ENUM_VALUE); +        rb.PushCopyHandles(0);      }      LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);  }  void SetReceiving(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const VAddr dest = cmd_buff[1]; -    const PortSet port_select(cmd_buff[2]); -    const u32 image_size = cmd_buff[3]; -    const u32 trans_unit = cmd_buff[4] & 0xFFFF; - +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x07, 4, 2); +    const VAddr dest = rp.Pop<u32>(); +    const PortSet port_select(rp.Pop<u8>()); +    const u32 image_size = rp.Pop<u32>(); +    const u16 trans_unit = rp.Pop<u16>(); +    rp.PopHandle(); // Handle to destination process. not used + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);      if (port_select.IsSingle()) {          int port_id = *port_select.begin();          PortConfig& port = ports[port_id]; @@ -403,149 +399,145 @@ void SetReceiving(Service::Interface* self) {              port.is_pending_receiving = true;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = IPC::CopyHandleDesc(); -        cmd_buff[3] = Kernel::g_handle_table.Create(port.completion_event).MoveFrom(); +        rb.Push(RESULT_SUCCESS); +        rb.PushCopyHandles(Kernel::g_handle_table.Create(port.completion_event).MoveFrom());      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE); +        rb.PushCopyHandles(0);      } -    cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2); -      LOG_DEBUG(Service_CAM, "called, addr=0x%X, port_select=%u, image_size=%u, trans_unit=%u", dest,                port_select.m_val, image_size, trans_unit);  }  void IsFinishedReceiving(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x08, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);      if (port_select.IsSingle()) {          int port = *port_select.begin(); -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = (ports[port].is_receiving || ports[port].is_pending_receiving) ? 0 : 1; +        bool is_busy = ports[port].is_receiving || ports[port].is_pending_receiving; +        rb.Push(RESULT_SUCCESS); +        rb.Push(!is_busy);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE); +        rb.Skip(1, false);      } -    cmd_buff[0] = IPC::MakeHeader(0x8, 2, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);  }  void SetTransferLines(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); -    const u32 transfer_lines = cmd_buff[2] & 0xFFFF; -    const u32 width = cmd_buff[3] & 0xFFFF; -    const u32 height = cmd_buff[4] & 0xFFFF; +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x09, 4, 0); +    const PortSet port_select(rp.Pop<u8>()); +    const u16 transfer_lines = rp.Pop<u16>(); +    const u16 width = rp.Pop<u16>(); +    const u16 height = rp.Pop<u16>(); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (port_select.IsValid()) {          for (int i : port_select) {              ports[i].transfer_bytes = transfer_lines * width * 2;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0); -      LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u, lines=%u, width=%u, height=%u",                  port_select.m_val, transfer_lines, width, height);  }  void GetMaxLines(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0A, 2, 0); +    const u16 width = rp.Pop<u16>(); +    const u16 height = rp.Pop<u16>(); -    const u32 width = cmd_buff[1] & 0xFFFF; -    const u32 height = cmd_buff[2] & 0xFFFF; +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);      // Note: the result of the algorithm below are hwtested with width < 640 and with height < 480      constexpr u32 MIN_TRANSFER_UNIT = 256;      constexpr u32 MAX_BUFFER_SIZE = 2560;      if (width * height * 2 % MIN_TRANSFER_UNIT != 0) { -        cmd_buff[1] = ERROR_OUT_OF_RANGE.raw; +        rb.Push(ERROR_OUT_OF_RANGE); +        rb.Skip(1, false);      } else {          u32 lines = MAX_BUFFER_SIZE / width;          if (lines > height) {              lines = height;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        ResultCode result = RESULT_SUCCESS;          while (height % lines != 0 || (lines * width * 2 % MIN_TRANSFER_UNIT != 0)) {              --lines;              if (lines == 0) { -                cmd_buff[1] = ERROR_OUT_OF_RANGE.raw; +                result = ERROR_OUT_OF_RANGE;                  break;              }          } -        cmd_buff[2] = lines; +        rb.Push(result); +        rb.Push(lines);      } -    cmd_buff[0] = IPC::MakeHeader(0xA, 2, 0); -      LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height);  }  void SetTransferBytes(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); -    const u32 transfer_bytes = cmd_buff[2] & 0xFFFF; -    const u32 width = cmd_buff[3] & 0xFFFF; -    const u32 height = cmd_buff[4] & 0xFFFF; +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0B, 4, 0); +    const PortSet port_select(rp.Pop<u8>()); +    const u16 transfer_bytes = rp.Pop<u16>(); +    const u16 width = rp.Pop<u16>(); +    const u16 height = rp.Pop<u16>(); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (port_select.IsValid()) {          for (int i : port_select) {              ports[i].transfer_bytes = transfer_bytes;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0); -      LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u, bytes=%u, width=%u, height=%u",                  port_select.m_val, transfer_bytes, width, height);  }  void GetTransferBytes(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0C, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);      if (port_select.IsSingle()) {          int port = *port_select.begin(); -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = ports[port].transfer_bytes; +        rb.Push(RESULT_SUCCESS); +        rb.Push(ports[port].transfer_bytes);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE); +        rb.Skip(1, false);      } -    cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0); -      LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u", port_select.m_val);  }  void GetMaxBytes(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0D, 2, 0); +    const u16 width = rp.Pop<u16>(); +    const u16 height = rp.Pop<u16>(); -    const u32 width = cmd_buff[1] & 0xFFFF; -    const u32 height = cmd_buff[2] & 0xFFFF; +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);      // Note: the result of the algorithm below are hwtested with width < 640 and with height < 480      constexpr u32 MIN_TRANSFER_UNIT = 256;      constexpr u32 MAX_BUFFER_SIZE = 2560;      if (width * height * 2 % MIN_TRANSFER_UNIT != 0) { -        cmd_buff[1] = ERROR_OUT_OF_RANGE.raw; +        rb.Push(ERROR_OUT_OF_RANGE); +        rb.Skip(1, false);      } else {          u32 bytes = MAX_BUFFER_SIZE; @@ -553,63 +545,59 @@ void GetMaxBytes(Service::Interface* self) {              bytes -= MIN_TRANSFER_UNIT;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = bytes; +        rb.Push(RESULT_SUCCESS); +        rb.Push(bytes);      } -    cmd_buff[0] = IPC::MakeHeader(0xD, 2, 0);      LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height);  }  void SetTrimming(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); -    const bool trim = (cmd_buff[2] & 0xFF) != 0; +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0E, 2, 0); +    const PortSet port_select(rp.Pop<u8>()); +    const bool trim = rp.Pop<bool>(); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (port_select.IsValid()) {          for (int i : port_select) {              ports[i].is_trimming = trim;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0xE, 1, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u, trim=%d", port_select.m_val, trim);  }  void IsTrimming(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0F, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);      if (port_select.IsSingle()) {          int port = *port_select.begin(); -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = ports[port].is_trimming; +        rb.Push(RESULT_SUCCESS); +        rb.Push(ports[port].is_trimming);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE); +        rb.Skip(1, false);      } -    cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);  }  void SetTrimmingParams(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); -    const u16 x0 = static_cast<u16>(cmd_buff[2] & 0xFFFF); -    const u16 y0 = static_cast<u16>(cmd_buff[3] & 0xFFFF); -    const u16 x1 = static_cast<u16>(cmd_buff[4] & 0xFFFF); -    const u16 y1 = static_cast<u16>(cmd_buff[5] & 0xFFFF); - +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x10, 5, 0); +    const PortSet port_select(rp.Pop<u8>()); +    const u16 x0 = rp.Pop<u16>(); +    const u16 y0 = rp.Pop<u16>(); +    const u16 x1 = rp.Pop<u16>(); +    const u16 y1 = rp.Pop<u16>(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (port_select.IsValid()) {          for (int i : port_select) {              ports[i].x0 = x0; @@ -617,49 +605,46 @@ void SetTrimmingParams(Service::Interface* self) {              ports[i].x1 = x1;              ports[i].y1 = y1;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u, x0=%u, y0=%u, x1=%u, y1=%u", port_select.m_val,                x0, y0, x1, y1);  }  void GetTrimmingParams(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x11, 1, 0); +    const PortSet port_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(5, 0);      if (port_select.IsSingle()) {          int port = *port_select.begin(); -        cmd_buff[1] = RESULT_SUCCESS.raw; -        cmd_buff[2] = ports[port].x0; -        cmd_buff[3] = ports[port].y0; -        cmd_buff[4] = ports[port].x1; -        cmd_buff[5] = ports[port].y1; +        rb.Push(RESULT_SUCCESS); +        rb.Push(ports[port].x0); +        rb.Push(ports[port].y0); +        rb.Push(ports[port].x1); +        rb.Push(ports[port].y1);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE); +        rb.Skip(4, false);      } -    cmd_buff[0] = IPC::MakeHeader(0x11, 5, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);  }  void SetTrimmingParamsCenter(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const PortSet port_select(cmd_buff[1]); -    const u16 trim_w = static_cast<u16>(cmd_buff[2] & 0xFFFF); -    const u16 trim_h = static_cast<u16>(cmd_buff[3] & 0xFFFF); -    const u16 cam_w = static_cast<u16>(cmd_buff[4] & 0xFFFF); -    const u16 cam_h = static_cast<u16>(cmd_buff[5] & 0xFFFF); - +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x12, 5, 0); +    const PortSet port_select(rp.Pop<u8>()); +    const u16 trim_w = rp.Pop<u16>(); +    const u16 trim_h = rp.Pop<u16>(); +    const u16 cam_w = rp.Pop<u16>(); +    const u16 cam_h = rp.Pop<u16>(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (port_select.IsValid()) {          for (int i : port_select) {              ports[i].x0 = (cam_w - trim_w) / 2; @@ -667,23 +652,21 @@ void SetTrimmingParamsCenter(Service::Interface* self) {              ports[i].x1 = ports[i].x0 + trim_w;              ports[i].y1 = ports[i].y0 + trim_h;          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0); -      LOG_DEBUG(Service_CAM, "called, port_select=%u, trim_w=%u, trim_h=%u, cam_w=%u, cam_h=%u",                port_select.m_val, trim_w, trim_h, cam_w, cam_h);  }  void Activate(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const CameraSet camera_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x13, 1, 0); +    const CameraSet camera_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (camera_select.IsValid()) {          if (camera_select.m_val == 0) { // deactive all              for (int i = 0; i < 2; ++i) { @@ -694,10 +677,10 @@ void Activate(Service::Interface* self) {                  }                  ports[i].is_active = false;              } -            cmd_buff[1] = RESULT_SUCCESS.raw; +            rb.Push(RESULT_SUCCESS);          } else if (camera_select[0] && camera_select[1]) {              LOG_ERROR(Service_CAM, "camera 0 and 1 can't be both activated"); -            cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +            rb.Push(ERROR_INVALID_ENUM_VALUE);          } else {              if (camera_select[0]) {                  ActivatePort(0, 0); @@ -708,24 +691,22 @@ void Activate(Service::Interface* self) {              if (camera_select[2]) {                  ActivatePort(1, 2);              } -            cmd_buff[1] = RESULT_SUCCESS.raw; +            rb.Push(RESULT_SUCCESS);          }      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u", camera_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); -      LOG_DEBUG(Service_CAM, "called, camera_select=%u", camera_select.m_val);  }  void SwitchContext(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const CameraSet camera_select(cmd_buff[1]); -    const ContextSet context_select(cmd_buff[2]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x14, 2, 0); +    const CameraSet camera_select(rp.Pop<u8>()); +    const ContextSet context_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (camera_select.IsValid() && context_select.IsSingle()) {          int context = *context_select.begin();          for (int camera : camera_select) { @@ -736,26 +717,24 @@ void SwitchContext(Service::Interface* self) {              cameras[camera].impl->SetFormat(context_config.format);              cameras[camera].impl->SetResolution(context_config.resolution);          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,                    context_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x14, 1, 0); -      LOG_DEBUG(Service_CAM, "called, camera_select=%u, context_select=%u", camera_select.m_val,                context_select.m_val);  }  void FlipImage(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const CameraSet camera_select(cmd_buff[1]); -    const Flip flip = static_cast<Flip>(cmd_buff[2] & 0xFF); -    const ContextSet context_select(cmd_buff[3]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1D, 3, 0); +    const CameraSet camera_select(rp.Pop<u8>()); +    const Flip flip = static_cast<Flip>(rp.Pop<u8>()); +    const ContextSet context_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (camera_select.IsValid() && context_select.IsValid()) {          for (int camera : camera_select) {              for (int context : context_select) { @@ -765,32 +744,30 @@ void FlipImage(Service::Interface* self) {                  }              }          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,                    context_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x1D, 1, 0); -      LOG_DEBUG(Service_CAM, "called, camera_select=%u, flip=%d, context_select=%u",                camera_select.m_val, static_cast<int>(flip), context_select.m_val);  }  void SetDetailSize(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const CameraSet camera_select(cmd_buff[1]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 8, 0); +    const CameraSet camera_select(rp.Pop<u8>());      Resolution resolution; -    resolution.width = static_cast<u16>(cmd_buff[2] & 0xFFFF); -    resolution.height = static_cast<u16>(cmd_buff[3] & 0xFFFF); -    resolution.crop_x0 = static_cast<u16>(cmd_buff[4] & 0xFFFF); -    resolution.crop_y0 = static_cast<u16>(cmd_buff[5] & 0xFFFF); -    resolution.crop_x1 = static_cast<u16>(cmd_buff[6] & 0xFFFF); -    resolution.crop_y1 = static_cast<u16>(cmd_buff[7] & 0xFFFF); -    const ContextSet context_select(cmd_buff[8]); - +    resolution.width = rp.Pop<u16>(); +    resolution.height = rp.Pop<u16>(); +    resolution.crop_x0 = rp.Pop<u16>(); +    resolution.crop_y0 = rp.Pop<u16>(); +    resolution.crop_x1 = rp.Pop<u16>(); +    resolution.crop_y1 = rp.Pop<u16>(); +    const ContextSet context_select(rp.Pop<u8>()); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (camera_select.IsValid() && context_select.IsValid()) {          for (int camera : camera_select) {              for (int context : context_select) { @@ -800,15 +777,13 @@ void SetDetailSize(Service::Interface* self) {                  }              }          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,                    context_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0); -      LOG_DEBUG(Service_CAM, "called, camera_select=%u, width=%u, height=%u, crop_x0=%u, crop_y0=%u, "                             "crop_x1=%u, crop_y1=%u, context_select=%u",                camera_select.m_val, resolution.width, resolution.height, resolution.crop_x0, @@ -816,12 +791,12 @@ void SetDetailSize(Service::Interface* self) {  }  void SetSize(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const CameraSet camera_select(cmd_buff[1]); -    const u32 size = cmd_buff[2] & 0xFF; -    const ContextSet context_select(cmd_buff[3]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1F, 3, 0); +    const CameraSet camera_select(rp.Pop<u8>()); +    const u8 size = rp.Pop<u8>(); +    const ContextSet context_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (camera_select.IsValid() && context_select.IsValid()) {          for (int camera : camera_select) {              for (int context : context_select) { @@ -831,49 +806,45 @@ void SetSize(Service::Interface* self) {                  }              }          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,                    context_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x1F, 1, 0); -      LOG_DEBUG(Service_CAM, "called, camera_select=%u, size=%u, context_select=%u",                camera_select.m_val, size, context_select.m_val);  }  void SetFrameRate(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const CameraSet camera_select(cmd_buff[1]); -    const FrameRate frame_rate = static_cast<FrameRate>(cmd_buff[2] & 0xFF); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x20, 2, 0); +    const CameraSet camera_select(rp.Pop<u8>()); +    const FrameRate frame_rate = static_cast<FrameRate>(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (camera_select.IsValid()) {          for (int camera : camera_select) {              cameras[camera].frame_rate = frame_rate;              // TODO(wwylele): consider hinting the actual camera with the expected frame rate          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u", camera_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0); -      LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select=%u, frame_rate=%d",                  camera_select.m_val, static_cast<int>(frame_rate));  }  void SetEffect(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const CameraSet camera_select(cmd_buff[1]); -    const Effect effect = static_cast<Effect>(cmd_buff[2] & 0xFF); -    const ContextSet context_select(cmd_buff[3]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x22, 3, 0); +    const CameraSet camera_select(rp.Pop<u8>()); +    const Effect effect = static_cast<Effect>(rp.Pop<u8>()); +    const ContextSet context_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (camera_select.IsValid() && context_select.IsValid()) {          for (int camera : camera_select) {              for (int context : context_select) { @@ -883,26 +854,24 @@ void SetEffect(Service::Interface* self) {                  }              }          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,                    context_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0); -      LOG_DEBUG(Service_CAM, "called, camera_select=%u, effect=%d, context_select=%u",                camera_select.m_val, static_cast<int>(effect), context_select.m_val);  }  void SetOutputFormat(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    const CameraSet camera_select(cmd_buff[1]); -    const OutputFormat format = static_cast<OutputFormat>(cmd_buff[2] & 0xFF); -    const ContextSet context_select(cmd_buff[3]); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x25, 3, 0); +    const CameraSet camera_select(rp.Pop<u8>()); +    const OutputFormat format = static_cast<OutputFormat>(rp.Pop<u8>()); +    const ContextSet context_select(rp.Pop<u8>()); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      if (camera_select.IsValid() && context_select.IsValid()) {          for (int camera : camera_select) {              for (int context : context_select) { @@ -912,34 +881,32 @@ void SetOutputFormat(Service::Interface* self) {                  }              }          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        rb.Push(RESULT_SUCCESS);      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,                    context_select.m_val); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        rb.Push(ERROR_INVALID_ENUM_VALUE);      } -    cmd_buff[0] = IPC::MakeHeader(0x25, 1, 0); -      LOG_DEBUG(Service_CAM, "called, camera_select=%u, format=%d, context_select=%u",                camera_select.m_val, static_cast<int>(format), context_select.m_val);  }  void SynchronizeVsyncTiming(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x29, 2, 0); +    const u8 camera_select1 = rp.Pop<u8>(); +    const u8 camera_select2 = rp.Pop<u8>(); -    const u32 camera_select1 = cmd_buff[1] & 0xFF; -    const u32 camera_select2 = cmd_buff[2] & 0xFF; - -    cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS);      LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select1=%u, camera_select2=%u",                  camera_select1, camera_select2);  }  void GetStereoCameraCalibrationData(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestBuilder rb = +        IPC::RequestParser(Kernel::GetCommandBuffer(), 0x2B, 0, 0).MakeBuilder(17, 0);      // Default values taken from yuriks' 3DS. Valid data is required here or games using the      // calibration get stuck in an infinite CPU loop. @@ -958,34 +925,28 @@ void GetStereoCameraCalibrationData(Service::Interface* self) {      data.imageWidth = 640;      data.imageHeight = 480; -    cmd_buff[0] = IPC::MakeHeader(0x2B, 17, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    memcpy(&cmd_buff[2], &data, sizeof(data)); +    rb.Push(RESULT_SUCCESS); +    rb.PushRaw(data);      LOG_TRACE(Service_CAM, "called");  }  void SetPackageParameterWithoutContext(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x33, 11, 0);      PackageParameterWithoutContext package; -    std::memcpy(&package, cmd_buff + 1, sizeof(package)); +    rp.PopRaw(package); -    cmd_buff[0] = IPC::MakeHeader(0x33, 1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS);      LOG_WARNING(Service_CAM, "(STUBBED) called");  } -template <typename PackageParameterType, int command_id> -static void SetPackageParameter() { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    PackageParameterType package; -    std::memcpy(&package, cmd_buff + 1, sizeof(package)); - -    const CameraSet camera_select(static_cast<u32>(package.camera_select)); -    const ContextSet context_select(static_cast<u32>(package.context_select)); +template <typename PackageParameterType> +static ResultCode SetPackageParameter(const PackageParameterType& package) { +    const CameraSet camera_select(package.camera_select); +    const ContextSet context_select(package.context_select);      if (camera_select.IsValid() && context_select.IsValid()) {          for (int camera_id : camera_select) { @@ -1002,53 +963,66 @@ static void SetPackageParameter() {                  }              }          } -        cmd_buff[1] = RESULT_SUCCESS.raw; +        return RESULT_SUCCESS;      } else {          LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", package.camera_select,                    package.context_select); -        cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw; +        return ERROR_INVALID_ENUM_VALUE;      } - -    cmd_buff[0] = IPC::MakeHeader(command_id, 1, 0); - -    LOG_DEBUG(Service_CAM, "called");  } -Resolution PackageParameterWithContext::GetResolution() { +Resolution PackageParameterWithContext::GetResolution() const {      return PRESET_RESOLUTION[static_cast<int>(size)];  }  void SetPackageParameterWithContext(Service::Interface* self) { -    SetPackageParameter<PackageParameterWithContext, 0x34>(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x34, 5, 0); + +    PackageParameterWithContext package; +    rp.PopRaw(package); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    ResultCode result = SetPackageParameter(package); +    rb.Push(result); + +    LOG_DEBUG(Service_CAM, "called");  }  void SetPackageParameterWithContextDetail(Service::Interface* self) { -    SetPackageParameter<PackageParameterWithContextDetail, 0x35>(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x35, 7, 0); + +    PackageParameterWithContextDetail package; +    rp.PopRaw(package); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    ResultCode result = SetPackageParameter(package); +    rb.Push(result); + +    LOG_DEBUG(Service_CAM, "called");  }  void GetSuitableY2rStandardCoefficient(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    cmd_buff[0] = IPC::MakeHeader(0x36, 2, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; -    cmd_buff[2] = 0; +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x36, 0, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); +    rb.Push(RESULT_SUCCESS); +    rb.Push<u32>(0);      LOG_WARNING(Service_CAM, "(STUBBED) called");  }  void PlayShutterSound(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    u8 sound_id = cmd_buff[1] & 0xFF; +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x38, 1, 0); +    u8 sound_id = rp.Pop<u8>(); -    cmd_buff[0] = IPC::MakeHeader(0x38, 1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS);      LOG_WARNING(Service_CAM, "(STUBBED) called, sound_id=%d", sound_id);  }  void DriverInitialize(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x39, 0, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      for (int camera_id = 0; camera_id < NumCameras; ++camera_id) {          CameraConfig& camera = cameras[camera_id]; @@ -1074,14 +1048,14 @@ void DriverInitialize(Service::Interface* self) {          port.Clear();      } -    cmd_buff[0] = IPC::MakeHeader(0x39, 1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    rb.Push(RESULT_SUCCESS);      LOG_DEBUG(Service_CAM, "called");  }  void DriverFinalize(Service::Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3A, 0, 0); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);      CancelReceiving(0);      CancelReceiving(1); @@ -1090,8 +1064,7 @@ void DriverFinalize(Service::Interface* self) {          camera.impl = nullptr;      } -    cmd_buff[0] = IPC::MakeHeader(0x3A, 1, 0); -    cmd_buff[1] = RESULT_SUCCESS.raw; +    rb.Push(RESULT_SUCCESS);      LOG_DEBUG(Service_CAM, "called");  } diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h index 34a9c8479..b6da721d8 100644 --- a/src/core/hle/service/cam/cam.h +++ b/src/core/hle/service/cam/cam.h @@ -184,9 +184,10 @@ struct PackageParameterWithoutContext {      s16 auto_white_balance_window_y;      s16 auto_white_balance_window_width;      s16 auto_white_balance_window_height; +    INSERT_PADDING_WORDS(4);  }; -static_assert(sizeof(PackageParameterWithoutContext) == 28, +static_assert(sizeof(PackageParameterWithoutContext) == 44,                "PackageParameterCameraWithoutContext structure size is wrong");  struct PackageParameterWithContext { @@ -196,11 +197,12 @@ struct PackageParameterWithContext {      Effect effect;      Size size;      INSERT_PADDING_BYTES(3); +    INSERT_PADDING_WORDS(3); -    Resolution GetResolution(); +    Resolution GetResolution() const;  }; -static_assert(sizeof(PackageParameterWithContext) == 8, +static_assert(sizeof(PackageParameterWithContext) == 20,                "PackageParameterWithContext structure size is wrong");  struct PackageParameterWithContextDetail { @@ -209,13 +211,14 @@ struct PackageParameterWithContextDetail {      Flip flip;      Effect effect;      Resolution resolution; +    INSERT_PADDING_WORDS(3); -    Resolution GetResolution() { +    Resolution GetResolution() const {          return resolution;      }  }; -static_assert(sizeof(PackageParameterWithContextDetail) == 16, +static_assert(sizeof(PackageParameterWithContextDetail) == 28,                "PackageParameterWithContextDetail structure size is wrong");  /** diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp index eb04273db..bd9814244 100644 --- a/src/core/hle/service/cecd/cecd.cpp +++ b/src/core/hle/service/cecd/cecd.cpp @@ -3,7 +3,10 @@  // Refer to the license.txt file included.  #include "common/logging/log.h" +#include "core/hle/ipc.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/result.h"  #include "core/hle/service/cecd/cecd.h"  #include "core/hle/service/cecd/cecd_ndm.h"  #include "core/hle/service/cecd/cecd_s.h" diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index caa41ded7..5a7878b31 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -13,6 +13,8 @@  #include "core/file_sys/archive_systemsavedata.h"  #include "core/file_sys/errors.h"  #include "core/file_sys/file_backend.h" +#include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h"  #include "core/hle/result.h"  #include "core/hle/service/cfg/cfg.h"  #include "core/hle/service/cfg/cfg_i.h" @@ -21,6 +23,7 @@  #include "core/hle/service/cfg/cfg_u.h"  #include "core/hle/service/fs/archive.h"  #include "core/hle/service/service.h" +#include "core/memory.h"  #include "core/settings.h"  namespace Service { diff --git a/src/core/hle/service/csnd_snd.cpp b/src/core/hle/service/csnd_snd.cpp index 6cf62f9bc..1455f20ca 100644 --- a/src/core/hle/service/csnd_snd.cpp +++ b/src/core/hle/service/csnd_snd.cpp @@ -4,9 +4,12 @@  #include <cstring>  #include "common/alignment.h" +#include "core/hle/ipc.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/mutex.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/service/csnd_snd.h" +#include "core/memory.h"  namespace Service {  namespace CSND { diff --git a/src/core/hle/service/dlp/dlp_srvr.cpp b/src/core/hle/service/dlp/dlp_srvr.cpp index 25c07f401..32cfa2c44 100644 --- a/src/core/hle/service/dlp/dlp_srvr.cpp +++ b/src/core/hle/service/dlp/dlp_srvr.cpp @@ -4,6 +4,7 @@  #include "common/common_types.h"  #include "common/logging/log.h" +#include "core/hle/ipc.h"  #include "core/hle/result.h"  #include "core/hle/service/dlp/dlp_srvr.h" diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index 79171a0bc..363066d14 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -3,12 +3,18 @@  // Refer to the license.txt file included.  #include <algorithm> +#include <array>  #include <cinttypes>  #include "audio_core/hle/pipe.h" +#include "common/assert.h"  #include "common/hash.h"  #include "common/logging/log.h" +#include "core/hle/ipc.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/result.h"  #include "core/hle/service/dsp_dsp.h" +#include "core/memory.h"  using DspPipe = DSP::HLE::DspPipe; diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp index 9da55f328..7c8f4339f 100644 --- a/src/core/hle/service/err_f.cpp +++ b/src/core/hle/service/err_f.cpp @@ -6,10 +6,11 @@  #include <chrono>  #include <iomanip>  #include <sstream> -  #include "common/bit_field.h"  #include "common/common_types.h"  #include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/ipc.h"  #include "core/hle/result.h"  #include "core/hle/service/err_f.h" @@ -172,6 +173,7 @@ static void ThrowFatalError(Interface* self) {      const ErrInfo* errinfo = reinterpret_cast<ErrInfo*>(&cmd_buff[1]);      LOG_CRITICAL(Service_ERR, "Fatal error type: %s",                   GetErrType(errinfo->errinfo_common.specifier).c_str()); +    Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorUnknown);      // Generic Info      LogGenericInfo(errinfo->errinfo_common); diff --git a/src/core/hle/service/frd/frd.cpp b/src/core/hle/service/frd/frd.cpp index 34fdf7f53..76ecda8b7 100644 --- a/src/core/hle/service/frd/frd.cpp +++ b/src/core/hle/service/frd/frd.cpp @@ -2,11 +2,16 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include "common/assert.h" +#include "common/logging/log.h"  #include "common/string_util.h" +#include "core/hle/ipc.h" +#include "core/hle/result.h"  #include "core/hle/service/frd/frd.h"  #include "core/hle/service/frd/frd_a.h"  #include "core/hle/service/frd/frd_u.h"  #include "core/hle/service/service.h" +#include "core/memory.h"  namespace Service {  namespace FRD { diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 632712f2c..3605ef175 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -24,7 +24,11 @@  #include "core/file_sys/directory_backend.h"  #include "core/file_sys/errors.h"  #include "core/file_sys/file_backend.h" +#include "core/hle/ipc.h" +#include "core/hle/kernel/client_port.h"  #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/server_session.h"  #include "core/hle/result.h"  #include "core/hle/service/fs/archive.h"  #include "core/hle/service/fs/fs_user.h" @@ -83,6 +87,10 @@ File::File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path&  File::~File() {}  void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { +    using Kernel::ClientSession; +    using Kernel::ServerSession; +    using Kernel::SharedPtr; +      u32* cmd_buff = Kernel::GetCommandBuffer();      FileCommand cmd = static_cast<FileCommand>(cmd_buff[0]);      switch (cmd) { @@ -161,10 +169,9 @@ void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_ses      case FileCommand::OpenLinkFile: {          LOG_WARNING(Service_FS, "(STUBBED) File command OpenLinkFile %s", GetName().c_str()); -        auto sessions = Kernel::ServerSession::CreateSessionPair(GetName(), shared_from_this()); -        ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); -        cmd_buff[3] = Kernel::g_handle_table -                          .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) +        auto sessions = ServerSession::CreateSessionPair(GetName()); +        ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); +        cmd_buff[3] = Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions))                            .ValueOr(INVALID_HANDLE);          break;      } @@ -258,9 +265,7 @@ ResultVal<ArchiveHandle> OpenArchive(ArchiveIdCode id_code, FileSys::Path& archi      auto itr = id_code_map.find(id_code);      if (itr == id_code_map.end()) { -        // TODO: Verify error against hardware -        return ResultCode(ErrorDescription::NotFound, ErrorModule::FS, ErrorSummary::NotFound, -                          ErrorLevel::Permanent); +        return FileSys::ERROR_NOT_FOUND;      }      CASCADE_RESULT(std::unique_ptr<ArchiveBackend> res, itr->second->Open(archive_path)); diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 2ea956e0b..3a3371c88 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -8,7 +8,7 @@  #include <string>  #include "common/common_types.h"  #include "core/file_sys/archive_backend.h" -#include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/hle_ipc.h"  #include "core/hle/result.h"  namespace FileSys { @@ -43,7 +43,7 @@ enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 };  typedef u64 ArchiveHandle; -class File final : public SessionRequestHandler, public std::enable_shared_from_this<File> { +class File final : public Kernel::SessionRequestHandler {  public:      File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& path);      ~File(); @@ -60,7 +60,7 @@ protected:      void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override;  }; -class Directory final : public SessionRequestHandler { +class Directory final : public Kernel::SessionRequestHandler {  public:      Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, const FileSys::Path& path);      ~Directory(); diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index e53a970d3..34e1783ec 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -8,8 +8,13 @@  #include "common/logging/log.h"  #include "common/scope_exit.h"  #include "common/string_util.h" +#include "core/core.h"  #include "core/file_sys/errors.h" +#include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_port.h"  #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_session.h"  #include "core/hle/result.h"  #include "core/hle/service/fs/archive.h"  #include "core/hle/service/fs/fs_user.h" @@ -18,8 +23,9 @@  ////////////////////////////////////////////////////////////////////////////////////////////////////  // Namespace FS_User -using Kernel::SharedPtr; +using Kernel::ClientSession;  using Kernel::ServerSession; +using Kernel::SharedPtr;  namespace Service {  namespace FS { @@ -77,11 +83,11 @@ static void OpenFile(Service::Interface* self) {      rb.Push(file_res.Code());      if (file_res.Succeeded()) {          std::shared_ptr<File> file = *file_res; -        auto sessions = ServerSession::CreateSessionPair(file->GetName(), file); -        file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); -        rb.PushMoveHandles(Kernel::g_handle_table -                               .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) -                               .MoveFrom()); +        auto sessions = ServerSession::CreateSessionPair(file->GetName()); +        file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); + +        rb.PushMoveHandles( +            Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom());      } else {          rb.PushMoveHandles(0);          LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str()); @@ -130,7 +136,7 @@ static void OpenFileDirectly(Service::Interface* self) {      ResultVal<ArchiveHandle> archive_handle = OpenArchive(archive_id, archive_path);      if (archive_handle.Failed()) {          LOG_ERROR(Service_FS, -                  "failed to get a handle for archive archive_id=0x%08X archive_path=%s", +                  "Failed to get a handle for archive archive_id=0x%08X archive_path=%s",                    static_cast<u32>(archive_id), archive_path.DebugStr().c_str());          cmd_buff[1] = archive_handle.Code().raw;          cmd_buff[3] = 0; @@ -143,11 +149,11 @@ static void OpenFileDirectly(Service::Interface* self) {      cmd_buff[1] = file_res.Code().raw;      if (file_res.Succeeded()) {          std::shared_ptr<File> file = *file_res; -        auto sessions = ServerSession::CreateSessionPair(file->GetName(), file); -        file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); -        cmd_buff[3] = Kernel::g_handle_table -                          .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) -                          .MoveFrom(); +        auto sessions = ServerSession::CreateSessionPair(file->GetName()); +        file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); + +        cmd_buff[3] = +            Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom();      } else {          cmd_buff[3] = 0;          LOG_ERROR(Service_FS, "failed to get a handle for file %s mode=%u attributes=%u", @@ -410,11 +416,11 @@ static void OpenDirectory(Service::Interface* self) {      cmd_buff[1] = dir_res.Code().raw;      if (dir_res.Succeeded()) {          std::shared_ptr<Directory> directory = *dir_res; -        auto sessions = ServerSession::CreateSessionPair(directory->GetName(), directory); -        directory->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); -        cmd_buff[3] = Kernel::g_handle_table -                          .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) -                          .MoveFrom(); +        auto sessions = ServerSession::CreateSessionPair(directory->GetName()); +        directory->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); + +        cmd_buff[3] = +            Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom();      } else {          LOG_ERROR(Service_FS, "failed to get a handle for directory type=%d size=%d data=%s",                    dirname_type, dirname_size, dir_path.DebugStr().c_str()); diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 94f6b8a9c..6ff0f4812 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -5,7 +5,9 @@  #include "common/bit_field.h"  #include "common/microprofile.h"  #include "core/core.h" +#include "core/hle/ipc.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/result.h"  #include "core/hle/service/gsp_gpu.h" diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 64d01cdd7..5255f6dc8 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -10,7 +10,9 @@  #include "core/core_timing.h"  #include "core/frontend/emu_window.h"  #include "core/frontend/input.h" +#include "core/hle/ipc.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/service/hid/hid.h"  #include "core/hle/service/hid/hid_spvr.h" diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 53807cd91..0de698003 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -6,6 +6,7 @@  #include "common/bit_field.h"  #include "core/core_timing.h"  #include "core/frontend/input.h" +#include "core/hle/ipc_helpers.h"  #include "core/hle/kernel/event.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/service/hid/hid.h" diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index 369115f09..fdecdce64 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -7,6 +7,7 @@  #include <boost/optional.hpp>  #include "common/string_util.h"  #include "common/swap.h" +#include "core/hle/ipc_helpers.h"  #include "core/hle/kernel/event.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/service/ir/extra_hid.h" diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp index d1e6d869f..7255ea026 100644 --- a/src/core/hle/service/ldr_ro/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -7,6 +7,7 @@  #include "common/logging/log.h"  #include "core/arm/arm_interface.h"  #include "core/core.h" +#include "core/hle/ipc_helpers.h"  #include "core/hle/kernel/process.h"  #include "core/hle/kernel/vm_manager.h"  #include "core/hle/service/ldr_ro/cro_helper.h" diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index e98388560..35212b59b 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -3,7 +3,9 @@  // Refer to the license.txt file included.  #include "common/logging/log.h" +#include "core/hle/ipc.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/service/mic_u.h" diff --git a/src/core/hle/service/ndm/ndm.cpp b/src/core/hle/service/ndm/ndm.cpp index 5eb97f0d3..096c0cdac 100644 --- a/src/core/hle/service/ndm/ndm.cpp +++ b/src/core/hle/service/ndm/ndm.cpp @@ -2,8 +2,10 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <array>  #include "common/common_types.h"  #include "common/logging/log.h" +#include "core/hle/ipc.h"  #include "core/hle/service/ndm/ndm.h"  #include "core/hle/service/ndm/ndm_u.h"  #include "core/hle/service/service.h" diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index fd3c7d9c2..b44a9f668 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -2,7 +2,9 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include "core/hle/ipc.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/service/nfc/nfc.h"  #include "core/hle/service/nfc/nfc_m.h"  #include "core/hle/service/nfc/nfc_u.h" diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index 63c334cb2..d5624fe54 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -4,6 +4,7 @@  #include "common/common_types.h"  #include "common/logging/log.h" +#include "core/hle/ipc.h"  #include "core/hle/service/nim/nim.h"  #include "core/hle/service/nim/nim_aoc.h"  #include "core/hle/service/nim/nim_s.h" diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 581816e81..e92900d48 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -9,6 +9,7 @@  #include "common/common_types.h"  #include "common/logging/log.h"  #include "core/core_timing.h" +#include "core/hle/ipc_helpers.h"  #include "core/hle/kernel/event.h"  #include "core/hle/kernel/shared_memory.h"  #include "core/hle/result.h" @@ -214,6 +215,11 @@ static void GetConnectionStatus(Interface* self) {      rb.Push(RESULT_SUCCESS);      rb.PushRaw(connection_status); +    // Reset the bitmask of changed nodes after each call to this +    // function to prevent falsely informing games of outstanding +    // changes in subsequent calls. +    connection_status.changed_nodes = 0; +      LOG_DEBUG(Service_NWM, "called");  } @@ -313,8 +319,11 @@ static void BeginHostingNetwork(Interface* self) {      // The host is always the first node      connection_status.network_node_id = 1;      node_info[0].network_node_id = 1; +    connection_status.nodes[0] = connection_status.network_node_id;      // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken.      connection_status.node_bitmask |= 1; +    // Notify the application that the first node was set. +    connection_status.changed_nodes |= 1;      // If the game has a preferred channel, use that instead.      if (network_info.channel != 0) @@ -351,6 +360,8 @@ static void DestroyNetwork(Interface* self) {      // Unschedule the beacon broadcast event.      CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); +    // TODO(Subv): Check if connection_status is indeed reset after this call. +    connection_status = {};      connection_status.status = static_cast<u8>(NetworkStatus::NotConnected);      connection_status_event->Signal(); @@ -422,6 +433,102 @@ static void SetApplicationData(Interface* self) {      rb.Push(RESULT_SUCCESS);  } +/** + * NWM_UDS::DecryptBeaconData service function. + * Decrypts the encrypted data tags contained in the 802.11 beacons. + *  Inputs: + *      1 : Input network struct buffer descriptor. + *      2 : Input network struct buffer ptr. + *      3 : Input tag0 encrypted buffer descriptor. + *      4 : Input tag0 encrypted buffer ptr. + *      5 : Input tag1 encrypted buffer descriptor. + *      6 : Input tag1 encrypted buffer ptr. + *     64 : Output buffer descriptor. + *     65 : Output buffer ptr. + *  Outputs: + *      0 : Return header + *      1 : Result of function, 0 on success, otherwise error code + */ +static void DecryptBeaconData(Interface* self) { +    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1F, 0, 6); + +    size_t desc_size; +    const VAddr network_struct_addr = rp.PopStaticBuffer(&desc_size); +    ASSERT(desc_size == sizeof(NetworkInfo)); + +    size_t data0_size; +    const VAddr encrypted_data0_addr = rp.PopStaticBuffer(&data0_size); + +    size_t data1_size; +    const VAddr encrypted_data1_addr = rp.PopStaticBuffer(&data1_size); + +    size_t output_buffer_size; +    const VAddr output_buffer_addr = rp.PeekStaticBuffer(0, &output_buffer_size); + +    // This size is hardcoded in the 3DS UDS code. +    ASSERT(output_buffer_size == sizeof(NodeInfo) * UDSMaxNodes); + +    LOG_WARNING(Service_NWM, "called in0=%08X in1=%08X out=%08X", encrypted_data0_addr, +                encrypted_data1_addr, output_buffer_addr); + +    NetworkInfo net_info; +    Memory::ReadBlock(network_struct_addr, &net_info, sizeof(net_info)); + +    // Read the encrypted data. +    // The first 4 bytes should be the OUI and the OUI Type of the tags. +    std::array<u8, 3> oui; +    Memory::ReadBlock(encrypted_data0_addr, oui.data(), oui.size()); +    ASSERT_MSG(oui == NintendoOUI, "Unexpected OUI"); +    Memory::ReadBlock(encrypted_data1_addr, oui.data(), oui.size()); +    ASSERT_MSG(oui == NintendoOUI, "Unexpected OUI"); + +    ASSERT_MSG(Memory::Read8(encrypted_data0_addr + 3) == +                   static_cast<u8>(NintendoTagId::EncryptedData0), +               "Unexpected tag id"); +    ASSERT_MSG(Memory::Read8(encrypted_data1_addr + 3) == +                   static_cast<u8>(NintendoTagId::EncryptedData1), +               "Unexpected tag id"); + +    std::vector<u8> beacon_data(data0_size + data1_size); +    Memory::ReadBlock(encrypted_data0_addr + 4, beacon_data.data(), data0_size); +    Memory::ReadBlock(encrypted_data1_addr + 4, beacon_data.data() + data0_size, data1_size); + +    // Decrypt the data +    DecryptBeaconData(net_info, beacon_data); + +    // The beacon data header contains the MD5 hash of the data. +    BeaconData beacon_header; +    std::memcpy(&beacon_header, beacon_data.data(), sizeof(beacon_header)); + +    // TODO(Subv): Verify the MD5 hash of the data and return 0xE1211005 if invalid. + +    u8 num_nodes = net_info.max_nodes; + +    std::vector<NodeInfo> nodes; + +    for (int i = 0; i < num_nodes; ++i) { +        BeaconNodeInfo info; +        std::memcpy(&info, beacon_data.data() + sizeof(beacon_header) + i * sizeof(info), +                    sizeof(info)); + +        // Deserialize the node information. +        NodeInfo node{}; +        node.friend_code_seed = info.friend_code_seed; +        node.network_node_id = info.network_node_id; +        for (int i = 0; i < info.username.size(); ++i) +            node.username[i] = info.username[i]; + +        nodes.push_back(node); +    } + +    Memory::ZeroBlock(output_buffer_addr, sizeof(NodeInfo) * UDSMaxNodes); +    Memory::WriteBlock(output_buffer_addr, nodes.data(), sizeof(NodeInfo) * nodes.size()); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); +    rb.PushStaticBuffer(output_buffer_addr, output_buffer_size, 0); +    rb.Push(RESULT_SUCCESS); +} +  // Sends a 802.11 beacon frame with information about the current network.  static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {      // Don't do anything if we're not actually hosting a network @@ -462,7 +569,7 @@ const Interface::FunctionInfo FunctionTable[] = {      {0x001B0302, InitializeWithVersion, "InitializeWithVersion"},      {0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"},      {0x001E0084, nullptr, "ConnectToNetwork"}, -    {0x001F0006, nullptr, "DecryptBeaconData"}, +    {0x001F0006, DecryptBeaconData, "DecryptBeaconData"},      {0x00200040, nullptr, "Flush"},      {0x00210080, nullptr, "SetProbeResponseParam"},      {0x00220402, nullptr, "ScanOnConnection"}, diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 29b146569..141f49f9c 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -24,6 +24,9 @@ const double MillisecondsPerTU = 1.024;  // Interval measured in TU, the default value is 100TU = 102.4ms  const u16 DefaultBeaconInterval = 100; +/// The maximum number of nodes that can exist in an UDS session. +constexpr u32 UDSMaxNodes = 16; +  struct NodeInfo {      u64_le friend_code_seed;      std::array<u16_le, 10> username; @@ -47,8 +50,8 @@ struct ConnectionStatus {      u32_le status;      INSERT_PADDING_WORDS(1);      u16_le network_node_id; -    INSERT_PADDING_BYTES(2); -    INSERT_PADDING_BYTES(32); +    u16_le changed_nodes; +    u16_le nodes[UDSMaxNodes];      u8 total_nodes;      u8 max_nodes;      u16_le node_bitmask; diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp index c6e5bc5f1..6332b404c 100644 --- a/src/core/hle/service/nwm/uds_beacon.cpp +++ b/src/core/hle/service/nwm/uds_beacon.cpp @@ -3,14 +3,13 @@  // Refer to the license.txt file included.  #include <cstring> - -#include "core/hle/service/nwm/nwm_uds.h" -#include "core/hle/service/nwm/uds_beacon.h" -  #include <cryptopp/aes.h>  #include <cryptopp/md5.h>  #include <cryptopp/modes.h>  #include <cryptopp/sha.h> +#include "common/assert.h" +#include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_beacon.h"  namespace Service {  namespace NWM { diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h index 6df4c4f47..caacf4c6f 100644 --- a/src/core/hle/service/nwm/uds_beacon.h +++ b/src/core/hle/service/nwm/uds_beacon.h @@ -15,9 +15,6 @@ namespace Service {  namespace NWM {  using MacAddress = std::array<u8, 6>; - -/// The maximum number of nodes that can exist in an UDS session. -constexpr u32 UDSMaxNodes = 16;  constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32};  /// Additional block tag ids in the Beacon frames diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 0672ac2e3..791a65c19 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -2,11 +2,16 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. -#include <boost/range/algorithm_ext/erase.hpp> - +#include <algorithm> +#include <fmt/format.h> +#include "common/assert.h"  #include "common/logging/log.h"  #include "common/string_util.h" +#include "core/hle/ipc.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/process.h"  #include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/server_session.h"  #include "core/hle/service/ac/ac.h"  #include "core/hle/service/act/act.h"  #include "core/hle/service/am/am.h" @@ -39,15 +44,20 @@  #include "core/hle/service/ptm/ptm.h"  #include "core/hle/service/qtm/qtm.h"  #include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" +#include "core/hle/service/sm/srv.h"  #include "core/hle/service/soc_u.h" -#include "core/hle/service/srv.h"  #include "core/hle/service/ssl_c.h"  #include "core/hle/service/y2r_u.h" +using Kernel::ClientPort; +using Kernel::ServerPort; +using Kernel::ServerSession; +using Kernel::SharedPtr; +  namespace Service { -std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports; -std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_srv_services; +std::unordered_map<std::string, SharedPtr<ClientPort>> g_kernel_named_ports;  /**   * Creates a function string for logging, complete with the name (or header code, depending @@ -66,20 +76,10 @@ static std::string MakeFunctionString(const char* name, const char* port_name,      return function_string;  } -void SessionRequestHandler::ClientConnected( -    Kernel::SharedPtr<Kernel::ServerSession> server_session) { -    connected_sessions.push_back(server_session); -} - -void SessionRequestHandler::ClientDisconnected( -    Kernel::SharedPtr<Kernel::ServerSession> server_session) { -    boost::range::remove_erase(connected_sessions, server_session); -} -  Interface::Interface(u32 max_sessions) : max_sessions(max_sessions) {}  Interface::~Interface() = default; -void Interface::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { +void Interface::HandleSyncRequest(SharedPtr<ServerSession> server_session) {      // TODO(Subv): Make use of the server_session in the HLE service handlers to distinguish which      // session triggered each command. @@ -113,27 +113,109 @@ void Interface::Register(const FunctionInfo* functions, size_t n) {  }  //////////////////////////////////////////////////////////////////////////////////////////////////// + +ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions, +                                           InvokerFn* handler_invoker) +    : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {} + +ServiceFrameworkBase::~ServiceFrameworkBase() = default; + +void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { +    ASSERT(port == nullptr); +    port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); +    port->SetHleHandler(shared_from_this()); +} + +void ServiceFrameworkBase::InstallAsNamedPort() { +    ASSERT(port == nullptr); +    SharedPtr<ServerPort> server_port; +    SharedPtr<ClientPort> client_port; +    std::tie(server_port, client_port) = ServerPort::CreatePortPair(max_sessions, service_name); +    server_port->SetHleHandler(shared_from_this()); +    AddNamedPort(service_name, std::move(client_port)); +} + +void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, size_t n) { +    handlers.reserve(handlers.size() + n); +    for (size_t i = 0; i < n; ++i) { +        // Usually this array is sorted by id already, so hint to insert at the end +        handlers.emplace_hint(handlers.cend(), functions[i].expected_header, functions[i]); +    } +} + +void ServiceFrameworkBase::ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info) { +    IPC::Header header{cmd_buf[0]}; +    int num_params = header.normal_params_size + header.translate_params_size; +    std::string function_name = info == nullptr ? fmt::format("{:#08x}", cmd_buf[0]) : info->name; + +    fmt::MemoryWriter w; +    w.write("function '{}': port='{}' cmd_buf={{[0]={:#x}", function_name, service_name, +            cmd_buf[0]); +    for (int i = 1; i <= num_params; ++i) { +        w.write(", [{}]={:#x}", i, cmd_buf[i]); +    } +    w << '}'; + +    LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str()); +    // TODO(bunnei): Hack - ignore error +    cmd_buf[1] = 0; +} + +void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_session) { +    u32* cmd_buf = Kernel::GetCommandBuffer(); + +    u32 header_code = cmd_buf[0]; +    auto itr = handlers.find(header_code); +    const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second; +    if (info == nullptr || info->handler_callback == nullptr) { +        return ReportUnimplementedFunction(cmd_buf, info); +    } + +    // TODO(yuriks): The kernel should be the one handling this as part of translation after +    // everything else is migrated +    Kernel::HLERequestContext context; +    context.session = std::move(server_session); +    context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process, +                                              Kernel::g_handle_table); + +    LOG_TRACE(Service, "%s", +              MakeFunctionString(info->name, GetServiceName().c_str(), cmd_buf).c_str()); +    handler_invoker(this, info->handler_callback, context); +    context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process, +                                         Kernel::g_handle_table); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////  // Module interface +// TODO(yuriks): Move to kernel +void AddNamedPort(std::string name, SharedPtr<ClientPort> port) { +    g_kernel_named_ports.emplace(std::move(name), std::move(port)); +} +  static void AddNamedPort(Interface* interface_) { -    auto ports = -        Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName(), -                                           std::shared_ptr<Interface>(interface_)); -    auto client_port = std::get<Kernel::SharedPtr<Kernel::ClientPort>>(ports); -    g_kernel_named_ports.emplace(interface_->GetPortName(), std::move(client_port)); +    SharedPtr<ServerPort> server_port; +    SharedPtr<ClientPort> client_port; +    std::tie(server_port, client_port) = +        ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName()); + +    server_port->SetHleHandler(std::shared_ptr<Interface>(interface_)); +    AddNamedPort(interface_->GetPortName(), std::move(client_port));  }  void AddService(Interface* interface_) { -    auto ports = -        Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName(), -                                           std::shared_ptr<Interface>(interface_)); -    auto client_port = std::get<Kernel::SharedPtr<Kernel::ClientPort>>(ports); -    g_srv_services.emplace(interface_->GetPortName(), std::move(client_port)); +    auto server_port = +        SM::g_service_manager +            ->RegisterService(interface_->GetPortName(), interface_->GetMaxSessions()) +            .MoveFrom(); +    server_port->SetHleHandler(std::shared_ptr<Interface>(interface_));  }  /// Initialize ServiceManager  void Init() { -    AddNamedPort(new SRV::SRV); +    SM::g_service_manager = std::make_shared<SM::ServiceManager>(); +    SM::ServiceManager::InstallInterfaces(SM::g_service_manager); +      AddNamedPort(new ERR::ERR_F);      FS::ArchiveInit(); @@ -194,7 +276,7 @@ void Shutdown() {      AC::Shutdown();      FS::ArchiveShutdown(); -    g_srv_services.clear(); +    SM::g_service_manager = nullptr;      g_kernel_named_ports.clear();      LOG_DEBUG(Service, "shutdown OK");  } diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index e6a5f1417..281ff99bb 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -8,70 +8,38 @@  #include <string>  #include <unordered_map>  #include <boost/container/flat_map.hpp> +#include "common/bit_field.h"  #include "common/common_types.h" -#include "core/hle/ipc.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/result.h" -#include "core/memory.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace Service  namespace Kernel { +class ClientPort; +class ServerPort;  class ServerSession;  } -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace Service -  namespace Service { +namespace SM { +class ServiceManager; +} +  static const int kMaxPortSize = 8; ///< Maximum size of a port name (8 characters)  /// Arbitrary default number of maximum connections to an HLE service.  static const u32 DefaultMaxSessions = 10;  /** - * Interface implemented by HLE Session handlers. - * This can be provided to a ServerSession in order to hook into several relevant events - * (such as a new connection or a SyncRequest) so they can be implemented in the emulator. - */ -class SessionRequestHandler { -public: -    /** -     * Handles a sync request from the emulated application. -     * @param server_session The ServerSession that was triggered for this sync request, -     * it should be used to differentiate which client (As in ClientSession) we're answering to. -     * TODO(Subv): Use a wrapper structure to hold all the information relevant to -     * this request (ServerSession, Originator thread, Translated command buffer, etc). -     * @returns ResultCode the result code of the translate operation. -     */ -    virtual void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) = 0; - -    /** -     * Signals that a client has just connected to this HLE handler and keeps the -     * associated ServerSession alive for the duration of the connection. -     * @param server_session Owning pointer to the ServerSession associated with the connection. -     */ -    void ClientConnected(Kernel::SharedPtr<Kernel::ServerSession> server_session); - -    /** -     * Signals that a client has just disconnected from this HLE handler and releases the -     * associated ServerSession. -     * @param server_session ServerSession associated with the connection. -     */ -    void ClientDisconnected(Kernel::SharedPtr<Kernel::ServerSession> server_session); - -protected: -    /// List of sessions that are connected to this handler. -    /// A ServerSession whose server endpoint is an HLE implementation is kept alive by this list -    // for the duration of the connection. -    std::vector<Kernel::SharedPtr<Kernel::ServerSession>> connected_sessions; -}; - -/**   * Framework for implementing HLE service handlers which dispatch incoming SyncRequests based on a   * table mapping header ids to handler functions. + * + * @deprecated Use ServiceFramework for new services instead. It allows services to be stateful and + *     is more extensible going forward.   */ -class Interface : public SessionRequestHandler { +class Interface : public Kernel::SessionRequestHandler {  public:      /**       * Creates an HLE interface with the specified max sessions. @@ -141,6 +109,146 @@ private:      boost::container::flat_map<u32, FunctionInfo> m_functions;  }; +/** + * This is an non-templated base of ServiceFramework to reduce code bloat and compilation times, it + * is not meant to be used directly. + * + * @see ServiceFramework + */ +class ServiceFrameworkBase : public Kernel::SessionRequestHandler { +public: +    /// Returns the string identifier used to connect to the service. +    std::string GetServiceName() const { +        return service_name; +    } + +    /** +     * Returns the maximum number of sessions that can be connected to this service at the same +     * time. +     */ +    u32 GetMaxSessions() const { +        return max_sessions; +    } + +    /// Creates a port pair and registers this service with the given ServiceManager. +    void InstallAsService(SM::ServiceManager& service_manager); +    /// Creates a port pair and registers it on the kernel's global port registry. +    void InstallAsNamedPort(); + +    void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; + +protected: +    /// Member-function pointer type of SyncRequest handlers. +    template <typename Self> +    using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&); + +private: +    template <typename T> +    friend class ServiceFramework; + +    struct FunctionInfoBase { +        u32 expected_header; +        HandlerFnP<ServiceFrameworkBase> handler_callback; +        const char* name; +    }; + +    using InvokerFn = void(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member, +                           Kernel::HLERequestContext& ctx); + +    ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker); +    ~ServiceFrameworkBase(); + +    void RegisterHandlersBase(const FunctionInfoBase* functions, size_t n); +    void ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info); + +    /// Identifier string used to connect to the service. +    std::string service_name; +    /// Maximum number of concurrent sessions that this service can handle. +    u32 max_sessions; + +    /** +     * Port where incoming connections will be received. Only created when InstallAsService() or +     * InstallAsNamedPort() are called. +     */ +    Kernel::SharedPtr<Kernel::ServerPort> port; + +    /// Function used to safely up-cast pointers to the derived class before invoking a handler. +    InvokerFn* handler_invoker; +    boost::container::flat_map<u32, FunctionInfoBase> handlers; +}; + +/** + * Framework for implementing HLE services. Dispatches on the header id of incoming SyncRequests + * based on a table mapping header ids to handler functions. Service implementations should inherit + * from ServiceFramework using the CRTP (`class Foo : public ServiceFramework<Foo> { ... };`) and + * populate it with handlers by calling #RegisterHandlers. + * + * In order to avoid duplicating code in the binary and exposing too many implementation details in + * the header, this class is split into a non-templated base (ServiceFrameworkBase) and a template + * deriving from it (ServiceFramework). The functions in this class will mostly only erase the type + * of the passed in function pointers and then delegate the actual work to the implementation in the + * base class. + */ +template <typename Self> +class ServiceFramework : public ServiceFrameworkBase { +protected: +    /// Contains information about a request type which is handled by the service. +    struct FunctionInfo : FunctionInfoBase { +        // TODO(yuriks): This function could be constexpr, but clang is the only compiler that +        // doesn't emit an ICE or a wrong diagnostic because of the static_cast. + +        /** +         * Constructs a FunctionInfo for a function. +         * +         * @param expected_header request header in the command buffer which will trigger dispatch +         *     to this handler +         * @param handler_callback member function in this service which will be called to handle +         *     the request +         * @param name human-friendly name for the request. Used mostly for logging purposes. +         */ +        FunctionInfo(u32 expected_header, HandlerFnP<Self> handler_callback, const char* name) +            : FunctionInfoBase{ +                  expected_header, +                  // Type-erase member function pointer by casting it down to the base class. +                  static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback), name} {} +    }; + +    /** +     * Initializes the handler with no functions installed. +     * @param max_sessions Maximum number of sessions that can be +     * connected to this service at the same time. +     */ +    ServiceFramework(const char* service_name, u32 max_sessions = DefaultMaxSessions) +        : ServiceFrameworkBase(service_name, max_sessions, Invoker) {} + +    /// Registers handlers in the service. +    template <size_t N> +    void RegisterHandlers(const FunctionInfo (&functions)[N]) { +        RegisterHandlers(functions, N); +    } + +    /** +     * Registers handlers in the service. Usually prefer using the other RegisterHandlers +     * overload in order to avoid needing to specify the array size. +     */ +    void RegisterHandlers(const FunctionInfo* functions, size_t n) { +        RegisterHandlersBase(functions, n); +    } + +private: +    /** +     * This function is used to allow invocation of pointers to handlers stored in the base class +     * without needing to expose the type of this derived class. Pointers-to-member may require a +     * fixup when being up or downcast, and thus code that does that needs to know the concrete type +     * of the derived class in order to invoke one of it's functions through a pointer. +     */ +    static void Invoker(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member, +                        Kernel::HLERequestContext& ctx) { +        // Cast back up to our original types and call the member function +        (static_cast<Self*>(object)->*static_cast<HandlerFnP<Self>>(member))(ctx); +    } +}; +  /// Initialize ServiceManager  void Init(); @@ -149,9 +257,9 @@ void Shutdown();  /// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort SVC.  extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports; -/// Map of services registered with the "srv:" service, retrieved using GetServiceHandle. -extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_srv_services; +/// Adds a port to the named port table +void AddNamedPort(std::string name, Kernel::SharedPtr<Kernel::ClientPort> port);  /// Adds a service to the services table  void AddService(Interface* interface_); diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp new file mode 100644 index 000000000..5e7fc68f9 --- /dev/null +++ b/src/core/hle/service/sm/sm.cpp @@ -0,0 +1,69 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <tuple> +#include "common/assert.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_port.h" +#include "core/hle/result.h" +#include "core/hle/service/sm/sm.h" +#include "core/hle/service/sm/srv.h" + +namespace Service { +namespace SM { + +static ResultCode ValidateServiceName(const std::string& name) { +    if (name.size() <= 0 || name.size() > 8) { +        return ERR_INVALID_NAME_SIZE; +    } +    if (name.find('\0') != std::string::npos) { +        return ERR_NAME_CONTAINS_NUL; +    } +    return RESULT_SUCCESS; +} + +void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self) { +    ASSERT(self->srv_interface.expired()); + +    auto srv = std::make_shared<SRV>(self); +    srv->InstallAsNamedPort(); +    self->srv_interface = srv; +} + +ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService( +    std::string name, unsigned int max_sessions) { + +    CASCADE_CODE(ValidateServiceName(name)); +    Kernel::SharedPtr<Kernel::ServerPort> server_port; +    Kernel::SharedPtr<Kernel::ClientPort> client_port; +    std::tie(server_port, client_port) = Kernel::ServerPort::CreatePortPair(max_sessions, name); + +    registered_services.emplace(std::move(name), std::move(client_port)); +    return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)); +} + +ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> ServiceManager::GetServicePort( +    const std::string& name) { + +    CASCADE_CODE(ValidateServiceName(name)); +    auto it = registered_services.find(name); +    if (it == registered_services.end()) { +        return ERR_SERVICE_NOT_REGISTERED; +    } + +    return MakeResult<Kernel::SharedPtr<Kernel::ClientPort>>(it->second); +} + +ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToService( +    const std::string& name) { + +    CASCADE_RESULT(auto client_port, GetServicePort(name)); +    return client_port->Connect(); +} + +std::shared_ptr<ServiceManager> g_service_manager; + +} // namespace SM +} // namespace Service diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h new file mode 100644 index 000000000..8f0dbf2db --- /dev/null +++ b/src/core/hle/service/sm/sm.h @@ -0,0 +1,55 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <unordered_map> +#include "core/hle/kernel/kernel.h" +#include "core/hle/result.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class ClientPort; +class ClientSession; +class ServerPort; +class SessionRequestHandler; +} // namespace Kernel + +namespace Service { +namespace SM { + +class SRV; + +constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(1, ErrorModule::SRV, ErrorSummary::WouldBlock, +                                                ErrorLevel::Temporary); // 0xD0406401 +constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(2, ErrorModule::SRV, ErrorSummary::WouldBlock, +                                                 ErrorLevel::Temporary); // 0xD0406402 +constexpr ResultCode ERR_INVALID_NAME_SIZE(5, ErrorModule::SRV, ErrorSummary::WrongArgument, +                                           ErrorLevel::Permanent); // 0xD9006405 +constexpr ResultCode ERR_ACCESS_DENIED(6, ErrorModule::SRV, ErrorSummary::InvalidArgument, +                                       ErrorLevel::Permanent); // 0xD8E06406 +constexpr ResultCode ERR_NAME_CONTAINS_NUL(7, ErrorModule::SRV, ErrorSummary::WrongArgument, +                                           ErrorLevel::Permanent); // 0xD9006407 + +class ServiceManager { +public: +    static void InstallInterfaces(std::shared_ptr<ServiceManager> self); + +    ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name, +                                                                     unsigned int max_sessions); +    ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name); +    ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name); + +private: +    std::weak_ptr<SRV> srv_interface; + +    /// Map of registered services, retrieved using GetServicePort or ConnectToService. +    std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> registered_services; +}; + +extern std::shared_ptr<ServiceManager> g_service_manager; + +} // namespace SM +} // namespace Service diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp new file mode 100644 index 000000000..74a1256e0 --- /dev/null +++ b/src/core/hle/service/sm/srv.cpp @@ -0,0 +1,211 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <tuple> + +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/semaphore.h" +#include "core/hle/kernel/server_session.h" +#include "core/hle/service/sm/sm.h" +#include "core/hle/service/sm/srv.h" + +namespace Service { +namespace SM { + +constexpr int MAX_PENDING_NOTIFICATIONS = 16; + +/** + * SRV::RegisterClient service function + *  Inputs: + *      0: 0x00010002 + *      1: ProcessId Header (must be 0x20) + *  Outputs: + *      0: 0x00010040 + *      1: ResultCode + */ +void SRV::RegisterClient(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp(ctx, 0x1, 0, 2); + +    u32 pid_descriptor = rp.Pop<u32>(); +    if (pid_descriptor != IPC::CallingPidDesc()) { +        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +        rb.Push(IPC::ERR_INVALID_BUFFER_DESCRIPTOR); +        return; +    } +    u32 caller_pid = rp.Pop<u32>(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); +    LOG_WARNING(Service_SRV, "(STUBBED) called"); +} + +/** + * SRV::EnableNotification service function + *  Inputs: + *      0: 0x00020000 + *  Outputs: + *      0: 0x00020042 + *      1: ResultCode + *      2: Translation descriptor: 0x20 + *      3: Handle to semaphore signaled on process notification + */ +void SRV::EnableNotification(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp(ctx, 0x2, 0, 0); + +    notification_semaphore = +        Kernel::Semaphore::Create(0, MAX_PENDING_NOTIFICATIONS, "SRV:Notification").Unwrap(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); +    rb.Push(RESULT_SUCCESS); +    rb.PushObjects(notification_semaphore); +    LOG_WARNING(Service_SRV, "(STUBBED) called"); +} + +/** + * SRV::GetServiceHandle service function + *  Inputs: + *      0: 0x00050100 + *      1-2: 8-byte UTF-8 service name + *      3: Name length + *      4: Flags (bit0: if not set, return port-handle if session-handle unavailable) + *  Outputs: + *      1: ResultCode + *      3: Service handle + */ +void SRV::GetServiceHandle(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp(ctx, 0x5, 4, 0); +    auto name_buf = rp.PopRaw<std::array<char, 8>>(); +    size_t name_len = rp.Pop<u32>(); +    u32 flags = rp.Pop<u32>(); + +    bool return_port_on_failure = (flags & 1) == 0; + +    if (name_len > Service::kMaxPortSize) { +        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +        rb.Push(ERR_INVALID_NAME_SIZE); +        LOG_ERROR(Service_SRV, "called name_len=0x%X -> ERR_INVALID_NAME_SIZE", name_len); +        return; +    } +    std::string name(name_buf.data(), name_len); + +    // TODO(yuriks): Permission checks go here + +    auto client_port = service_manager->GetServicePort(name); +    if (client_port.Failed()) { +        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +        rb.Push(client_port.Code()); +        LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), +                  client_port.Code().raw); +        return; +    } + +    auto session = client_port.Unwrap()->Connect(); +    if (session.Succeeded()) { +        LOG_DEBUG(Service_SRV, "called service=%s -> session=%u", name.c_str(), +                  (*session)->GetObjectId()); +        IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); +        rb.Push(session.Code()); +        rb.PushObjects(session.MoveFrom()); +    } else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED && return_port_on_failure) { +        LOG_WARNING(Service_SRV, "called service=%s -> ERR_MAX_CONNECTIONS_REACHED, *port*=%u", +                    name.c_str(), (*client_port)->GetObjectId()); +        IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); +        rb.Push(ERR_MAX_CONNECTIONS_REACHED); +        rb.PushObjects(client_port.MoveFrom()); +    } else { +        LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), session.Code()); +        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +        rb.Push(session.Code()); +    } +} + +/** + * SRV::Subscribe service function + *  Inputs: + *      0: 0x00090040 + *      1: Notification ID + *  Outputs: + *      0: 0x00090040 + *      1: ResultCode + */ +void SRV::Subscribe(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp(ctx, 0x9, 1, 0); +    u32 notification_id = rp.Pop<u32>(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); +    LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id); +} + +/** + * SRV::Unsubscribe service function + *  Inputs: + *      0: 0x000A0040 + *      1: Notification ID + *  Outputs: + *      0: 0x000A0040 + *      1: ResultCode + */ +void SRV::Unsubscribe(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp(ctx, 0xA, 1, 0); +    u32 notification_id = rp.Pop<u32>(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); +    LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id); +} + +/** + * SRV::PublishToSubscriber service function + *  Inputs: + *      0: 0x000C0080 + *      1: Notification ID + *      2: Flags (bit0: only fire if not fired, bit1: report errors) + *  Outputs: + *      0: 0x000C0040 + *      1: ResultCode + */ +void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) { +    IPC::RequestParser rp(ctx, 0xC, 2, 0); +    u32 notification_id = rp.Pop<u32>(); +    u8 flags = rp.Pop<u8>(); + +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +    rb.Push(RESULT_SUCCESS); +    LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X, flags=%u", notification_id, +                flags); +} + +SRV::SRV(std::shared_ptr<ServiceManager> service_manager) +    : ServiceFramework("srv:", 4), service_manager(std::move(service_manager)) { +    static const FunctionInfo functions[] = { +        {0x00010002, &SRV::RegisterClient, "RegisterClient"}, +        {0x00020000, &SRV::EnableNotification, "EnableNotification"}, +        {0x00030100, nullptr, "RegisterService"}, +        {0x000400C0, nullptr, "UnregisterService"}, +        {0x00050100, &SRV::GetServiceHandle, "GetServiceHandle"}, +        {0x000600C2, nullptr, "RegisterPort"}, +        {0x000700C0, nullptr, "UnregisterPort"}, +        {0x00080100, nullptr, "GetPort"}, +        {0x00090040, &SRV::Subscribe, "Subscribe"}, +        {0x000A0040, &SRV::Unsubscribe, "Unsubscribe"}, +        {0x000B0000, nullptr, "ReceiveNotification"}, +        {0x000C0080, &SRV::PublishToSubscriber, "PublishToSubscriber"}, +        {0x000D0040, nullptr, "PublishAndGetSubscriber"}, +        {0x000E00C0, nullptr, "IsServiceRegistered"}, +    }; +    RegisterHandlers(functions); +} + +SRV::~SRV() = default; + +} // namespace SM +} // namespace Service diff --git a/src/core/hle/service/sm/srv.h b/src/core/hle/service/sm/srv.h new file mode 100644 index 000000000..75cca5184 --- /dev/null +++ b/src/core/hle/service/sm/srv.h @@ -0,0 +1,37 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/kernel.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class HLERequestContext; +class Semaphore; +} + +namespace Service { +namespace SM { + +/// Interface to "srv:" service +class SRV final : public ServiceFramework<SRV> { +public: +    explicit SRV(std::shared_ptr<ServiceManager> service_manager); +    ~SRV(); + +private: +    void RegisterClient(Kernel::HLERequestContext& ctx); +    void EnableNotification(Kernel::HLERequestContext& ctx); +    void GetServiceHandle(Kernel::HLERequestContext& ctx); +    void Subscribe(Kernel::HLERequestContext& ctx); +    void Unsubscribe(Kernel::HLERequestContext& ctx); +    void PublishToSubscriber(Kernel::HLERequestContext& ctx); + +    std::shared_ptr<ServiceManager> service_manager; +    Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore; +}; + +} // namespace SM +} // namespace Service diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index 530614e6f..3d215d42d 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -11,6 +11,7 @@  #include "common/common_types.h"  #include "common/logging/log.h"  #include "common/scope_exit.h" +#include "core/hle/ipc.h"  #include "core/hle/kernel/server_session.h"  #include "core/hle/result.h"  #include "core/hle/service/soc_u.h" diff --git a/src/core/hle/service/srv.cpp b/src/core/hle/service/srv.cpp deleted file mode 100644 index 130c9d25e..000000000 --- a/src/core/hle/service/srv.cpp +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <tuple> - -#include "common/common_types.h" -#include "common/logging/log.h" -#include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/server_session.h" -#include "core/hle/service/srv.h" - -namespace Service { -namespace SRV { - -static Kernel::SharedPtr<Kernel::Event> event_handle; - -/** - * SRV::RegisterClient service function - *  Inputs: - *      0: 0x00010002 - *      1: ProcessId Header (must be 0x20) - *  Outputs: - *      0: 0x00010040 - *      1: ResultCode - */ -static void RegisterClient(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    if (cmd_buff[1] != IPC::CallingPidDesc()) { -        cmd_buff[0] = IPC::MakeHeader(0x0, 0x1, 0); // 0x40 -        cmd_buff[1] = IPC::ERR_INVALID_BUFFER_DESCRIPTOR.raw; -        return; -    } -    cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); // 0x10040 -    cmd_buff[1] = RESULT_SUCCESS.raw;           // No error -    LOG_WARNING(Service_SRV, "(STUBBED) called"); -} - -/** - * SRV::EnableNotification service function - *  Inputs: - *      0: 0x00020000 - *  Outputs: - *      0: 0x00020042 - *      1: ResultCode - *      2: Translation descriptor: 0x20 - *      3: Handle to semaphore signaled on process notification - */ -static void EnableNotification(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    // TODO(bunnei): Change to a semaphore once these have been implemented -    event_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "SRV:Event"); -    event_handle->Clear(); - -    cmd_buff[0] = IPC::MakeHeader(0x2, 0x1, 0x2); // 0x20042 -    cmd_buff[1] = RESULT_SUCCESS.raw;             // No error -    cmd_buff[2] = IPC::CopyHandleDesc(1); -    cmd_buff[3] = Kernel::g_handle_table.Create(event_handle).MoveFrom(); -    LOG_WARNING(Service_SRV, "(STUBBED) called"); -} - -/** - * SRV::GetServiceHandle service function - *  Inputs: - *      0: 0x00050100 - *      1-2: 8-byte UTF-8 service name - *      3: Name length - *      4: Flags (bit0: if not set, return port-handle if session-handle unavailable) - *  Outputs: - *      1: ResultCode - *      3: Service handle - */ -static void GetServiceHandle(Interface* self) { -    ResultCode res = RESULT_SUCCESS; -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    std::string port_name = std::string((const char*)&cmd_buff[1], 0, Service::kMaxPortSize); -    auto it = Service::g_srv_services.find(port_name); - -    if (it != Service::g_srv_services.end()) { -        auto client_port = it->second; - -        auto client_session = client_port->Connect(); -        res = client_session.Code(); - -        if (client_session.Succeeded()) { -            // Return the client session -            cmd_buff[3] = Kernel::g_handle_table.Create(*client_session).MoveFrom(); -        } -        LOG_TRACE(Service_SRV, "called port=%s, handle=0x%08X", port_name.c_str(), cmd_buff[3]); -    } else { -        LOG_ERROR(Service_SRV, "(UNIMPLEMENTED) called port=%s", port_name.c_str()); -        res = UnimplementedFunction(ErrorModule::SRV); -    } -    cmd_buff[1] = res.raw; -} - -/** - * SRV::Subscribe service function - *  Inputs: - *      0: 0x00090040 - *      1: Notification ID - *  Outputs: - *      0: 0x00090040 - *      1: ResultCode - */ -static void Subscribe(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    u32 notification_id = cmd_buff[1]; - -    cmd_buff[0] = IPC::MakeHeader(0x9, 0x1, 0); // 0x90040 -    cmd_buff[1] = RESULT_SUCCESS.raw;           // No error -    LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id); -} - -/** - * SRV::Unsubscribe service function - *  Inputs: - *      0: 0x000A0040 - *      1: Notification ID - *  Outputs: - *      0: 0x000A0040 - *      1: ResultCode - */ -static void Unsubscribe(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    u32 notification_id = cmd_buff[1]; - -    cmd_buff[0] = IPC::MakeHeader(0xA, 0x1, 0); // 0xA0040 -    cmd_buff[1] = RESULT_SUCCESS.raw;           // No error -    LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id); -} - -/** - * SRV::PublishToSubscriber service function - *  Inputs: - *      0: 0x000C0080 - *      1: Notification ID - *      2: Flags (bit0: only fire if not fired, bit1: report errors) - *  Outputs: - *      0: 0x000C0040 - *      1: ResultCode - */ -static void PublishToSubscriber(Interface* self) { -    u32* cmd_buff = Kernel::GetCommandBuffer(); - -    u32 notification_id = cmd_buff[1]; -    u8 flags = cmd_buff[2] & 0xFF; - -    cmd_buff[0] = IPC::MakeHeader(0xC, 0x1, 0); // 0xC0040 -    cmd_buff[1] = RESULT_SUCCESS.raw;           // No error -    LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X, flags=%u", notification_id, -                flags); -} - -const Interface::FunctionInfo FunctionTable[] = { -    {0x00010002, RegisterClient, "RegisterClient"}, -    {0x00020000, EnableNotification, "EnableNotification"}, -    {0x00030100, nullptr, "RegisterService"}, -    {0x000400C0, nullptr, "UnregisterService"}, -    {0x00050100, GetServiceHandle, "GetServiceHandle"}, -    {0x000600C2, nullptr, "RegisterPort"}, -    {0x000700C0, nullptr, "UnregisterPort"}, -    {0x00080100, nullptr, "GetPort"}, -    {0x00090040, Subscribe, "Subscribe"}, -    {0x000A0040, Unsubscribe, "Unsubscribe"}, -    {0x000B0000, nullptr, "ReceiveNotification"}, -    {0x000C0080, PublishToSubscriber, "PublishToSubscriber"}, -    {0x000D0040, nullptr, "PublishAndGetSubscriber"}, -    {0x000E00C0, nullptr, "IsServiceRegistered"}, -}; - -SRV::SRV() { -    Register(FunctionTable); -    event_handle = nullptr; -} - -SRV::~SRV() { -    event_handle = nullptr; -} - -} // namespace SRV -} // namespace Service diff --git a/src/core/hle/service/srv.h b/src/core/hle/service/srv.h deleted file mode 100644 index d3a9de879..000000000 --- a/src/core/hle/service/srv.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/hle/service/service.h" - -namespace Service { -namespace SRV { - -/// Interface to "srv:" service -class SRV final : public Interface { -public: -    SRV(); -    ~SRV() override; - -    std::string GetPortName() const override { -        return "srv:"; -    } -}; - -} // namespace SRV -} // namespace Service diff --git a/src/core/hle/service/ssl_c.cpp b/src/core/hle/service/ssl_c.cpp index 09ced9d7a..300acca75 100644 --- a/src/core/hle/service/ssl_c.cpp +++ b/src/core/hle/service/ssl_c.cpp @@ -4,7 +4,9 @@  #include <random>  #include "common/common_types.h" +#include "core/hle/ipc.h"  #include "core/hle/service/ssl_c.h" +#include "core/memory.h"  namespace Service {  namespace SSL { diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index c0837d49d..bb7bf2d67 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -6,6 +6,8 @@  #include "common/common_funcs.h"  #include "common/common_types.h"  #include "common/logging/log.h" +#include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h"  #include "core/hle/kernel/event.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/service/y2r_u.h" diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 30230d65a..e68b9f16a 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <algorithm>  #include <cinttypes>  #include <map>  #include "common/logging/log.h" @@ -16,6 +17,7 @@  #include "core/hle/kernel/client_session.h"  #include "core/hle/kernel/errors.h"  #include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/memory.h"  #include "core/hle/kernel/mutex.h"  #include "core/hle/kernel/process.h" @@ -27,6 +29,7 @@  #include "core/hle/kernel/thread.h"  #include "core/hle/kernel/timer.h"  #include "core/hle/kernel/vm_manager.h" +#include "core/hle/kernel/wait_object.h"  #include "core/hle/result.h"  #include "core/hle/service/service.h" @@ -244,7 +247,7 @@ static ResultCode CloseHandle(Kernel::Handle handle) {  /// Wait for a handle to synchronize, timeout after the specified nanoseconds  static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds) { -    auto object = Kernel::g_handle_table.GetWaitObject(handle); +    auto object = Kernel::g_handle_table.Get<Kernel::WaitObject>(handle);      Kernel::Thread* thread = Kernel::GetCurrentThread();      if (object == nullptr) @@ -299,7 +302,7 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha      std::vector<ObjectPtr> objects(handle_count);      for (int i = 0; i < handle_count; ++i) { -        auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); +        auto object = Kernel::g_handle_table.Get<Kernel::WaitObject>(handles[i]);          if (object == nullptr)              return ERR_INVALID_HANDLE;          objects[i] = object; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 1d80766ae..48bbf687d 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -8,6 +8,7 @@  #include <initializer_list>  #include <memory>  #include <string> +#include <utility>  #include <vector>  #include <boost/optional.hpp>  #include "common/common_types.h" @@ -100,11 +101,11 @@ public:       * Loads the system mode that this application needs.       * This function defaults to 2 (96MB allocated to the application) if it can't read the       * information. -     * @returns Optional with the kernel system mode +     * @returns A pair with the optional system mode, and and the status.       */ -    virtual boost::optional<u32> LoadKernelSystemMode() { +    virtual std::pair<boost::optional<u32>, ResultStatus> LoadKernelSystemMode() {          // 96MB allocated to the application. -        return 2; +        return std::make_pair(2, ResultStatus::Success);      }      /** diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index beeb13ffa..ffc019560 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -121,12 +121,16 @@ FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) {      return FileType::Error;  } -boost::optional<u32> AppLoader_NCCH::LoadKernelSystemMode() { +std::pair<boost::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() {      if (!is_loaded) { -        if (LoadExeFS() != ResultStatus::Success) -            return boost::none; +        ResultStatus res = LoadExeFS(); +        if (res != ResultStatus::Success) { +            return std::make_pair(boost::none, res); +        }      } -    return exheader_header.arm11_system_local_caps.system_mode.Value(); +    // Set the system mode as the one from the exheader. +    return std::make_pair(exheader_header.arm11_system_local_caps.system_mode.Value(), +                          ResultStatus::Success);  }  ResultStatus AppLoader_NCCH::LoadExec() { diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 4ef95b5c6..0ebd47fd5 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -179,9 +179,9 @@ public:      /**       * Loads the Exheader and returns the system mode for this application. -     * @return Optional with the kernel system mode +     * @returns A pair with the optional system mode, and and the status.       */ -    boost::optional<u32> LoadKernelSystemMode() override; +    std::pair<boost::optional<u32>, ResultStatus> LoadKernelSystemMode() override;      ResultStatus ReadCode(std::vector<u8>& buffer) override; diff --git a/src/video_core/regs_lighting.h b/src/video_core/regs_lighting.h index 6793405d9..fbfebc0a7 100644 --- a/src/video_core/regs_lighting.h +++ b/src/video_core/regs_lighting.h @@ -26,6 +26,16 @@ struct LightingRegs {          DistanceAttenuation = 16,      }; +    static LightingSampler SpotlightAttenuationSampler(unsigned index) { +        return static_cast<LightingSampler>( +            static_cast<unsigned>(LightingSampler::SpotlightAttenuation) + index); +    } + +    static LightingSampler DistanceAttenuationSampler(unsigned index) { +        return static_cast<LightingSampler>( +            static_cast<unsigned>(LightingSampler::DistanceAttenuation) + index); +    } +      /**      * Pica fragment lighting supports using different LUTs for each lighting component:  Reflectance      * R, G, and B channels, distribution function for specular components 0 and 1, fresnel factor, @@ -73,6 +83,8 @@ struct LightingRegs {          VH = 1, // Cosine of the angle between the view and half-angle vectors          NV = 2, // Cosine of the angle between the normal and the view vector          LN = 3, // Cosine of the angle between the light and the normal vectors +        SP = 4, // Cosine of the angle between the light and the inverse spotlight vectors +        CP = 5, // TODO: document and implement      };      enum class LightingBumpMode : u32 { @@ -104,6 +116,9 @@ struct LightingRegs {              return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) &&                     (config != LightingConfig::Config5); +        case LightingSampler::SpotlightAttenuation: +            return (config != LightingConfig::Config2) && (config != LightingConfig::Config3); +          case LightingSampler::Fresnel:              return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) &&                     (config != LightingConfig::Config4); @@ -116,11 +131,10 @@ struct LightingRegs {              return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) ||                     (config == LightingConfig::Config7);          default: -            UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached " -                            "unreachable section, sampler should be one " -                            "of Distribution0, Distribution1, Fresnel, " -                            "ReflectRed, ReflectGreen or ReflectBlue, instead " -                            "got %i", +            UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached unreachable section, " +                            "sampler should be one of Distribution0, Distribution1, " +                            "SpotlightAttenuation, Fresnel, ReflectRed, ReflectGreen or " +                            "ReflectBlue, instead got %i",                              static_cast<int>(config));          }      } @@ -140,7 +154,16 @@ struct LightingRegs {              BitField<0, 16, u32> z;          }; -        INSERT_PADDING_WORDS(0x3); +        // inverse spotlight direction vector, encoded as fixed1.1.11 +        union { +            BitField<0, 13, s32> spot_x; +            BitField<16, 13, s32> spot_y; +        }; +        union { +            BitField<0, 13, s32> spot_z; +        }; + +        INSERT_PADDING_WORDS(0x1);          union {              BitField<0, 1, u32> directional; @@ -169,8 +192,16 @@ struct LightingRegs {      } config0;      union { +        u32 raw; + +        // Each bit specifies whether spot light attenuation should be applied for the corresponding +        // light. +        BitField<8, 8, u32> disable_spot_atten; +          BitField<16, 1, u32> disable_lut_d0;          BitField<17, 1, u32> disable_lut_d1; +        // Note: by intuition, BitField<18, 1, u32> should be disable_lut_sp, but it is actually a +        // dummy bit which is always set as 1.          BitField<19, 1, u32> disable_lut_fr;          BitField<20, 1, u32> disable_lut_rr;          BitField<21, 1, u32> disable_lut_rg; @@ -178,23 +209,15 @@ struct LightingRegs {          // Each bit specifies whether distance attenuation should be applied for the corresponding          // light. -        BitField<24, 1, u32> disable_dist_atten_light_0; -        BitField<25, 1, u32> disable_dist_atten_light_1; -        BitField<26, 1, u32> disable_dist_atten_light_2; -        BitField<27, 1, u32> disable_dist_atten_light_3; -        BitField<28, 1, u32> disable_dist_atten_light_4; -        BitField<29, 1, u32> disable_dist_atten_light_5; -        BitField<30, 1, u32> disable_dist_atten_light_6; -        BitField<31, 1, u32> disable_dist_atten_light_7; +        BitField<24, 8, u32> disable_dist_atten;      } config1;      bool IsDistAttenDisabled(unsigned index) const { -        const unsigned disable[] = { -            config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1, -            config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3, -            config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5, -            config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7}; -        return disable[index] != 0; +        return (config1.disable_dist_atten & (1 << index)) != 0; +    } + +    bool IsSpotAttenDisabled(unsigned index) const { +        return (config1.disable_spot_atten & (1 << index)) != 0;      }      union { diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h index 2c654d5c8..0b09f2299 100644 --- a/src/video_core/regs_texturing.h +++ b/src/video_core/regs_texturing.h @@ -133,7 +133,32 @@ struct TexturingRegs {          BitField<16, 1, u32> clear_texture_cache; // TODO: unimplemented      } main_config;      TextureConfig texture0; -    INSERT_PADDING_WORDS(0x8); + +    enum class CubeFace { +        PositiveX = 0, +        NegativeX = 1, +        PositiveY = 2, +        NegativeY = 3, +        PositiveZ = 4, +        NegativeZ = 5, +    }; + +    BitField<0, 22, u32> cube_address[5]; + +    PAddr GetCubePhysicalAddress(CubeFace face) const { +        PAddr address = texture0.address; +        if (face != CubeFace::PositiveX) { +            // Bits [22:27] from the main texture address is shared with all cubemap additional +            // addresses. +            auto& face_addr = cube_address[static_cast<size_t>(face) - 1]; +            address &= ~face_addr.mask; +            address |= face_addr; +        } +        // A multiplier of 8 is also needed in the same way as the main address. +        return address * 8; +    } + +    INSERT_PADDING_WORDS(0x3);      BitField<0, 4, TextureFormat> texture0_format;      BitField<0, 1, u32> fragment_lighting_enable;      INSERT_PADDING_WORDS(0x1); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index aa9b831dd..e6cccebf6 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -182,19 +182,22 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {  RasterizerOpenGL::~RasterizerOpenGL() {}  /** - * This is a helper function to resolve an issue with opposite quaternions being interpolated by - * OpenGL. See below for a detailed description of this issue (yuriks): + * This is a helper function to resolve an issue when interpolating opposite quaternions. See below + * for a detailed description of this issue (yuriks):   *   * For any rotation, there are two quaternions Q, and -Q, that represent the same rotation. If you   * interpolate two quaternions that are opposite, instead of going from one rotation to another   * using the shortest path, you'll go around the longest path. You can test if two quaternions are - * opposite by checking if Dot(Q1, W2) < 0. In that case, you can flip either of them, therefore - * making Dot(-Q1, W2) positive. + * opposite by checking if Dot(Q1, Q2) < 0. In that case, you can flip either of them, therefore + * making Dot(Q1, -Q2) positive.   * - * NOTE: This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This - * should be correct for nearly all cases, however a more correct implementation (but less trivial - * and perhaps unnecessary) would be to handle this per-fragment, by interpolating the quaternions - * manually using two Lerps, and doing this correction before each Lerp. + * This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This is + * correct for most cases but can still rotate around the long way sometimes. An implementation + * which did `lerp(lerp(Q1, Q2), Q3)` (with proper weighting), applying the dot product check + * between each step would work for those cases at the cost of being more complex to implement. + * + * Fortunately however, the 3DS hardware happens to also use this exact same logic to work around + * these issues, making this basic implementation actually more accurate to the hardware.   */  static bool AreQuaternionsOpposite(Math::Vec4<Pica::float24> qa, Math::Vec4<Pica::float24> qb) {      Math::Vec4f a{qa.x.ToFloat32(), qa.y.ToFloat32(), qa.z.ToFloat32(), qa.w.ToFloat32()}; @@ -735,6 +738,40 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {          SyncLightPosition(7);          break; +    // Fragment spot lighting direction +    case PICA_REG_INDEX_WORKAROUND(lighting.light[0].spot_x, 0x146 + 0 * 0x10): +    case PICA_REG_INDEX_WORKAROUND(lighting.light[0].spot_z, 0x147 + 0 * 0x10): +        SyncLightSpotDirection(0); +        break; +    case PICA_REG_INDEX_WORKAROUND(lighting.light[1].spot_x, 0x146 + 1 * 0x10): +    case PICA_REG_INDEX_WORKAROUND(lighting.light[1].spot_z, 0x147 + 1 * 0x10): +        SyncLightSpotDirection(1); +        break; +    case PICA_REG_INDEX_WORKAROUND(lighting.light[2].spot_x, 0x146 + 2 * 0x10): +    case PICA_REG_INDEX_WORKAROUND(lighting.light[2].spot_z, 0x147 + 2 * 0x10): +        SyncLightSpotDirection(2); +        break; +    case PICA_REG_INDEX_WORKAROUND(lighting.light[3].spot_x, 0x146 + 3 * 0x10): +    case PICA_REG_INDEX_WORKAROUND(lighting.light[3].spot_z, 0x147 + 3 * 0x10): +        SyncLightSpotDirection(3); +        break; +    case PICA_REG_INDEX_WORKAROUND(lighting.light[4].spot_x, 0x146 + 4 * 0x10): +    case PICA_REG_INDEX_WORKAROUND(lighting.light[4].spot_z, 0x147 + 4 * 0x10): +        SyncLightSpotDirection(4); +        break; +    case PICA_REG_INDEX_WORKAROUND(lighting.light[5].spot_x, 0x146 + 5 * 0x10): +    case PICA_REG_INDEX_WORKAROUND(lighting.light[5].spot_z, 0x147 + 5 * 0x10): +        SyncLightSpotDirection(5); +        break; +    case PICA_REG_INDEX_WORKAROUND(lighting.light[6].spot_x, 0x146 + 6 * 0x10): +    case PICA_REG_INDEX_WORKAROUND(lighting.light[6].spot_z, 0x147 + 6 * 0x10): +        SyncLightSpotDirection(6); +        break; +    case PICA_REG_INDEX_WORKAROUND(lighting.light[7].spot_x, 0x146 + 7 * 0x10): +    case PICA_REG_INDEX_WORKAROUND(lighting.light[7].spot_z, 0x147 + 7 * 0x10): +        SyncLightSpotDirection(7); +        break; +      // Fragment lighting light source config      case PICA_REG_INDEX_WORKAROUND(lighting.light[0].config, 0x149 + 0 * 0x10):      case PICA_REG_INDEX_WORKAROUND(lighting.light[1].config, 0x149 + 1 * 0x10): @@ -1595,6 +1632,17 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) {      }  } +void RasterizerOpenGL::SyncLightSpotDirection(int light_index) { +    const auto& light = Pica::g_state.regs.lighting.light[light_index]; +    GLvec3 spot_direction = {light.spot_x / 2047.0f, light.spot_y / 2047.0f, +                             light.spot_z / 2047.0f}; + +    if (spot_direction != uniform_block_data.data.light_src[light_index].spot_direction) { +        uniform_block_data.data.light_src[light_index].spot_direction = spot_direction; +        uniform_block_data.dirty = true; +    } +} +  void RasterizerOpenGL::SyncLightDistanceAttenuationBias(int light_index) {      GLfloat dist_atten_bias =          Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_bias) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index a9ad7d660..d9a3e9d1c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -125,6 +125,7 @@ private:          alignas(16) GLvec3 diffuse;          alignas(16) GLvec3 ambient;          alignas(16) GLvec3 position; +        alignas(16) GLvec3 spot_direction; // negated          GLfloat dist_atten_bias;          GLfloat dist_atten_scale;      }; @@ -153,7 +154,7 @@ private:      };      static_assert( -        sizeof(UniformData) == 0x3E0, +        sizeof(UniformData) == 0x460,          "The size of the UniformData structure has changed, update the structure in the shader");      static_assert(sizeof(UniformData) < 16384,                    "UniformData structure must be less than 16kb as per the OpenGL spec"); @@ -241,6 +242,9 @@ private:      /// Syncs the specified light's position to match the PICA register      void SyncLightPosition(int light_index); +    /// Syncs the specified spot light direcition to match the PICA register +    void SyncLightSpotDirection(int light_index); +      /// Syncs the specified light's distance attenuation bias to match the PICA register      void SyncLightDistanceAttenuationBias(int light_index); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index ffe419863..db53710aa 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -75,6 +75,8 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {          state.lighting.light[light_index].two_sided_diffuse = light.config.two_sided_diffuse != 0;          state.lighting.light[light_index].dist_atten_enable =              !regs.lighting.IsDistAttenDisabled(num); +        state.lighting.light[light_index].spot_atten_enable = +            !regs.lighting.IsSpotAttenDisabled(num);      }      state.lighting.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0; @@ -87,6 +89,12 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {      state.lighting.lut_d1.type = regs.lighting.lut_input.d1.Value();      state.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1); +    // this is a dummy field due to lack of the corresponding register +    state.lighting.lut_sp.enable = true; +    state.lighting.lut_sp.abs_input = regs.lighting.abs_lut_input.disable_sp == 0; +    state.lighting.lut_sp.type = regs.lighting.lut_input.sp.Value(); +    state.lighting.lut_sp.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.sp); +      state.lighting.lut_fr.enable = regs.lighting.config1.disable_lut_fr == 0;      state.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0;      state.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value(); @@ -509,7 +517,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {      out += "vec4 diffuse_sum = vec4(0.0, 0.0, 0.0, 1.0);\n"             "vec4 specular_sum = vec4(0.0, 0.0, 0.0, 1.0);\n"             "vec3 light_vector = vec3(0.0);\n" -           "vec3 refl_value = vec3(0.0);\n"; +           "vec3 refl_value = vec3(0.0);\n" +           "vec3 spot_dir = vec3(0.0);\n;";      // Compute fragment normals      if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) { @@ -560,6 +569,10 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {              index = std::string("dot(light_vector, normal)");              break; +        case LightingRegs::LightingLutInput::SP: +            index = std::string("dot(light_vector, spot_dir)"); +            break; +          default:              LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %d\n", (int)input);              UNIMPLEMENTED(); @@ -596,21 +609,34 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {          else              out += "light_vector = normalize(" + light_src + ".position + view);\n"; +        out += "spot_dir = " + light_src + ".spot_direction;\n"; +          // Compute dot product of light_vector and normal, adjust if lighting is one-sided or          // two-sided          std::string dot_product = light_config.two_sided_diffuse                                        ? "abs(dot(light_vector, normal))"                                        : "max(dot(light_vector, normal), 0.0)"; +        // If enabled, compute spot light attenuation value +        std::string spot_atten = "1.0"; +        if (light_config.spot_atten_enable && +            LightingRegs::IsLightingSamplerSupported( +                lighting.config, LightingRegs::LightingSampler::SpotlightAttenuation)) { +            std::string index = +                GetLutIndex(light_config.num, lighting.lut_sp.type, lighting.lut_sp.abs_input); +            auto sampler = LightingRegs::SpotlightAttenuationSampler(light_config.num); +            spot_atten = "(" + std::to_string(lighting.lut_sp.scale) + " * " + +                         GetLutValue(sampler, index) + ")"; +        } +          // If enabled, compute distance attenuation value          std::string dist_atten = "1.0";          if (light_config.dist_atten_enable) {              std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " +                                  light_src + ".position) + " + light_src + ".dist_atten_bias)";              index = "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))"; -            const unsigned lut_num = -                ((unsigned)LightingRegs::LightingSampler::DistanceAttenuation + light_config.num); -            dist_atten = GetLutValue((LightingRegs::LightingSampler)lut_num, index); +            auto sampler = LightingRegs::DistanceAttenuationSampler(light_config.num); +            dist_atten = GetLutValue(sampler, index);          }          // If enabled, clamp specular component if lighting result is negative @@ -711,11 +737,11 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {          // Compute primary fragment color (diffuse lighting) function          out += "diffuse_sum.rgb += ((" + light_src + ".diffuse * " + dot_product + ") + " + -               light_src + ".ambient) * " + dist_atten + ";\n"; +               light_src + ".ambient) * " + dist_atten + " * " + spot_atten + ";\n";          // Compute secondary fragment color (specular lighting) function          out += "specular_sum.rgb += (" + specular_0 + " + " + specular_1 + ") * " + -               clamp_highlights + " * " + dist_atten + ";\n"; +               clamp_highlights + " * " + dist_atten + " * " + spot_atten + ";\n";      }      // Sum final lighting result @@ -967,6 +993,7 @@ struct LightSrc {      vec3 diffuse;      vec3 ambient;      vec3 position; +    vec3 spot_direction;      float dist_atten_bias;      float dist_atten_scale;  }; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index ea6d216d1..9c90eadf9 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -93,6 +93,7 @@ union PicaShaderConfig {                  bool directional;                  bool two_sided_diffuse;                  bool dist_atten_enable; +                bool spot_atten_enable;              } light[8];              bool enable; @@ -110,7 +111,7 @@ union PicaShaderConfig {                  bool abs_input;                  Pica::LightingRegs::LightingLutInput type;                  float scale; -            } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb; +            } lut_d0, lut_d1, lut_sp, lut_fr, lut_rr, lut_rg, lut_rb;          } lighting;          struct { diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index 908c7bb9e..cd7b6c39d 100644 --- a/src/video_core/swrasterizer/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -5,6 +5,7 @@  #include <algorithm>  #include <array>  #include <cmath> +#include <tuple>  #include "common/assert.h"  #include "common/bit_field.h"  #include "common/color.h" @@ -70,6 +71,49 @@ static int SignedArea(const Math::Vec2<Fix12P4>& vtx1, const Math::Vec2<Fix12P4>      return Math::Cross(vec1, vec2).z;  }; +/// Convert a 3D vector for cube map coordinates to 2D texture coordinates along with the face name +static std::tuple<float24, float24, PAddr> ConvertCubeCoord(float24 u, float24 v, float24 w, +                                                            const TexturingRegs& regs) { +    const float abs_u = std::abs(u.ToFloat32()); +    const float abs_v = std::abs(v.ToFloat32()); +    const float abs_w = std::abs(w.ToFloat32()); +    float24 x, y, z; +    PAddr addr; +    if (abs_u > abs_v && abs_u > abs_w) { +        if (u > float24::FromFloat32(0)) { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveX); +            y = -v; +        } else { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeX); +            y = v; +        } +        x = -w; +        z = u; +    } else if (abs_v > abs_w) { +        if (v > float24::FromFloat32(0)) { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveY); +            x = u; +        } else { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeY); +            x = -u; +        } +        y = w; +        z = v; +    } else { +        if (w > float24::FromFloat32(0)) { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::PositiveZ); +            y = -v; +        } else { +            addr = regs.GetCubePhysicalAddress(TexturingRegs::CubeFace::NegativeZ); +            y = v; +        } +        x = u; +        z = w; +    } +    const float24 half = float24::FromFloat32(0.5f); +    return std::make_tuple(x / z * half + half, y / z * half + half, addr); +} +  MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 240));  /** @@ -284,10 +328,16 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                  // Only unit 0 respects the texturing type (according to 3DBrew)                  // TODO: Refactor so cubemaps and shadowmaps can be handled +                PAddr texture_address = texture.config.GetPhysicalAddress();                  if (i == 0) {                      switch (texture.config.type) {                      case TexturingRegs::TextureConfig::Texture2D:                          break; +                    case TexturingRegs::TextureConfig::TextureCube: { +                        auto w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w); +                        std::tie(u, v, texture_address) = ConvertCubeCoord(u, v, w, regs.texturing); +                        break; +                    }                      case TexturingRegs::TextureConfig::Projection2D: {                          auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w);                          u /= tc0_w; @@ -334,8 +384,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                      t = texture.config.height - 1 -                          GetWrappedTexCoord(texture.config.wrap_t, t, texture.config.height); -                    u8* texture_data = -                        Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); +                    const u8* texture_data = Memory::GetPhysicalPointer(texture_address);                      auto info =                          Texture::TextureInfo::FromPicaRegister(texture.config, texture.format);  | 
